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, TaskInput, Upcast, ValueDefault, Vc,
22 trace::TraceRawVcs, 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#[derive(
162 PartialEq,
163 Eq,
164 NonLocalValue,
165 TraceRawVcs,
166 Clone,
167 Debug,
168 Hash,
169 Default,
170 TaskInput,
171 Encode,
172 Decode,
173)]
174pub struct ContentSourceData {
175 pub method: Option<RcStr>,
177 pub url: Option<RcStr>,
179 pub original_url: Option<RcStr>,
182 pub query: Option<Query>,
184 pub raw_query: Option<RcStr>,
186 pub headers: Option<Headers>,
189 pub raw_headers: Option<Vec<(RcStr, RcStr)>>,
192 pub body: Option<ResolvedVc<Body>>,
194 pub cache_buster: u64,
196}
197
198pub type BodyChunk = Result<Bytes, SharedError>;
199
200#[turbo_tasks::value(shared)]
202#[derive(Default, Clone, Debug)]
203pub struct Body {
204 #[turbo_tasks(trace_ignore)]
205 chunks: Stream<BodyChunk>,
206}
207
208impl Body {
209 pub fn new(chunks: Vec<BodyChunk>) -> Self {
211 Self {
212 chunks: Stream::new_closed(chunks),
213 }
214 }
215
216 pub fn read(&self) -> StreamRead<BodyChunk> {
218 self.chunks.read()
219 }
220
221 pub fn from_stream<T: StreamTrait<Item = BodyChunk> + Send + Unpin + 'static>(
222 source: T,
223 ) -> Self {
224 Self {
225 chunks: Stream::from(source),
226 }
227 }
228}
229
230impl<T: Into<Bytes>> From<T> for Body {
231 fn from(value: T) -> Self {
232 Body::new(vec![Ok(value.into())])
233 }
234}
235
236impl ValueDefault for Body {
237 fn value_default() -> Vc<Self> {
238 Body::default().cell()
239 }
240}
241
242#[derive(Debug, Clone, PartialEq, Eq, TraceRawVcs, Hash, NonLocalValue, Encode, Decode)]
244pub enum ContentSourceDataFilter {
245 All,
246 Subset(BTreeSet<String>),
247}
248
249impl ContentSourceDataFilter {
250 pub fn extend(&mut self, other: &ContentSourceDataFilter) {
252 match self {
253 ContentSourceDataFilter::All => {}
254 ContentSourceDataFilter::Subset(set) => match other {
255 ContentSourceDataFilter::All => *self = ContentSourceDataFilter::All,
256 ContentSourceDataFilter::Subset(set2) => set.extend(set2.iter().cloned()),
257 },
258 }
259 }
260
261 pub fn extend_options(
265 this: &mut Option<ContentSourceDataFilter>,
266 other: &Option<ContentSourceDataFilter>,
267 ) {
268 if let Some(this) = this.as_mut() {
269 if let Some(other) = other.as_ref() {
270 this.extend(other);
271 }
272 } else {
273 this.clone_from(other);
274 }
275 }
276
277 pub fn contains(&self, key: &str) -> bool {
279 match self {
280 ContentSourceDataFilter::All => true,
281 ContentSourceDataFilter::Subset(set) => set.contains(key),
282 }
283 }
284
285 pub fn fulfills(
288 this: &Option<ContentSourceDataFilter>,
289 other: &Option<ContentSourceDataFilter>,
290 ) -> bool {
291 match (this, other) {
292 (_, None) => true,
293 (None, Some(_)) => false,
294 (Some(this), Some(other)) => match (this, other) {
295 (ContentSourceDataFilter::All, _) => true,
296 (_, ContentSourceDataFilter::All) => false,
297 (ContentSourceDataFilter::Subset(this), ContentSourceDataFilter::Subset(other)) => {
298 this.is_superset(other)
299 }
300 },
301 }
302 }
303}
304
305#[turbo_tasks::value(shared)]
309#[derive(Debug, Default, Clone, Hash)]
310pub struct ContentSourceDataVary {
311 pub method: bool,
312 pub url: bool,
313 pub original_url: bool,
314 pub query: Option<ContentSourceDataFilter>,
315 pub raw_query: bool,
316 pub headers: Option<ContentSourceDataFilter>,
317 pub raw_headers: bool,
318 pub body: bool,
319 pub cache_buster: bool,
323 pub placeholder_for_future_extensions: (),
324}
325
326impl ContentSourceDataVary {
327 pub fn extend(&mut self, other: &ContentSourceDataVary) {
330 let ContentSourceDataVary {
331 method,
332 url,
333 original_url,
334 query,
335 raw_query,
336 headers,
337 raw_headers,
338 body,
339 cache_buster,
340 placeholder_for_future_extensions: _,
341 } = self;
342 *method = *method || other.method;
343 *url = *url || other.url;
344 *original_url = *original_url || other.original_url;
345 *body = *body || other.body;
346 *cache_buster = *cache_buster || other.cache_buster;
347 *raw_query = *raw_query || other.raw_query;
348 *raw_headers = *raw_headers || other.raw_headers;
349 ContentSourceDataFilter::extend_options(query, &other.query);
350 ContentSourceDataFilter::extend_options(headers, &other.headers);
351 }
352
353 pub fn fulfills(&self, other: &ContentSourceDataVary) -> bool {
355 let ContentSourceDataVary {
357 method,
358 url,
359 original_url,
360 query,
361 raw_query,
362 headers,
363 raw_headers,
364 body,
365 cache_buster,
366 placeholder_for_future_extensions: _,
367 } = self;
368 if other.method && !method {
369 return false;
370 }
371 if other.url && !url {
372 return false;
373 }
374 if other.original_url && !original_url {
375 return false;
376 }
377 if other.body && !body {
378 return false;
379 }
380 if other.raw_query && !raw_query {
381 return false;
382 }
383 if other.raw_headers && !raw_headers {
384 return false;
385 }
386 if other.cache_buster && !cache_buster {
387 return false;
388 }
389 if !ContentSourceDataFilter::fulfills(query, &other.query) {
390 return false;
391 }
392 if !ContentSourceDataFilter::fulfills(headers, &other.headers) {
393 return false;
394 }
395 true
396 }
397}
398
399#[turbo_tasks::value_trait]
401pub trait ContentSource {
402 #[turbo_tasks::function]
403 fn get_routes(self: Vc<Self>) -> Vc<RouteTree>;
404
405 #[turbo_tasks::function]
407 fn get_children(self: Vc<Self>) -> Vc<ContentSources> {
408 ContentSources::empty()
409 }
410}
411
412pub trait ContentSourceExt {
413 fn issue_file_path(
414 self: Vc<Self>,
415 file_path: FileSystemPath,
416 description: RcStr,
417 ) -> Vc<Box<dyn ContentSource>>;
418}
419
420impl<T> ContentSourceExt for T
421where
422 T: Upcast<Box<dyn ContentSource>>,
423{
424 fn issue_file_path(
425 self: Vc<Self>,
426 file_path: FileSystemPath,
427 description: RcStr,
428 ) -> Vc<Box<dyn ContentSource>> {
429 Vc::upcast(IssueFilePathContentSource::new_file_path(
430 file_path,
431 description,
432 Vc::upcast_non_strict(self),
433 ))
434 }
435}
436
437#[turbo_tasks::value(transparent)]
438pub struct ContentSources(Vec<ResolvedVc<Box<dyn ContentSource>>>);
439
440#[turbo_tasks::value_impl]
441impl ContentSources {
442 #[turbo_tasks::function]
443 pub fn empty() -> Vc<Self> {
444 Vc::cell(Vec::new())
445 }
446}
447
448#[turbo_tasks::value]
451pub struct NoContentSource;
452
453#[turbo_tasks::value_impl]
454impl NoContentSource {
455 #[turbo_tasks::function]
456 pub fn new() -> Vc<Self> {
457 NoContentSource.cell()
458 }
459}
460#[turbo_tasks::value_impl]
461impl ContentSource for NoContentSource {
462 #[turbo_tasks::function]
463 fn get_routes(&self) -> Vc<RouteTree> {
464 RouteTree::empty()
465 }
466}
467
468#[derive(Debug, Clone, PartialEq, Eq, TraceRawVcs, NonLocalValue, Encode, Decode)]
469pub enum RewriteType {
470 Location {
471 path_and_query: RcStr,
474 },
475 ContentSource {
476 source: OperationVc<Box<dyn ContentSource>>,
479 path_and_query: RcStr,
482 },
483 Sources {
484 sources: OperationVc<GetContentSourceContents>,
487 },
488}
489
490#[turbo_tasks::value(shared)]
493#[derive(Debug)]
494pub struct Rewrite {
495 pub ty: RewriteType,
496
497 pub response_headers: Option<ResolvedVc<HeaderList>>,
500
501 pub request_headers: Option<ResolvedVc<HeaderList>>,
504}
505
506pub struct RewriteBuilder {
507 rewrite: Rewrite,
508}
509
510impl RewriteBuilder {
511 pub fn new(path_and_query: RcStr) -> Self {
512 Self {
513 rewrite: Rewrite {
514 ty: RewriteType::Location { path_and_query },
515 response_headers: None,
516 request_headers: None,
517 },
518 }
519 }
520
521 pub fn new_source_with_path_and_query(
522 source: OperationVc<Box<dyn ContentSource>>,
523 path_and_query: RcStr,
524 ) -> Self {
525 Self {
526 rewrite: Rewrite {
527 ty: RewriteType::ContentSource {
528 source,
529 path_and_query,
530 },
531 response_headers: None,
532 request_headers: None,
533 },
534 }
535 }
536
537 pub fn new_sources(sources: OperationVc<GetContentSourceContents>) -> Self {
538 Self {
539 rewrite: Rewrite {
540 ty: RewriteType::Sources { sources },
541 response_headers: None,
542 request_headers: None,
543 },
544 }
545 }
546
547 pub fn response_headers(mut self, headers: ResolvedVc<HeaderList>) -> Self {
550 self.rewrite.response_headers = Some(headers);
551 self
552 }
553
554 pub fn request_headers(mut self, headers: ResolvedVc<HeaderList>) -> Self {
557 self.rewrite.request_headers = Some(headers);
558 self
559 }
560
561 pub fn build(self) -> Vc<Rewrite> {
562 self.rewrite.cell()
563 }
564}