1use std::borrow::Cow;
2use std::future::Future;
3#[cfg(test)]
4use std::sync::Arc;
5
6use http::Method;
7#[cfg(test)]
8use serde::Serialize;
9use serde_json::Value;
10#[cfg(test)]
11use vorma_tasks::Result as TaskResult;
12
13#[cfg(test)]
14use crate::api;
15#[cfg(test)]
16use crate::mux::RouteExecutionError;
17use crate::mux::{self, NestedRouter};
18#[cfg(test)]
19use crate::mux::{InputParser, None};
20use crate::searchparams;
21use crate::tsgen::{Type, TypePhase, TypeRef, TypeRegistry};
22
23mod context;
24mod contract;
25#[cfg(test)]
26use context::accepts_client_redirect;
27pub use context::{HeadHandle, MiddlewareCtx, Params, ResourceCtx, ResponseHandle, ViewCtx};
28use contract::RouteCollector;
29pub use contract::{Contract, ResourceEntry, ViewEntry, contract_for};
30#[cfg(test)]
31use pattern::default_resource_kind_for_method;
32use pattern::validate_declared_route_pattern;
33pub use pattern::{
34 default_resource_kind, params_for_pattern, pattern_is_splat, view_parents_for_patterns,
35};
36mod pattern;
37pub(crate) use runtime::{RuntimeRoutes, runtime_routes_for};
38mod runtime;
39#[cfg(test)]
40use runner::serialize_route_output;
41pub use runner::{
42 ErasedRequestCtx, ErasedRouteFuture, ErasedRouteHandler, PathParams, RouteFuture,
43 run_static_resource, run_static_view,
44};
45use runner::{RouteRunner, run_route_runner};
46mod runner;
47
48#[doc(hidden)]
49pub type TypeResolver = fn(TypePhase, &mut TypeRegistry) -> Result<TypeRef, String>;
50#[doc(hidden)]
51pub type SearchSchemaResolver = fn() -> Result<Value, String>;
52#[cfg(test)]
53type ResourceHandler<S, E, I, P, O> =
54 dyn Fn(ResourceCtx<S, E, I, P>) -> RouteFuture<O, E> + Send + Sync;
55#[cfg(test)]
56type ViewHandler<S, E, I, P, O> = dyn Fn(ViewCtx<S, E, I, P>) -> RouteFuture<O, E> + Send + Sync;
57
58#[doc(hidden)]
59pub fn type_resolver<T>(phase: TypePhase, registry: &mut TypeRegistry) -> Result<TypeRef, String>
60where
61 T: Type,
62{
63 T::collect_type_defs_for(phase, registry).map_err(|err| err.to_string())?;
64 Ok(T::type_ref_for(phase))
65}
66
67#[doc(hidden)]
68pub fn search_schema_resolver<T>() -> Result<Value, String>
69where
70 T: Type,
71{
72 searchparams::schema_for_type::<T>().map_err(|err| err.to_string())
73}
74
75pub struct Middleware<S, E = Box<dyn std::error::Error + Send + Sync>> {
77 mw: mux::Middleware<S, E>,
78}
79
80impl<S, E> Middleware<S, E>
81where
82 S: Send + Sync + 'static,
83 E: Send + Sync + 'static,
84{
85 pub fn new<F, Fut, O>(handler: F) -> Self
87 where
88 F: Fn(MiddlewareCtx<S, E>) -> Fut + Send + Sync + 'static,
89 Fut: Future<Output = vorma_tasks::Result<O, E>> + Send + 'static,
90 O: Send + Sync + 'static,
91 {
92 Self {
93 mw: mux::Middleware::new(move |ctx| {
94 let future = handler(MiddlewareCtx::new(ctx));
95 async move { future.await.map(|_| ()) }
96 }),
97 }
98 }
99
100 fn register_resource(&self, r: &mut mux::Router<S, E>) -> Result<(), mux::Error> {
101 r.use_middleware_entry(&self.mw);
102 Ok(())
103 }
104
105 fn register_view(&self, r: &mut NestedRouter<S, E>) -> Result<(), mux::Error> {
106 r.use_middleware_entry(&self.mw);
107 Ok(())
108 }
109
110 fn register_runtime(&self, routes: &mut RuntimeRoutes<S, E>) -> Result<(), mux::Error> {
111 self.register_resource(&mut routes.resources)?;
112 self.register_view(&mut routes.views)?;
113 Ok(())
114 }
115}
116
117#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub enum ResourceKind {
120 Query,
122 Mutation,
124}
125
126impl ResourceKind {
127 pub fn as_str(self) -> &'static str {
129 match self {
130 Self::Query => "query",
131 Self::Mutation => "mutation",
132 }
133 }
134}
135
136pub struct View<S, E = Box<dyn std::error::Error + Send + Sync>> {
138 pattern: Cow<'static, str>,
139 handler: RouteRunner<S, E>,
140 client_file: Cow<'static, str>,
141 input_type: TypeResolver,
142 output_type: TypeResolver,
143 search_schema: SearchSchemaResolver,
144}
145
146impl<S, E> View<S, E>
147where
148 S: Send + Sync + 'static,
149 E: Send + Sync + 'static,
150{
151 #[doc(hidden)]
152 pub const fn from_static(
153 pattern: &'static str,
154 client_file: &'static str,
155 input_type: TypeResolver,
156 output_type: TypeResolver,
157 search_schema: SearchSchemaResolver,
158 handler: ErasedRouteHandler<S, E>,
159 ) -> Self {
160 Self {
161 pattern: Cow::Borrowed(pattern),
162 handler: RouteRunner::Static(handler),
163 client_file: Cow::Borrowed(client_file),
164 input_type,
165 output_type,
166 search_schema,
167 }
168 }
169
170 #[cfg(test)]
171 pub(crate) fn new<I, P, O, F, Fut>(
172 pattern: impl Into<String>,
173 client_file: impl Into<String>,
174 input_parser: InputParser<I>,
175 handler: F,
176 ) -> Self
177 where
178 F: Fn(ViewCtx<S, E, I, P>) -> Fut + Send + Sync + 'static,
179 Fut: Future<Output = TaskResult<O, E>> + Send + 'static,
180 I: Type + api::ViewInput,
181 P: PathParams,
182 O: Type + Serialize + Send + Sync + 'static,
183 {
184 let handler: Arc<ViewHandler<S, E, I, P, O>> = Arc::new(move |ctx| Box::pin(handler(ctx)));
185 Self {
186 pattern: Cow::Owned(pattern.into()),
187 handler: RouteRunner::Dynamic(Arc::new(move |ctx| {
188 let input_parser = input_parser.clone();
189 let handler = handler.clone();
190 Box::pin(async move {
191 let input = input_parser
192 .parse(ctx.request())
193 .await
194 .map_err(RouteExecutionError::Input)?;
195 let params = P::from_raw_path_params(ctx.params())
196 .map_err(RouteExecutionError::Input)?;
197 let output = handler(ViewCtx::new(ctx.with_input(input), params))
198 .await
199 .map_err(RouteExecutionError::Task)?;
200 serialize_route_output(output)
201 })
202 })),
203 client_file: Cow::Owned(client_file.into()),
204 input_type: type_resolver::<I>,
205 output_type: type_resolver::<O>,
206 search_schema: search_schema_resolver::<I>,
207 }
208 }
209
210 pub fn pattern(&self) -> &str {
212 &self.pattern
213 }
214
215 pub fn client_file(&self) -> &str {
217 &self.client_file
218 }
219}
220
221#[derive(Default)]
223pub struct Middlewares<S, E = Box<dyn std::error::Error + Send + Sync>> {
224 middlewares: Vec<Middleware<S, E>>,
225}
226
227impl<S, E> Middlewares<S, E>
228where
229 S: Send + Sync + 'static,
230 E: Send + Sync + 'static,
231{
232 pub fn new() -> Self {
234 Self {
235 middlewares: Vec::new(),
236 }
237 }
238
239 pub fn push(&mut self, middleware: Middleware<S, E>) -> &mut Self {
241 self.middlewares.push(middleware);
242 self
243 }
244
245 fn register_runtime(&self, routes: &mut RuntimeRoutes<S, E>) -> Result<(), mux::Error> {
246 for middleware in &self.middlewares {
247 middleware.register_runtime(routes)?;
248 }
249 Ok(())
250 }
251}
252
253impl<S, E> View<S, E>
254where
255 S: Send + Sync + 'static,
256 E: Send + Sync + 'static,
257{
258 fn register_contract(&self, collector: &mut RouteCollector) -> Result<(), String> {
259 let input = (self.input_type)(TypePhase::Deserialize, &mut collector.types)?;
260 let output = (self.output_type)(TypePhase::Serialize, &mut collector.types)?;
261 collector.views.push(ViewEntry {
262 pattern: self.pattern.to_string(),
263 client_file: self.client_file.to_string(),
264 input,
265 output,
266 search_schema: (self.search_schema)()
267 .map_err(|err| format!("view {} input: {err}", self.pattern))?,
268 });
269 Ok(())
270 }
271
272 fn register_to_mux(&self, r: &mut NestedRouter<S, E>) -> Result<(), mux::Error> {
273 validate_declared_route_pattern("view", &self.pattern)
274 .map_err(mux::Error::InvalidPattern)?;
275 let handler = self.handler.clone();
276 r.add_handler_entry(
277 self.pattern.to_string(),
278 mux::erased_handler(move |ctx| {
279 let handler = handler.clone();
280 async move { run_route_runner(handler, ctx).await }
281 }),
282 )
283 }
284}
285
286pub struct Resource<S, E = Box<dyn std::error::Error + Send + Sync>> {
288 method: Method,
289 pattern: Cow<'static, str>,
290 kind: Option<ResourceKind>,
291 handler: RouteRunner<S, E>,
292 input_type: TypeResolver,
293 output_type: TypeResolver,
294}
295
296impl<S, E> Resource<S, E>
297where
298 S: Send + Sync + 'static,
299 E: Send + Sync + 'static,
300{
301 #[doc(hidden)]
302 pub const fn from_static(
303 method: Method,
304 pattern: &'static str,
305 kind: Option<ResourceKind>,
306 input_type: TypeResolver,
307 output_type: TypeResolver,
308 handler: ErasedRouteHandler<S, E>,
309 ) -> Self {
310 Self {
311 method,
312 pattern: Cow::Borrowed(pattern),
313 kind,
314 handler: RouteRunner::Static(handler),
315 input_type,
316 output_type,
317 }
318 }
319
320 #[cfg(test)]
321 pub(crate) fn new<I, P, O, F, Fut>(
322 method: Method,
323 pattern: impl Into<String>,
324 kind: Option<ResourceKind>,
325 input_parser: InputParser<I>,
326 handler: F,
327 ) -> Self
328 where
329 F: Fn(ResourceCtx<S, E, I, P>) -> Fut + Send + Sync + 'static,
330 Fut: Future<Output = TaskResult<O, E>> + Send + 'static,
331 I: Type + api::ResourceInput,
332 P: PathParams,
333 O: Type + Serialize + Send + Sync + 'static,
334 {
335 let handler: Arc<ResourceHandler<S, E, I, P, O>> =
336 Arc::new(move |ctx| Box::pin(handler(ctx)));
337 Self {
338 method,
339 pattern: Cow::Owned(pattern.into()),
340 kind,
341 handler: RouteRunner::Dynamic(Arc::new(move |ctx| {
342 let input_parser = input_parser.clone();
343 let handler = handler.clone();
344 Box::pin(async move {
345 let input = input_parser
346 .parse(ctx.request())
347 .await
348 .map_err(RouteExecutionError::Input)?;
349 let params = P::from_raw_path_params(ctx.params())
350 .map_err(RouteExecutionError::Input)?;
351 let output = handler(ResourceCtx::new(ctx.with_input(input), params))
352 .await
353 .map_err(RouteExecutionError::Task)?;
354 serialize_route_output(output)
355 })
356 })),
357 input_type: type_resolver::<I>,
358 output_type: type_resolver::<O>,
359 }
360 }
361
362 pub fn method(&self) -> &Method {
364 &self.method
365 }
366
367 pub fn pattern(&self) -> &str {
369 &self.pattern
370 }
371
372 pub fn kind(&self) -> Option<ResourceKind> {
374 self.kind
375 }
376}
377
378impl<S, E> Resource<S, E>
379where
380 S: Send + Sync + 'static,
381 E: Send + Sync + 'static,
382{
383 #[cfg(test)]
384 pub(crate) fn without_handler(
385 method: Method,
386 pattern: impl Into<String>,
387 kind: Option<ResourceKind>,
388 ) -> Self {
389 Self::new(
390 method,
391 pattern,
392 kind,
393 InputParser::default_input(),
394 |_: ResourceCtx<S, E, (), ()>| async { Ok(None) },
395 )
396 }
397}
398
399impl<S, E> Resource<S, E>
400where
401 S: Send + Sync + 'static,
402 E: Send + Sync + 'static,
403{
404 fn register_contract(&self, collector: &mut RouteCollector) -> Result<(), String> {
405 let input = (self.input_type)(TypePhase::Deserialize, &mut collector.types)?;
406 let output = (self.output_type)(TypePhase::Serialize, &mut collector.types)?;
407 collector.resources.push(ResourceEntry {
408 method: self.method.as_str().to_owned(),
409 pattern: self.pattern.to_string(),
410 kind: self.kind,
411 input,
412 output,
413 });
414 Ok(())
415 }
416
417 fn register_to_mux(&self, r: &mut mux::Router<S, E>) -> Result<(), mux::Error> {
418 validate_declared_route_pattern("resource", &self.pattern)
419 .map_err(mux::Error::InvalidPattern)?;
420 let handler = self.handler.clone();
421 r.add_handler_entry(
422 self.method.clone(),
423 self.pattern.to_string(),
424 mux::erased_handler(move |ctx| {
425 let handler = handler.clone();
426 async move { run_route_runner(handler, ctx).await }
427 }),
428 )?;
429 Ok(())
430 }
431}
432
433#[derive(Default)]
435pub struct Views<S, E = Box<dyn std::error::Error + Send + Sync>> {
436 views: Vec<View<S, E>>,
437}
438
439impl<S, E> Views<S, E>
440where
441 S: Send + Sync + 'static,
442 E: Send + Sync + 'static,
443{
444 pub fn new() -> Self {
446 Self { views: Vec::new() }
447 }
448
449 pub fn push(&mut self, view: View<S, E>) -> &mut Self {
451 self.views.push(view);
452 self
453 }
454
455 fn register_contract(&self, collector: &mut RouteCollector) -> Result<(), String> {
456 for view in &self.views {
457 view.register_contract(collector)?;
458 }
459 Ok(())
460 }
461
462 fn register_runtime(&self, routes: &mut RuntimeRoutes<S, E>) -> Result<(), mux::Error> {
463 for view in &self.views {
464 view.register_to_mux(&mut routes.views)?;
465 }
466 Ok(())
467 }
468}
469
470#[derive(Default)]
472pub struct Resources<S, E = Box<dyn std::error::Error + Send + Sync>> {
473 resources: Vec<Resource<S, E>>,
474}
475
476impl<S, E> Resources<S, E>
477where
478 S: Send + Sync + 'static,
479 E: Send + Sync + 'static,
480{
481 pub fn new() -> Self {
483 Self {
484 resources: Vec::new(),
485 }
486 }
487
488 pub fn push(&mut self, resource: Resource<S, E>) -> &mut Self {
490 self.resources.push(resource);
491 self
492 }
493
494 fn register_contract(&self, collector: &mut RouteCollector) -> Result<(), String> {
495 for resource in &self.resources {
496 resource.register_contract(collector)?;
497 }
498 Ok(())
499 }
500
501 fn register_runtime(&self, routes: &mut RuntimeRoutes<S, E>) -> Result<(), mux::Error> {
502 for resource in &self.resources {
503 resource.register_to_mux(&mut routes.resources)?;
504 }
505 Ok(())
506 }
507}
508
509#[cfg(test)]
510#[path = "core_tests.rs"]
511mod core_tests;