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