1pub mod asset_graph;
2pub mod combined;
3pub mod headers;
4pub mod issue_context;
5pub mod lazy_instantiated;
6pub mod query;
7pub mod request;
8pub mod resolve;
9pub mod route_tree;
10pub mod router;
11pub mod static_assets;
12pub mod wrapping_source;
13
14use std::collections::BTreeSet;
15
16use anyhow::Result;
17use bincode::{Decode, Encode};
18use futures::{TryStreamExt, stream::Stream as StreamTrait};
19use turbo_rcstr::RcStr;
20use turbo_tasks::{
21 Completion, NonLocalValue, OperationVc, ResolvedVc, Upcast, Vc, trace::TraceRawVcs,
22 util::SharedError,
23};
24use turbo_tasks_bytes::{Bytes, Stream, StreamRead};
25use turbo_tasks_fs::FileSystemPath;
26use turbo_tasks_hash::{DeterministicHash, DeterministicHasher, Xxh3Hash64Hasher};
27use turbopack_core::version::{Version, VersionedContent};
28
29use crate::source::{
30 headers::Headers, issue_context::IssueFilePathContentSource, query::Query,
31 route_tree::RouteTree,
32};
33
34#[turbo_tasks::value(shared, operation)]
36pub struct ProxyResult {
37 pub status: u16,
39 pub headers: Vec<(RcStr, RcStr)>,
41 #[turbo_tasks(trace_ignore)]
43 pub body: Body,
44}
45
46#[turbo_tasks::value_impl]
47impl Version for ProxyResult {
48 #[turbo_tasks::function]
49 async fn id(&self) -> Result<Vc<RcStr>> {
50 let mut hash = Xxh3Hash64Hasher::new();
51 hash.write_u16(self.status);
52 for (name, value) in &self.headers {
53 name.deterministic_hash(&mut hash);
54 value.deterministic_hash(&mut hash);
55 }
56 let mut read = self.body.read();
57 while let Some(chunk) = read.try_next().await? {
58 hash.write_bytes(&chunk);
59 }
60 Ok(Vc::cell(hash.finish().to_string().into()))
61 }
62}
63
64#[turbo_tasks::value_trait]
66pub trait GetContentSourceContent {
67 #[turbo_tasks::function]
70 fn vary(self: Vc<Self>) -> Vc<ContentSourceDataVary> {
71 ContentSourceDataVary::default().cell()
72 }
73
74 #[turbo_tasks::function]
75 fn get(self: Vc<Self>, path: RcStr, data: ContentSourceData) -> Vc<ContentSourceContent>;
76}
77
78#[turbo_tasks::value(transparent)]
79pub struct GetContentSourceContents(Vec<ResolvedVc<Box<dyn GetContentSourceContent>>>);
80
81#[turbo_tasks::value]
82pub struct StaticContent {
83 pub content: ResolvedVc<Box<dyn VersionedContent>>,
84 pub status_code: u16,
85 pub headers: ResolvedVc<HeaderList>,
86}
87
88#[turbo_tasks::value(shared)]
89pub enum ContentSourceContent {
92 NotFound,
93 Static(ResolvedVc<StaticContent>),
94 HttpProxy(OperationVc<ProxyResult>),
95 Rewrite(ResolvedVc<Rewrite>),
96 Next,
98}
99
100#[turbo_tasks::value_trait]
104pub trait ContentSourceSideEffect {
105 #[turbo_tasks::function]
106 fn apply(self: Vc<Self>) -> Vc<Completion>;
107}
108
109#[turbo_tasks::value_impl]
110impl GetContentSourceContent for ContentSourceContent {
111 #[turbo_tasks::function]
112 fn get(self: Vc<Self>, _path: RcStr, _data: ContentSourceData) -> Vc<ContentSourceContent> {
113 self
114 }
115}
116
117#[turbo_tasks::value_impl]
118impl ContentSourceContent {
119 #[turbo_tasks::function]
120 pub async fn static_content(
121 content: ResolvedVc<Box<dyn VersionedContent>>,
122 ) -> Result<Vc<ContentSourceContent>> {
123 Ok(ContentSourceContent::Static(
124 StaticContent {
125 content,
126 status_code: 200,
127 headers: HeaderList::empty().to_resolved().await?,
128 }
129 .resolved_cell(),
130 )
131 .cell())
132 }
133
134 #[turbo_tasks::function]
135 pub fn not_found() -> Vc<ContentSourceContent> {
136 ContentSourceContent::NotFound.cell()
137 }
138}
139
140#[turbo_tasks::value(transparent)]
142pub struct HeaderList(Vec<(RcStr, RcStr)>);
143
144#[turbo_tasks::value_impl]
145impl HeaderList {
146 #[turbo_tasks::function]
147 pub fn new(headers: Vec<(RcStr, RcStr)>) -> Vc<Self> {
148 HeaderList(headers).cell()
149 }
150
151 #[turbo_tasks::function]
152 pub fn empty() -> Vc<Self> {
153 HeaderList(vec![]).cell()
154 }
155}
156
157#[turbo_tasks::task_input]
162#[derive(PartialEq, Eq, TraceRawVcs, Clone, Debug, Hash, Default, Encode, Decode)]
163pub struct ContentSourceData {
164 pub method: Option<RcStr>,
166 pub url: Option<RcStr>,
168 pub original_url: Option<RcStr>,
171 pub query: Option<Query>,
173 pub raw_query: Option<RcStr>,
175 pub headers: Option<Headers>,
178 pub raw_headers: Option<Vec<(RcStr, RcStr)>>,
181 pub body: Option<ResolvedVc<Body>>,
183 pub cache_buster: u64,
185}
186
187pub type BodyChunk = Result<Bytes, SharedError>;
188
189#[turbo_tasks::value(shared)]
191#[derive(Default, Clone, Debug)]
192pub struct Body {
193 #[turbo_tasks(trace_ignore)]
194 chunks: Stream<BodyChunk>,
195}
196
197impl Body {
198 pub fn new(chunks: Vec<BodyChunk>) -> Self {
200 Self {
201 chunks: Stream::new_closed(chunks),
202 }
203 }
204
205 pub fn read(&self) -> StreamRead<BodyChunk> {
207 self.chunks.read()
208 }
209
210 pub fn from_stream<T: StreamTrait<Item = BodyChunk> + Send + Unpin + 'static>(
211 source: T,
212 ) -> Self {
213 Self {
214 chunks: Stream::from(source),
215 }
216 }
217}
218
219impl<T: Into<Bytes>> From<T> for Body {
220 fn from(value: T) -> Self {
221 Body::new(vec![Ok(value.into())])
222 }
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, TraceRawVcs, Hash, NonLocalValue, Encode, Decode)]
227pub enum ContentSourceDataFilter {
228 All,
229 Subset(BTreeSet<String>),
230}
231
232impl ContentSourceDataFilter {
233 pub fn extend(&mut self, other: &ContentSourceDataFilter) {
235 match self {
236 ContentSourceDataFilter::All => {}
237 ContentSourceDataFilter::Subset(set) => match other {
238 ContentSourceDataFilter::All => *self = ContentSourceDataFilter::All,
239 ContentSourceDataFilter::Subset(set2) => set.extend(set2.iter().cloned()),
240 },
241 }
242 }
243
244 pub fn extend_options(
248 this: &mut Option<ContentSourceDataFilter>,
249 other: &Option<ContentSourceDataFilter>,
250 ) {
251 if let Some(this) = this.as_mut() {
252 if let Some(other) = other.as_ref() {
253 this.extend(other);
254 }
255 } else {
256 this.clone_from(other);
257 }
258 }
259
260 pub fn contains(&self, key: &str) -> bool {
262 match self {
263 ContentSourceDataFilter::All => true,
264 ContentSourceDataFilter::Subset(set) => set.contains(key),
265 }
266 }
267
268 pub fn fulfills(
271 this: &Option<ContentSourceDataFilter>,
272 other: &Option<ContentSourceDataFilter>,
273 ) -> bool {
274 match (this, other) {
275 (_, None) => true,
276 (None, Some(_)) => false,
277 (Some(this), Some(other)) => match (this, other) {
278 (ContentSourceDataFilter::All, _) => true,
279 (_, ContentSourceDataFilter::All) => false,
280 (ContentSourceDataFilter::Subset(this), ContentSourceDataFilter::Subset(other)) => {
281 this.is_superset(other)
282 }
283 },
284 }
285 }
286}
287
288#[turbo_tasks::value(shared)]
292#[derive(Debug, Default, Clone, Hash)]
293pub struct ContentSourceDataVary {
294 pub method: bool,
295 pub url: bool,
296 pub original_url: bool,
297 pub query: Option<ContentSourceDataFilter>,
298 pub raw_query: bool,
299 pub headers: Option<ContentSourceDataFilter>,
300 pub raw_headers: bool,
301 pub body: bool,
302 pub cache_buster: bool,
306 pub placeholder_for_future_extensions: (),
307}
308
309impl ContentSourceDataVary {
310 pub fn extend(&mut self, other: &ContentSourceDataVary) {
313 let ContentSourceDataVary {
314 method,
315 url,
316 original_url,
317 query,
318 raw_query,
319 headers,
320 raw_headers,
321 body,
322 cache_buster,
323 placeholder_for_future_extensions: _,
324 } = self;
325 *method = *method || other.method;
326 *url = *url || other.url;
327 *original_url = *original_url || other.original_url;
328 *body = *body || other.body;
329 *cache_buster = *cache_buster || other.cache_buster;
330 *raw_query = *raw_query || other.raw_query;
331 *raw_headers = *raw_headers || other.raw_headers;
332 ContentSourceDataFilter::extend_options(query, &other.query);
333 ContentSourceDataFilter::extend_options(headers, &other.headers);
334 }
335
336 pub fn fulfills(&self, other: &ContentSourceDataVary) -> bool {
338 let ContentSourceDataVary {
340 method,
341 url,
342 original_url,
343 query,
344 raw_query,
345 headers,
346 raw_headers,
347 body,
348 cache_buster,
349 placeholder_for_future_extensions: _,
350 } = self;
351 if other.method && !method {
352 return false;
353 }
354 if other.url && !url {
355 return false;
356 }
357 if other.original_url && !original_url {
358 return false;
359 }
360 if other.body && !body {
361 return false;
362 }
363 if other.raw_query && !raw_query {
364 return false;
365 }
366 if other.raw_headers && !raw_headers {
367 return false;
368 }
369 if other.cache_buster && !cache_buster {
370 return false;
371 }
372 if !ContentSourceDataFilter::fulfills(query, &other.query) {
373 return false;
374 }
375 if !ContentSourceDataFilter::fulfills(headers, &other.headers) {
376 return false;
377 }
378 true
379 }
380}
381
382#[turbo_tasks::value_trait]
384pub trait ContentSource {
385 #[turbo_tasks::function]
386 fn get_routes(self: Vc<Self>) -> Vc<RouteTree>;
387
388 #[turbo_tasks::function]
390 fn get_children(self: Vc<Self>) -> Vc<ContentSources> {
391 ContentSources::empty()
392 }
393}
394
395pub trait ContentSourceExt {
396 fn issue_file_path(
397 self: Vc<Self>,
398 file_path: FileSystemPath,
399 description: RcStr,
400 ) -> Vc<Box<dyn ContentSource>>;
401}
402
403impl<T> ContentSourceExt for T
404where
405 T: Upcast<Box<dyn ContentSource>>,
406{
407 fn issue_file_path(
408 self: Vc<Self>,
409 file_path: FileSystemPath,
410 description: RcStr,
411 ) -> Vc<Box<dyn ContentSource>> {
412 Vc::upcast(IssueFilePathContentSource::new_file_path(
413 file_path,
414 description,
415 Vc::upcast_non_strict(self),
416 ))
417 }
418}
419
420#[turbo_tasks::value(transparent)]
421pub struct ContentSources(Vec<ResolvedVc<Box<dyn ContentSource>>>);
422
423#[turbo_tasks::value_impl]
424impl ContentSources {
425 #[turbo_tasks::function]
426 pub fn empty() -> Vc<Self> {
427 Vc::cell(Vec::new())
428 }
429}
430
431#[turbo_tasks::value]
434pub struct NoContentSource;
435
436#[turbo_tasks::value_impl]
437impl NoContentSource {
438 #[turbo_tasks::function]
439 pub fn new() -> Vc<Self> {
440 NoContentSource.cell()
441 }
442}
443#[turbo_tasks::value_impl]
444impl ContentSource for NoContentSource {
445 #[turbo_tasks::function]
446 fn get_routes(&self) -> Vc<RouteTree> {
447 RouteTree::empty()
448 }
449}
450
451#[derive(Debug, Clone, PartialEq, Eq, TraceRawVcs, NonLocalValue, Encode, Decode)]
452pub enum RewriteType {
453 Location {
454 path_and_query: RcStr,
457 },
458 ContentSource {
459 source: OperationVc<Box<dyn ContentSource>>,
462 path_and_query: RcStr,
465 },
466 Sources {
467 sources: OperationVc<GetContentSourceContents>,
470 },
471}
472
473#[turbo_tasks::value(shared)]
476#[derive(Debug)]
477pub struct Rewrite {
478 pub ty: RewriteType,
479
480 pub response_headers: Option<ResolvedVc<HeaderList>>,
483
484 pub request_headers: Option<ResolvedVc<HeaderList>>,
487}
488
489pub struct RewriteBuilder {
490 rewrite: Rewrite,
491}
492
493impl RewriteBuilder {
494 pub fn new(path_and_query: RcStr) -> Self {
495 Self {
496 rewrite: Rewrite {
497 ty: RewriteType::Location { path_and_query },
498 response_headers: None,
499 request_headers: None,
500 },
501 }
502 }
503
504 pub fn new_source_with_path_and_query(
505 source: OperationVc<Box<dyn ContentSource>>,
506 path_and_query: RcStr,
507 ) -> Self {
508 Self {
509 rewrite: Rewrite {
510 ty: RewriteType::ContentSource {
511 source,
512 path_and_query,
513 },
514 response_headers: None,
515 request_headers: None,
516 },
517 }
518 }
519
520 pub fn new_sources(sources: OperationVc<GetContentSourceContents>) -> Self {
521 Self {
522 rewrite: Rewrite {
523 ty: RewriteType::Sources { sources },
524 response_headers: None,
525 request_headers: None,
526 },
527 }
528 }
529
530 pub fn response_headers(mut self, headers: ResolvedVc<HeaderList>) -> Self {
533 self.rewrite.response_headers = Some(headers);
534 self
535 }
536
537 pub fn request_headers(mut self, headers: ResolvedVc<HeaderList>) -> Self {
540 self.rewrite.request_headers = Some(headers);
541 self
542 }
543
544 pub fn build(self) -> Vc<Rewrite> {
545 self.rewrite.cell()
546 }
547}