1pub mod asset_graph;
2pub mod combined;
3pub mod conditional;
4pub mod headers;
5pub mod issue_context;
6pub mod lazy_instantiated;
7pub mod query;
8pub mod request;
9pub(crate) mod resolve;
10pub mod route_tree;
11pub mod router;
12pub mod static_assets;
13pub mod wrapping_source;
14
15use std::collections::BTreeSet;
16
17use anyhow::Result;
18use futures::{TryStreamExt, stream::Stream as StreamTrait};
19use serde::{Deserialize, Serialize};
20use turbo_rcstr::RcStr;
21use turbo_tasks::{
22 Completion, NonLocalValue, OperationVc, ResolvedVc, Upcast, Value, ValueDefault, Vc,
23 trace::TraceRawVcs, util::SharedError,
24};
25use turbo_tasks_bytes::{Bytes, Stream, StreamRead};
26use turbo_tasks_fs::FileSystemPath;
27use turbo_tasks_hash::{DeterministicHash, DeterministicHasher, Xxh3Hash64Hasher};
28use turbopack_core::version::{Version, VersionedContent};
29
30use self::{
31 headers::Headers, issue_context::IssueFilePathContentSource, query::Query,
32 route_tree::RouteTree,
33};
34
35#[turbo_tasks::value(shared, operation)]
37pub struct ProxyResult {
38 pub status: u16,
40 pub headers: Vec<(RcStr, RcStr)>,
42 #[turbo_tasks(trace_ignore)]
44 pub body: Body,
45}
46
47#[turbo_tasks::value_impl]
48impl Version for ProxyResult {
49 #[turbo_tasks::function]
50 async fn id(&self) -> Result<Vc<RcStr>> {
51 let mut hash = Xxh3Hash64Hasher::new();
52 hash.write_u16(self.status);
53 for (name, value) in &self.headers {
54 name.deterministic_hash(&mut hash);
55 value.deterministic_hash(&mut hash);
56 }
57 let mut read = self.body.read();
58 while let Some(chunk) = read.try_next().await? {
59 hash.write_bytes(&chunk);
60 }
61 Ok(Vc::cell(hash.finish().to_string().into()))
62 }
63}
64
65#[turbo_tasks::value_trait]
67pub trait GetContentSourceContent {
68 fn vary(self: Vc<Self>) -> Vc<ContentSourceDataVary> {
71 ContentSourceDataVary::default().cell()
72 }
73
74 fn get(self: Vc<Self>, path: RcStr, data: Value<ContentSourceData>)
76 -> Vc<ContentSourceContent>;
77}
78
79#[turbo_tasks::value(transparent)]
80pub struct GetContentSourceContents(Vec<ResolvedVc<Box<dyn GetContentSourceContent>>>);
81
82#[turbo_tasks::value]
83pub struct StaticContent {
84 pub content: ResolvedVc<Box<dyn VersionedContent>>,
85 pub status_code: u16,
86 pub headers: ResolvedVc<HeaderList>,
87}
88
89#[turbo_tasks::value(shared)]
90pub enum ContentSourceContent {
93 NotFound,
94 Static(ResolvedVc<StaticContent>),
95 HttpProxy(OperationVc<ProxyResult>),
96 Rewrite(ResolvedVc<Rewrite>),
97 Next,
99}
100
101#[turbo_tasks::value_trait]
105pub trait ContentSourceSideEffect {
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(
113 self: Vc<Self>,
114 _path: RcStr,
115 _data: Value<ContentSourceData>,
116 ) -> Vc<ContentSourceContent> {
117 self
118 }
119}
120
121#[turbo_tasks::value_impl]
122impl ContentSourceContent {
123 #[turbo_tasks::function]
124 pub async fn static_content(
125 content: ResolvedVc<Box<dyn VersionedContent>>,
126 ) -> Result<Vc<ContentSourceContent>> {
127 Ok(ContentSourceContent::Static(
128 StaticContent {
129 content,
130 status_code: 200,
131 headers: HeaderList::empty().to_resolved().await?,
132 }
133 .resolved_cell(),
134 )
135 .cell())
136 }
137
138 #[turbo_tasks::function]
139 pub fn static_with_headers(
140 content: ResolvedVc<Box<dyn VersionedContent>>,
141 status_code: u16,
142 headers: ResolvedVc<HeaderList>,
143 ) -> Vc<ContentSourceContent> {
144 ContentSourceContent::Static(
145 StaticContent {
146 content,
147 status_code,
148 headers,
149 }
150 .resolved_cell(),
151 )
152 .cell()
153 }
154
155 #[turbo_tasks::function]
156 pub fn not_found() -> Vc<ContentSourceContent> {
157 ContentSourceContent::NotFound.cell()
158 }
159}
160
161#[turbo_tasks::value(transparent)]
163pub struct HeaderList(Vec<(RcStr, RcStr)>);
164
165#[turbo_tasks::value_impl]
166impl HeaderList {
167 #[turbo_tasks::function]
168 pub fn new(headers: Vec<(RcStr, RcStr)>) -> Vc<Self> {
169 HeaderList(headers).cell()
170 }
171
172 #[turbo_tasks::function]
173 pub fn empty() -> Vc<Self> {
174 HeaderList(vec![]).cell()
175 }
176}
177
178#[turbo_tasks::value(shared, serialization = "auto_for_input")]
185#[derive(Clone, Debug, Hash, Default)]
186pub struct ContentSourceData {
187 pub method: Option<RcStr>,
189 pub url: Option<RcStr>,
191 pub original_url: Option<RcStr>,
194 pub query: Option<Query>,
196 pub raw_query: Option<RcStr>,
198 pub headers: Option<Headers>,
201 pub raw_headers: Option<Vec<(RcStr, RcStr)>>,
204 pub body: Option<ResolvedVc<Body>>,
206 pub cache_buster: u64,
208}
209
210pub type BodyChunk = Result<Bytes, SharedError>;
211
212#[turbo_tasks::value(shared)]
214#[derive(Default, Clone, Debug)]
215pub struct Body {
216 #[turbo_tasks(trace_ignore)]
217 chunks: Stream<BodyChunk>,
218}
219
220impl Body {
221 pub fn new(chunks: Vec<BodyChunk>) -> Self {
223 Self {
224 chunks: Stream::new_closed(chunks),
225 }
226 }
227
228 pub fn read(&self) -> StreamRead<BodyChunk> {
230 self.chunks.read()
231 }
232
233 pub fn from_stream<T: StreamTrait<Item = BodyChunk> + Send + Unpin + 'static>(
234 source: T,
235 ) -> Self {
236 Self {
237 chunks: Stream::from(source),
238 }
239 }
240}
241
242impl<T: Into<Bytes>> From<T> for Body {
243 fn from(value: T) -> Self {
244 Body::new(vec![Ok(value.into())])
245 }
246}
247
248impl ValueDefault for Body {
249 fn value_default() -> Vc<Self> {
250 Body::default().cell()
251 }
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, TraceRawVcs, Hash, Serialize, Deserialize, NonLocalValue)]
256pub enum ContentSourceDataFilter {
257 All,
258 Subset(BTreeSet<String>),
259}
260
261impl ContentSourceDataFilter {
262 pub fn extend(&mut self, other: &ContentSourceDataFilter) {
264 match self {
265 ContentSourceDataFilter::All => {}
266 ContentSourceDataFilter::Subset(set) => match other {
267 ContentSourceDataFilter::All => *self = ContentSourceDataFilter::All,
268 ContentSourceDataFilter::Subset(set2) => set.extend(set2.iter().cloned()),
269 },
270 }
271 }
272
273 pub fn extend_options(
276 this: &mut Option<ContentSourceDataFilter>,
277 other: &Option<ContentSourceDataFilter>,
278 ) {
279 if let Some(this) = this.as_mut() {
280 if let Some(other) = other.as_ref() {
281 this.extend(other);
282 }
283 } else {
284 this.clone_from(other);
285 }
286 }
287
288 pub fn contains(&self, key: &str) -> bool {
290 match self {
291 ContentSourceDataFilter::All => true,
292 ContentSourceDataFilter::Subset(set) => set.contains(key),
293 }
294 }
295
296 pub fn fulfills(
299 this: &Option<ContentSourceDataFilter>,
300 other: &Option<ContentSourceDataFilter>,
301 ) -> bool {
302 match (this, other) {
303 (_, None) => true,
304 (None, Some(_)) => false,
305 (Some(this), Some(other)) => match (this, other) {
306 (ContentSourceDataFilter::All, _) => true,
307 (_, ContentSourceDataFilter::All) => false,
308 (ContentSourceDataFilter::Subset(this), ContentSourceDataFilter::Subset(other)) => {
309 this.is_superset(other)
310 }
311 },
312 }
313 }
314}
315
316#[turbo_tasks::value(shared, serialization = "auto_for_input")]
320#[derive(Debug, Default, Clone, Hash)]
321pub struct ContentSourceDataVary {
322 pub method: bool,
323 pub url: bool,
324 pub original_url: bool,
325 pub query: Option<ContentSourceDataFilter>,
326 pub raw_query: bool,
327 pub headers: Option<ContentSourceDataFilter>,
328 pub raw_headers: bool,
329 pub body: bool,
330 pub cache_buster: bool,
334 pub placeholder_for_future_extensions: (),
335}
336
337impl ContentSourceDataVary {
338 pub fn extend(&mut self, other: &ContentSourceDataVary) {
341 let ContentSourceDataVary {
342 method,
343 url,
344 original_url,
345 query,
346 raw_query,
347 headers,
348 raw_headers,
349 body,
350 cache_buster,
351 placeholder_for_future_extensions: _,
352 } = self;
353 *method = *method || other.method;
354 *url = *url || other.url;
355 *original_url = *original_url || other.original_url;
356 *body = *body || other.body;
357 *cache_buster = *cache_buster || other.cache_buster;
358 *raw_query = *raw_query || other.raw_query;
359 *raw_headers = *raw_headers || other.raw_headers;
360 ContentSourceDataFilter::extend_options(query, &other.query);
361 ContentSourceDataFilter::extend_options(headers, &other.headers);
362 }
363
364 pub fn fulfills(&self, other: &ContentSourceDataVary) -> bool {
367 let ContentSourceDataVary {
369 method,
370 url,
371 original_url,
372 query,
373 raw_query,
374 headers,
375 raw_headers,
376 body,
377 cache_buster,
378 placeholder_for_future_extensions: _,
379 } = self;
380 if other.method && !method {
381 return false;
382 }
383 if other.url && !url {
384 return false;
385 }
386 if other.original_url && !original_url {
387 return false;
388 }
389 if other.body && !body {
390 return false;
391 }
392 if other.raw_query && !raw_query {
393 return false;
394 }
395 if other.raw_headers && !raw_headers {
396 return false;
397 }
398 if other.cache_buster && !cache_buster {
399 return false;
400 }
401 if !ContentSourceDataFilter::fulfills(query, &other.query) {
402 return false;
403 }
404 if !ContentSourceDataFilter::fulfills(headers, &other.headers) {
405 return false;
406 }
407 true
408 }
409}
410
411#[turbo_tasks::value_trait]
413pub trait ContentSource {
414 fn get_routes(self: Vc<Self>) -> Vc<RouteTree>;
415
416 fn get_children(self: Vc<Self>) -> Vc<ContentSources> {
418 ContentSources::empty()
419 }
420}
421
422pub trait ContentSourceExt {
423 fn issue_file_path(
424 self: Vc<Self>,
425 file_path: Vc<FileSystemPath>,
426 description: RcStr,
427 ) -> Vc<Box<dyn ContentSource>>;
428}
429
430impl<T> ContentSourceExt for T
431where
432 T: Upcast<Box<dyn ContentSource>>,
433{
434 fn issue_file_path(
435 self: Vc<Self>,
436 file_path: Vc<FileSystemPath>,
437 description: RcStr,
438 ) -> Vc<Box<dyn ContentSource>> {
439 Vc::upcast(IssueFilePathContentSource::new_file_path(
440 file_path,
441 description,
442 Vc::upcast(self),
443 ))
444 }
445}
446
447#[turbo_tasks::value(transparent)]
448pub struct ContentSources(Vec<ResolvedVc<Box<dyn ContentSource>>>);
449
450#[turbo_tasks::value_impl]
451impl ContentSources {
452 #[turbo_tasks::function]
453 pub fn empty() -> Vc<Self> {
454 Vc::cell(Vec::new())
455 }
456}
457
458#[turbo_tasks::value]
461pub struct NoContentSource;
462
463#[turbo_tasks::value_impl]
464impl NoContentSource {
465 #[turbo_tasks::function]
466 pub fn new() -> Vc<Self> {
467 NoContentSource.cell()
468 }
469}
470#[turbo_tasks::value_impl]
471impl ContentSource for NoContentSource {
472 #[turbo_tasks::function]
473 fn get_routes(&self) -> Vc<RouteTree> {
474 RouteTree::empty()
475 }
476}
477
478#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
479pub enum RewriteType {
480 Location {
481 path_and_query: RcStr,
484 },
485 ContentSource {
486 source: OperationVc<Box<dyn ContentSource>>,
489 path_and_query: RcStr,
492 },
493 Sources {
494 sources: OperationVc<GetContentSourceContents>,
497 },
498}
499
500#[turbo_tasks::value(shared)]
503#[derive(Debug)]
504pub struct Rewrite {
505 pub ty: RewriteType,
506
507 pub response_headers: Option<ResolvedVc<HeaderList>>,
510
511 pub request_headers: Option<ResolvedVc<HeaderList>>,
514}
515
516pub struct RewriteBuilder {
517 rewrite: Rewrite,
518}
519
520impl RewriteBuilder {
521 pub fn new(path_and_query: RcStr) -> Self {
522 Self {
523 rewrite: Rewrite {
524 ty: RewriteType::Location { path_and_query },
525 response_headers: None,
526 request_headers: None,
527 },
528 }
529 }
530
531 pub fn new_source_with_path_and_query(
532 source: OperationVc<Box<dyn ContentSource>>,
533 path_and_query: RcStr,
534 ) -> Self {
535 Self {
536 rewrite: Rewrite {
537 ty: RewriteType::ContentSource {
538 source,
539 path_and_query,
540 },
541 response_headers: None,
542 request_headers: None,
543 },
544 }
545 }
546
547 pub fn new_sources(sources: OperationVc<GetContentSourceContents>) -> Self {
548 Self {
549 rewrite: Rewrite {
550 ty: RewriteType::Sources { sources },
551 response_headers: None,
552 request_headers: None,
553 },
554 }
555 }
556
557 pub fn response_headers(mut self, headers: ResolvedVc<HeaderList>) -> Self {
560 self.rewrite.response_headers = Some(headers);
561 self
562 }
563
564 pub fn request_headers(mut self, headers: ResolvedVc<HeaderList>) -> Self {
567 self.rewrite.request_headers = Some(headers);
568 self
569 }
570
571 pub fn build(self) -> Vc<Rewrite> {
572 self.rewrite.cell()
573 }
574}