1use anyhow::Result;
2use serde_json::Value as JsonValue;
3use turbo_rcstr::{RcStr, rcstr};
4use turbo_tasks::{FxIndexSet, ResolvedVc, Vc};
5use turbo_tasks_env::ProcessEnv;
6use turbo_tasks_fs::FileSystemPath;
7use turbopack_core::introspect::{
8 Introspectable, IntrospectableChildren, module::IntrospectableModule,
9 output_asset::IntrospectableOutputAsset,
10};
11use turbopack_dev_server::source::{
12 ContentSource, ContentSourceContent, ContentSourceData, ContentSourceDataVary,
13 GetContentSourceContent,
14 route_tree::{BaseSegment, RouteTree, RouteType},
15};
16
17use super::{RenderData, render_proxy::render_proxy_operation};
18use crate::{get_intermediate_asset, node_entry::NodeEntry, route_matcher::RouteMatcher};
19
20#[turbo_tasks::function]
22pub fn create_node_api_source(
23 cwd: FileSystemPath,
24 env: ResolvedVc<Box<dyn ProcessEnv>>,
25 base_segments: Vec<BaseSegment>,
26 route_type: RouteType,
27 server_root: FileSystemPath,
28 route_match: ResolvedVc<Box<dyn RouteMatcher>>,
29 pathname: ResolvedVc<RcStr>,
30 entry: ResolvedVc<Box<dyn NodeEntry>>,
31 render_data: ResolvedVc<JsonValue>,
32 debug: bool,
33) -> Vc<Box<dyn ContentSource>> {
34 Vc::upcast(
35 NodeApiContentSource {
36 cwd,
37 env,
38 base_segments,
39 route_type,
40 server_root,
41 pathname,
42 route_match,
43 entry,
44 render_data,
45 debug,
46 }
47 .cell(),
48 )
49}
50
51#[turbo_tasks::value]
58pub struct NodeApiContentSource {
59 cwd: FileSystemPath,
60 env: ResolvedVc<Box<dyn ProcessEnv>>,
61 base_segments: Vec<BaseSegment>,
62 route_type: RouteType,
63 server_root: FileSystemPath,
64 pathname: ResolvedVc<RcStr>,
65 route_match: ResolvedVc<Box<dyn RouteMatcher>>,
66 entry: ResolvedVc<Box<dyn NodeEntry>>,
67 render_data: ResolvedVc<JsonValue>,
68 debug: bool,
69}
70
71#[turbo_tasks::value_impl]
72impl NodeApiContentSource {
73 #[turbo_tasks::function]
74 pub fn get_pathname(&self) -> Vc<RcStr> {
75 *self.pathname
76 }
77}
78
79#[turbo_tasks::value_impl]
80impl ContentSource for NodeApiContentSource {
81 #[turbo_tasks::function]
82 async fn get_routes(self: Vc<Self>) -> Result<Vc<RouteTree>> {
83 let this = self.await?;
84 Ok(RouteTree::new_route(
85 this.base_segments.clone(),
86 this.route_type.clone(),
87 Vc::upcast(self),
88 ))
89 }
90}
91
92#[turbo_tasks::value_impl]
93impl GetContentSourceContent for NodeApiContentSource {
94 #[turbo_tasks::function]
95 fn vary(&self) -> Vc<ContentSourceDataVary> {
96 ContentSourceDataVary {
97 method: true,
98 url: true,
99 original_url: true,
100 raw_headers: true,
101 raw_query: true,
102 body: true,
103 cache_buster: true,
104 ..Default::default()
105 }
106 .cell()
107 }
108
109 #[turbo_tasks::function]
110 async fn get(&self, path: RcStr, data: ContentSourceData) -> Result<Vc<ContentSourceContent>> {
111 let Some(params) = &*self.route_match.params(path.clone()).await? else {
112 anyhow::bail!("Non matching path provided")
113 };
114 let ContentSourceData {
115 method: Some(method),
116 url: Some(url),
117 original_url: Some(original_url),
118 raw_headers: Some(raw_headers),
119 raw_query: Some(raw_query),
120 body: Some(body),
121 ..
122 } = &data
123 else {
124 anyhow::bail!("Missing request data")
125 };
126 let entry = (*self.entry).entry(data.clone()).await?;
127 Ok(ContentSourceContent::HttpProxy(render_proxy_operation(
128 self.cwd.clone(),
129 self.env,
130 self.server_root.join(&path.clone())?,
131 ResolvedVc::upcast(entry.module),
132 entry.runtime_entries,
133 entry.chunking_context,
134 entry.intermediate_output_path.clone(),
135 entry.output_root.clone(),
136 entry.project_dir.clone(),
137 RenderData {
138 params: params.clone(),
139 method: method.clone(),
140 url: url.clone(),
141 original_url: original_url.clone(),
142 raw_query: raw_query.clone(),
143 raw_headers: raw_headers.clone(),
144 path: format!("/{path}").into(),
145 data: Some(self.render_data.await?),
146 }
147 .resolved_cell(),
148 *body,
149 self.debug,
150 ))
151 .cell())
152 }
153}
154
155#[turbo_tasks::value_impl]
156impl Introspectable for NodeApiContentSource {
157 #[turbo_tasks::function]
158 fn ty(&self) -> Vc<RcStr> {
159 Vc::cell(rcstr!("node api content source"))
160 }
161
162 #[turbo_tasks::function]
163 fn title(&self) -> Vc<RcStr> {
164 *self.pathname
165 }
166
167 #[turbo_tasks::function]
168 fn details(&self) -> Vc<RcStr> {
169 Vc::cell(
170 format!(
171 "base: {:?}\ntype: {:?}",
172 self.base_segments, self.route_type
173 )
174 .into(),
175 )
176 }
177
178 #[turbo_tasks::function]
179 async fn children(&self) -> Result<Vc<IntrospectableChildren>> {
180 let mut set = FxIndexSet::default();
181 for &entry in self.entry.entries().await?.iter() {
182 let entry = entry.await?;
183 set.insert((
184 rcstr!("module"),
185 IntrospectableModule::new(Vc::upcast(*entry.module))
186 .to_resolved()
187 .await?,
188 ));
189 set.insert((
190 rcstr!("intermediate asset"),
191 IntrospectableOutputAsset::new(get_intermediate_asset(
192 *entry.chunking_context,
193 Vc::upcast(*entry.module),
194 *entry.runtime_entries,
195 ))
196 .to_resolved()
197 .await?,
198 ));
199 }
200 Ok(Vc::cell(set))
201 }
202}