1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use turbo_rcstr::RcStr;
4use turbo_tasks::{
5 CollectiblesSource, FxIndexMap, NonLocalValue, OperationValue, OperationVc, ResolvedVc,
6 TaskInput, Vc, debug::ValueDebugFormat, get_effects, trace::TraceRawVcs,
7};
8use turbopack_core::{diagnostics::Diagnostic, issue::IssueDescriptionExt};
9
10use crate::{
11 entrypoints::Entrypoints,
12 route::{Endpoint, Route},
13};
14
15#[turbo_tasks::value(shared)]
24pub struct EntrypointsOperation {
25 pub routes: FxIndexMap<RcStr, RouteOperation>,
26 pub middleware: Option<MiddlewareOperation>,
27 pub instrumentation: Option<InstrumentationOperation>,
28 pub pages_document_endpoint: OperationVc<OptionEndpoint>,
29 pub pages_app_endpoint: OperationVc<OptionEndpoint>,
30 pub pages_error_endpoint: OperationVc<OptionEndpoint>,
31}
32
33#[turbo_tasks::function(operation)]
36async fn entrypoints_without_collectibles_operation(
37 entrypoints: OperationVc<Entrypoints>,
38) -> Result<Vc<Entrypoints>> {
39 let _ = entrypoints.resolve_strongly_consistent().await?;
40 let _ = entrypoints.take_collectibles::<Box<dyn Diagnostic>>();
41 let _ = entrypoints.take_issues_with_path().await?;
42 let _ = get_effects(entrypoints).await?;
43 Ok(entrypoints.connect())
44}
45
46#[turbo_tasks::value_impl]
47impl EntrypointsOperation {
48 #[turbo_tasks::function(operation)]
49 pub async fn new(entrypoints: OperationVc<Entrypoints>) -> Result<Vc<Self>> {
50 let e = entrypoints.connect().await?;
51 let entrypoints = entrypoints_without_collectibles_operation(entrypoints);
52 Ok(Self {
53 routes: e
54 .routes
55 .iter()
56 .map(|(k, v)| (k.clone(), pick_route(entrypoints, k.clone(), v)))
57 .collect(),
58 middleware: e.middleware.as_ref().map(|_| MiddlewareOperation {
59 endpoint: pick_endpoint(entrypoints, EndpointSelector::Middleware),
60 }),
61 instrumentation: e
62 .instrumentation
63 .as_ref()
64 .map(|_| InstrumentationOperation {
65 node_js: pick_endpoint(entrypoints, EndpointSelector::InstrumentationNodeJs),
66 edge: pick_endpoint(entrypoints, EndpointSelector::InstrumentationEdge),
67 }),
68 pages_document_endpoint: pick_endpoint(entrypoints, EndpointSelector::PagesDocument),
69 pages_app_endpoint: pick_endpoint(entrypoints, EndpointSelector::PagesApp),
70 pages_error_endpoint: pick_endpoint(entrypoints, EndpointSelector::PagesError),
71 }
72 .cell())
73 }
74}
75
76fn pick_route(entrypoints: OperationVc<Entrypoints>, key: RcStr, route: &Route) -> RouteOperation {
77 match route {
78 Route::Page { .. } => RouteOperation::Page {
79 html_endpoint: pick_endpoint(entrypoints, EndpointSelector::RoutePageHtml(key.clone())),
80 data_endpoint: pick_endpoint(entrypoints, EndpointSelector::RoutePageData(key)),
81 },
82 Route::PageApi { .. } => RouteOperation::PageApi {
83 endpoint: pick_endpoint(entrypoints, EndpointSelector::RoutePageApi(key)),
84 },
85 Route::AppPage(pages) => RouteOperation::AppPage(
86 pages
87 .iter()
88 .enumerate()
89 .map(|(i, p)| AppPageRouteOperation {
90 original_name: p.original_name.clone(),
91 html_endpoint: pick_endpoint(
92 entrypoints,
93 EndpointSelector::RouteAppPageHtml(key.clone(), i),
94 ),
95 rsc_endpoint: pick_endpoint(
96 entrypoints,
97 EndpointSelector::RouteAppPageRsc(key.clone(), i),
98 ),
99 })
100 .collect(),
101 ),
102 Route::AppRoute { original_name, .. } => RouteOperation::AppRoute {
103 original_name: original_name.clone(),
104 endpoint: pick_endpoint(entrypoints, EndpointSelector::RouteAppRoute(key)),
105 },
106 Route::Conflict => RouteOperation::Conflict,
107 }
108}
109
110#[derive(
111 Debug,
112 Clone,
113 TaskInput,
114 Serialize,
115 Deserialize,
116 TraceRawVcs,
117 PartialEq,
118 Eq,
119 Hash,
120 ValueDebugFormat,
121 NonLocalValue,
122 OperationValue,
123)]
124enum EndpointSelector {
125 RoutePageHtml(RcStr),
126 RoutePageData(RcStr),
127 RoutePageApi(RcStr),
128 RouteAppPageHtml(RcStr, usize),
129 RouteAppPageRsc(RcStr, usize),
130 RouteAppRoute(RcStr),
131 InstrumentationNodeJs,
132 InstrumentationEdge,
133 Middleware,
134 PagesDocument,
135 PagesApp,
136 PagesError,
137}
138
139#[turbo_tasks::value(transparent)]
140pub struct OptionEndpoint(Option<ResolvedVc<Box<dyn Endpoint>>>);
141
142#[turbo_tasks::function(operation)]
146async fn pick_endpoint(
147 op: OperationVc<Entrypoints>,
148 selector: EndpointSelector,
149) -> Result<Vc<OptionEndpoint>> {
150 let endpoints = op.connect().strongly_consistent().await?;
151 let endpoint = match selector {
152 EndpointSelector::InstrumentationNodeJs => {
153 endpoints.instrumentation.as_ref().map(|i| i.node_js)
154 }
155 EndpointSelector::InstrumentationEdge => endpoints.instrumentation.as_ref().map(|i| i.edge),
156 EndpointSelector::Middleware => endpoints.middleware.as_ref().map(|m| m.endpoint),
157 EndpointSelector::PagesDocument => Some(endpoints.pages_document_endpoint),
158 EndpointSelector::PagesApp => Some(endpoints.pages_app_endpoint),
159 EndpointSelector::PagesError => Some(endpoints.pages_error_endpoint),
160 EndpointSelector::RoutePageHtml(name) => {
161 if let Some(Route::Page { html_endpoint, .. }) = endpoints.routes.get(&name) {
162 Some(*html_endpoint)
163 } else {
164 None
165 }
166 }
167 EndpointSelector::RoutePageData(name) => {
168 if let Some(Route::Page { data_endpoint, .. }) = endpoints.routes.get(&name) {
169 Some(*data_endpoint)
170 } else {
171 None
172 }
173 }
174 EndpointSelector::RoutePageApi(name) => {
175 if let Some(Route::PageApi { endpoint }) = endpoints.routes.get(&name) {
176 Some(*endpoint)
177 } else {
178 None
179 }
180 }
181 EndpointSelector::RouteAppPageHtml(name, i) => {
182 if let Some(Route::AppPage(pages)) = endpoints.routes.get(&name) {
183 pages.get(i).as_ref().map(|p| p.html_endpoint)
184 } else {
185 None
186 }
187 }
188 EndpointSelector::RouteAppPageRsc(name, i) => {
189 if let Some(Route::AppPage(pages)) = endpoints.routes.get(&name) {
190 pages.get(i).as_ref().map(|p| p.rsc_endpoint)
191 } else {
192 None
193 }
194 }
195 EndpointSelector::RouteAppRoute(name) => {
196 if let Some(Route::AppRoute { endpoint, .. }) = endpoints.routes.get(&name) {
197 Some(*endpoint)
198 } else {
199 None
200 }
201 }
202 };
203 Ok(Vc::cell(endpoint))
204}
205
206#[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue)]
207pub struct InstrumentationOperation {
208 pub node_js: OperationVc<OptionEndpoint>,
209 pub edge: OperationVc<OptionEndpoint>,
210}
211
212#[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue)]
213pub struct MiddlewareOperation {
214 pub endpoint: OperationVc<OptionEndpoint>,
215}
216
217#[turbo_tasks::value(shared)]
218#[derive(Clone, Debug)]
219pub enum RouteOperation {
220 Page {
221 html_endpoint: OperationVc<OptionEndpoint>,
222 data_endpoint: OperationVc<OptionEndpoint>,
223 },
224 PageApi {
225 endpoint: OperationVc<OptionEndpoint>,
226 },
227 AppPage(Vec<AppPageRouteOperation>),
228 AppRoute {
229 original_name: RcStr,
230 endpoint: OperationVc<OptionEndpoint>,
231 },
232 Conflict,
233}
234
235#[derive(
236 TraceRawVcs,
237 Serialize,
238 Deserialize,
239 PartialEq,
240 Eq,
241 ValueDebugFormat,
242 Clone,
243 Debug,
244 NonLocalValue,
245)]
246pub struct AppPageRouteOperation {
247 pub original_name: RcStr,
248 pub html_endpoint: OperationVc<OptionEndpoint>,
249 pub rsc_endpoint: OperationVc<OptionEndpoint>,
250}