1#![forbid(unsafe_code)]
2
3use base64::Engine;
4use blazingly_core::{
5 Accepted, ApiError, ApiModel, ApiSchema, App, AppDefinition, Background, BackgroundTask,
6 BodyStreamError, Cookie, Created, File, Form, Header, HttpMethod, HttpUpgrade, InputDescriptor,
7 InputSource, Json, MAX_MULTIPART_HEADER_BYTES, MAX_MULTIPART_PARTS, Multipart, MultipartError,
8 MultipartStream, NoContent, OperationDescriptor, OperationFailure, OperationId, Path,
9 PreparedJson, Query, ResponseBuildError, ResponseHeader, SchemaKind, SecuritySchemeDescriptor,
10 Status, StreamingBody, TypeDescriptor, UploadFile, UploadSlots, WithHeaders, find_bytes,
11 multipart_boundary, multipart_part_headers,
12};
13pub use blazingly_di::DependencyError;
14use blazingly_di::{
15 CompiledProvider, DependencyLifetime, DependencyRequest, DependencySlot, DependencyValue,
16 Depends, Provider,
17};
18use blazingly_json::{Value, json};
19use serde::Serialize;
20use serde::de::DeserializeOwned;
21use std::borrow::Cow;
22use std::cell::Cell;
23use std::collections::{BTreeMap, HashMap, HashSet};
24use std::fmt;
25use std::future::Future;
26use std::num::NonZeroUsize;
27use std::panic::{AssertUnwindSafe, catch_unwind};
28use std::pin::Pin;
29use std::rc::Rc;
30use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
31use std::sync::mpsc::{self, Receiver, SyncSender, TryRecvError, TrySendError};
32use std::sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError};
33use std::task::{Poll, Waker};
34use std::thread::Thread;
35
36pub type OperationFuture = Pin<Box<dyn Future<Output = ExecutionOutcome> + 'static>>;
37const INLINE_DEPENDENCY_SLOTS: usize = 8;
38type AsyncHandler = Rc<
39 dyn for<'input, 'dependencies> Fn(
40 InvocationInput<'input>,
41 &'dependencies ResolvedDependencies<'dependencies>,
42 ) -> Result<OperationFuture, ExecutionOutcome>
43 + 'static,
44>;
45type SyncHandler = Rc<
46 dyn for<'input, 'dependencies> Fn(
47 InvocationInput<'input>,
48 &'dependencies ResolvedDependencies<'dependencies>,
49 ) -> Result<ExecutionOutcome, ExecutionOutcome>
50 + 'static,
51>;
52enum Handler {
53 Async(AsyncHandler),
54 Sync {
55 direct: SyncHandler,
56 fallback: AsyncHandler,
57 },
58}
59
60impl Handler {
61 fn prepare(
62 &self,
63 input: InvocationInput<'_>,
64 dependencies: &ResolvedDependencies<'_>,
65 ) -> Result<OperationFuture, ExecutionOutcome> {
66 match self {
67 Self::Async(handler) => handler(input, dependencies),
68 Self::Sync { fallback, .. } => fallback(input, dependencies),
69 }
70 }
71
72 fn invoke_sync(
73 &self,
74 input: InvocationInput<'_>,
75 dependencies: &ResolvedDependencies<'_>,
76 ) -> Option<Result<ExecutionOutcome, ExecutionOutcome>> {
77 match self {
78 Self::Async(_) => None,
79 Self::Sync { direct, .. } => Some(direct(input, dependencies)),
80 }
81 }
82}
83
84type SingletonCompilation = (Vec<Option<DependencyValue>>, Vec<Option<usize>>);
85type OperationHookFuture = Pin<Box<dyn Future<Output = Result<(), DependencyError>> + 'static>>;
86type ResponseHookFuture = Pin<Box<dyn Future<Output = ()> + 'static>>;
87type OperationHook = Rc<dyn Fn(HookContext) -> OperationHookFuture>;
88type ResponseHook = Rc<dyn Fn(HookContext, HookOutcome) -> ResponseHookFuture>;
89type LifecycleHook = Rc<dyn Fn() -> OperationHookFuture>;
90type AbortFuture = Pin<Box<dyn Future<Output = InvocationAbort> + 'static>>;
91type BlockingJob = Box<dyn FnOnce() + Send + 'static>;
92
93static GLOBAL_BLOCKING_POOL: OnceLock<BlockingPool> = OnceLock::new();
94
95#[derive(Clone, Copy, Debug, Eq, PartialEq)]
97pub struct BlockingPoolConfig {
98 workers: NonZeroUsize,
99 queue_capacity: NonZeroUsize,
100}
101
102impl BlockingPoolConfig {
103 #[must_use]
104 pub const fn new(workers: NonZeroUsize, queue_capacity: NonZeroUsize) -> Self {
105 Self {
106 workers,
107 queue_capacity,
108 }
109 }
110
111 #[must_use]
112 pub const fn workers(self) -> NonZeroUsize {
113 self.workers
114 }
115
116 #[must_use]
117 pub const fn queue_capacity(self) -> NonZeroUsize {
118 self.queue_capacity
119 }
120}
121
122impl Default for BlockingPoolConfig {
123 fn default() -> Self {
124 let workers = std::thread::available_parallelism()
125 .unwrap_or(NonZeroUsize::MIN)
126 .get()
127 .max(2);
128 Self {
129 workers: NonZeroUsize::new(workers).expect("worker count is non-zero"),
130 queue_capacity: NonZeroUsize::new(1024).expect("queue capacity is non-zero"),
131 }
132 }
133}
134
135thread_local! {
136 static ON_BLOCKING_WORKER: Cell<bool> = const { Cell::new(false) };
137}
138
139#[must_use]
145pub fn on_blocking_worker() -> bool {
146 ON_BLOCKING_WORKER.with(Cell::get)
147}
148
149struct ParkedWorkers {
151 threads: Vec<Option<Thread>>,
152 idle: Vec<usize>,
153}
154
155struct BlockingShared {
160 receiver: Mutex<Receiver<BlockingJob>>,
161 parked: Mutex<ParkedWorkers>,
162 idle: AtomicUsize,
163}
164
165impl BlockingShared {
166 fn parked(&self) -> MutexGuard<'_, ParkedWorkers> {
167 self.parked.lock().unwrap_or_else(PoisonError::into_inner)
168 }
169
170 fn take_job(&self) -> Result<BlockingJob, TryRecvError> {
171 self.receiver
172 .lock()
173 .unwrap_or_else(PoisonError::into_inner)
174 .try_recv()
175 }
176
177 fn wake_one(&self) {
183 if self.idle.load(Ordering::SeqCst) == 0 {
184 return;
185 }
186 let thread = {
187 let mut parked = self.parked();
188 let index = parked.idle.pop();
189 self.idle.store(parked.idle.len(), Ordering::SeqCst);
190 index.and_then(|index| parked.threads[index].clone())
191 };
192 if let Some(thread) = thread {
193 thread.unpark();
194 }
195 }
196
197 fn wake_all(&self) {
198 let threads: Vec<Thread> = {
199 let mut parked = self.parked();
200 parked.idle.clear();
201 self.idle.store(0, Ordering::SeqCst);
202 parked.threads.iter().flatten().cloned().collect()
203 };
204 for thread in threads {
205 thread.unpark();
206 }
207 }
208
209 fn register(&self, index: usize) {
210 let mut parked = self.parked();
211 if !parked.idle.contains(&index) {
212 parked.idle.push(index);
213 }
214 self.idle.store(parked.idle.len(), Ordering::SeqCst);
215 }
216
217 fn unregister(&self, index: usize) {
218 let mut parked = self.parked();
219 if let Some(position) = parked.idle.iter().rposition(|&at| at == index) {
220 parked.idle.swap_remove(position);
221 self.idle.store(parked.idle.len(), Ordering::SeqCst);
222 }
223 }
224
225 fn run_worker(&self, index: usize) {
226 ON_BLOCKING_WORKER.with(|worker| worker.set(true));
227 self.parked().threads[index] = Some(std::thread::current());
228 loop {
229 match self.take_job() {
230 Ok(job) => {
231 drop(catch_unwind(AssertUnwindSafe(job)));
233 continue;
234 }
235 Err(TryRecvError::Disconnected) => return,
236 Err(TryRecvError::Empty) => {}
237 }
238 self.register(index);
239 match self.take_job() {
240 Ok(job) => {
241 self.unregister(index);
242 drop(catch_unwind(AssertUnwindSafe(job)));
243 continue;
244 }
245 Err(TryRecvError::Disconnected) => {
246 self.unregister(index);
247 return;
248 }
249 Err(TryRecvError::Empty) => {}
250 }
251 std::thread::park();
252 self.unregister(index);
253 }
254 }
255}
256
257struct PoolHandle {
258 sender: Option<SyncSender<BlockingJob>>,
259 shared: Arc<BlockingShared>,
260}
261
262impl Drop for PoolHandle {
263 fn drop(&mut self) {
264 drop(self.sender.take());
267 self.shared.wake_all();
268 }
269}
270
271#[derive(Clone)]
273pub struct BlockingPool {
274 handle: Arc<PoolHandle>,
275}
276
277impl BlockingPool {
278 pub fn new(config: BlockingPoolConfig) -> std::io::Result<Self> {
284 let (sender, receiver) = mpsc::sync_channel::<BlockingJob>(config.queue_capacity.get());
285 let workers = config.workers.get();
286 let shared = Arc::new(BlockingShared {
287 receiver: Mutex::new(receiver),
288 parked: Mutex::new(ParkedWorkers {
289 threads: vec![None; workers],
290 idle: Vec::with_capacity(workers),
291 }),
292 idle: AtomicUsize::new(0),
293 });
294 for index in 0..workers {
295 let worker = Arc::clone(&shared);
296 let spawned = std::thread::Builder::new()
297 .name(format!("blazingly-blocking-{index}"))
298 .spawn(move || worker.run_worker(index));
299 if let Err(error) = spawned {
300 drop(sender);
301 shared.wake_all();
302 return Err(error);
303 }
304 }
305 Ok(Self {
306 handle: Arc::new(PoolHandle {
307 sender: Some(sender),
308 shared,
309 }),
310 })
311 }
312
313 fn submit(&self, job: BlockingJob) -> Result<(), BlockingError> {
314 let Some(sender) = self.handle.sender.as_ref() else {
315 return Err(BlockingError::Unavailable);
316 };
317 sender.try_send(job).map_err(|error| match error {
318 TrySendError::Full(_) => BlockingError::Saturated,
319 TrySendError::Disconnected(_) => BlockingError::Unavailable,
320 })?;
321 self.handle.shared.wake_one();
322 Ok(())
323 }
324}
325
326impl fmt::Debug for BlockingPool {
327 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
328 formatter
329 .debug_struct("BlockingPool")
330 .finish_non_exhaustive()
331 }
332}
333
334pub fn install_global_blocking_pool(config: BlockingPoolConfig) -> Result<(), BlockingError> {
341 let pool = BlockingPool::new(config).map_err(|_| BlockingError::Unavailable)?;
342 GLOBAL_BLOCKING_POOL
343 .set(pool)
344 .map_err(|_| BlockingError::AlreadyConfigured)
345}
346
347#[derive(Clone, Copy, Debug, Eq, PartialEq)]
349pub enum BlockingError {
350 Saturated,
351 Unavailable,
352 Panicked,
353 AlreadyConfigured,
354}
355
356impl fmt::Display for BlockingError {
357 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
358 formatter.write_str(match self {
359 Self::Saturated => "blocking handler queue is saturated",
360 Self::Unavailable => "blocking handler pool is unavailable",
361 Self::Panicked => "blocking handler panicked",
362 Self::AlreadyConfigured => "blocking handler pool is already configured",
363 })
364 }
365}
366
367impl std::error::Error for BlockingError {}
368
369struct BlockingState<T> {
370 result: Option<Result<T, BlockingError>>,
371 waker: Option<Waker>,
372}
373
374pub struct BlockingFuture<T> {
376 state: Arc<Mutex<BlockingState<T>>>,
377}
378
379impl<T> Future for BlockingFuture<T> {
380 type Output = Result<T, BlockingError>;
381
382 fn poll(self: Pin<&mut Self>, context: &mut std::task::Context<'_>) -> Poll<Self::Output> {
383 let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
384 if let Some(result) = state.result.take() {
385 return Poll::Ready(result);
386 }
387 state.waker = Some(context.waker().clone());
388 Poll::Pending
389 }
390}
391
392#[must_use]
394pub fn run_blocking<Task, Output>(task: Task) -> BlockingFuture<Output>
395where
396 Task: FnOnce() -> Output + Send + 'static,
397 Output: Send + 'static,
398{
399 let state = Arc::new(Mutex::new(BlockingState {
400 result: None,
401 waker: None,
402 }));
403 let future = BlockingFuture {
404 state: Arc::clone(&state),
405 };
406 let pool = match global_blocking_pool() {
407 Ok(pool) => pool,
408 Err(error) => {
409 complete_blocking(&state, Err(error));
410 return future;
411 }
412 };
413 let job_state = Arc::clone(&state);
414 let job = Box::new(move || {
415 let result = catch_unwind(AssertUnwindSafe(task)).map_err(|_| BlockingError::Panicked);
416 complete_blocking(&job_state, result);
417 });
418 if let Err(error) = pool.submit(job) {
419 complete_blocking(&state, Err(error));
420 }
421 future
422}
423
424fn complete_blocking<T>(state: &Arc<Mutex<BlockingState<T>>>, result: Result<T, BlockingError>) {
425 let waker = {
426 let mut state = state.lock().unwrap_or_else(PoisonError::into_inner);
427 state.result = Some(result);
428 state.waker.take()
429 };
430 if let Some(waker) = waker {
431 waker.wake();
432 }
433}
434
435fn global_blocking_pool() -> Result<&'static BlockingPool, BlockingError> {
436 if let Some(pool) = GLOBAL_BLOCKING_POOL.get() {
437 return Ok(pool);
438 }
439 let pool =
440 BlockingPool::new(BlockingPoolConfig::default()).map_err(|_| BlockingError::Unavailable)?;
441 let _ = GLOBAL_BLOCKING_POOL.set(pool);
442 GLOBAL_BLOCKING_POOL.get().ok_or(BlockingError::Unavailable)
443}
444
445#[must_use]
446pub fn blocking_error_outcome(error: BlockingError) -> ExecutionOutcome {
447 match error {
448 BlockingError::Saturated => ExecutionOutcome::Rejected {
449 status: 503,
450 code: "blocking_pool_saturated".to_owned(),
451 message: error.to_string(),
452 details: None,
453 },
454 BlockingError::Unavailable | BlockingError::Panicked | BlockingError::AlreadyConfigured => {
455 ExecutionOutcome::InternalError {
456 code: "blocking_handler_failed".to_owned(),
457 message: error.to_string(),
458 }
459 }
460 }
461}
462
463struct CancellationState {
464 cancelled: AtomicBool,
465 wakers: Mutex<Vec<Waker>>,
466}
467
468#[derive(Clone)]
471pub struct CancellationToken {
472 state: Arc<CancellationState>,
473}
474
475impl CancellationToken {
476 #[must_use]
477 pub fn new() -> Self {
478 Self {
479 state: Arc::new(CancellationState {
480 cancelled: AtomicBool::new(false),
481 wakers: Mutex::new(Vec::new()),
482 }),
483 }
484 }
485
486 pub fn cancel(&self) {
488 if self.state.cancelled.swap(true, Ordering::AcqRel) {
489 return;
490 }
491 let mut wakers = self
492 .state
493 .wakers
494 .lock()
495 .unwrap_or_else(std::sync::PoisonError::into_inner);
496 for waker in wakers.drain(..) {
497 waker.wake();
498 }
499 }
500
501 #[must_use]
502 pub fn is_cancelled(&self) -> bool {
503 self.state.cancelled.load(Ordering::Acquire)
504 }
505
506 #[must_use]
507 pub fn cancelled(&self) -> Cancelled {
508 Cancelled {
509 token: self.clone(),
510 }
511 }
512}
513
514impl Default for CancellationToken {
515 fn default() -> Self {
516 Self::new()
517 }
518}
519
520pub struct Cancelled {
522 token: CancellationToken,
523}
524
525impl Future for Cancelled {
526 type Output = ();
527
528 fn poll(self: Pin<&mut Self>, context: &mut std::task::Context<'_>) -> Poll<Self::Output> {
529 if self.token.is_cancelled() {
530 return Poll::Ready(());
531 }
532 let mut wakers = self
533 .token
534 .state
535 .wakers
536 .lock()
537 .unwrap_or_else(std::sync::PoisonError::into_inner);
538 if self.token.is_cancelled() {
539 return Poll::Ready(());
540 }
541 if !wakers.iter().any(|waker| waker.will_wake(context.waker())) {
542 wakers.push(context.waker().clone());
543 }
544 Poll::Pending
545 }
546}
547
548#[derive(Clone, Copy, Debug, Eq, PartialEq)]
550pub enum InvocationAbort {
551 Cancelled,
552 TimedOut,
553}
554
555impl InvocationAbort {
556 fn into_execution_outcome(self) -> ExecutionOutcome {
557 match self {
558 Self::Cancelled => ExecutionOutcome::Rejected {
559 status: 499,
560 code: "invocation_cancelled".to_owned(),
561 message: "operation invocation was cancelled".to_owned(),
562 details: None,
563 },
564 Self::TimedOut => ExecutionOutcome::Rejected {
565 status: 504,
566 code: "invocation_timeout".to_owned(),
567 message: "operation invocation exceeded its time limit".to_owned(),
568 details: None,
569 },
570 }
571 }
572}
573
574#[derive(Default)]
580pub struct InvocationControl {
581 signals: Vec<AbortFuture>,
582}
583
584impl InvocationControl {
585 #[must_use]
586 pub const fn new() -> Self {
587 Self {
588 signals: Vec::new(),
589 }
590 }
591
592 #[must_use]
593 pub fn with_cancellation(mut self, token: CancellationToken) -> Self {
594 self.signals.push(Box::pin(async move {
595 token.cancelled().await;
596 InvocationAbort::Cancelled
597 }));
598 self
599 }
600
601 #[must_use]
602 pub fn with_timeout<Timeout>(mut self, timeout: Timeout) -> Self
603 where
604 Timeout: Future<Output = ()> + 'static,
605 {
606 self.signals.push(Box::pin(async move {
607 timeout.await;
608 InvocationAbort::TimedOut
609 }));
610 self
611 }
612
613 async fn run<Output>(
614 &mut self,
615 future: impl Future<Output = Output>,
616 ) -> Result<Output, InvocationAbort> {
617 let mut future = Box::pin(future);
618 std::future::poll_fn(|context| {
619 for signal in &mut self.signals {
620 if let Poll::Ready(abort) = signal.as_mut().poll(context) {
621 return Poll::Ready(Err(abort));
622 }
623 }
624 future.as_mut().poll(context).map(Ok)
625 })
626 .await
627 }
628}
629
630#[derive(Clone, Debug, Eq, PartialEq)]
632pub struct HookContext {
633 operation_id: Rc<str>,
634}
635
636impl HookContext {
637 #[must_use]
638 pub fn operation_id(&self) -> &str {
639 &self.operation_id
640 }
641}
642
643#[derive(Clone, Copy, Debug, Eq, PartialEq)]
645pub struct HookOutcome {
646 pub status: u16,
647 pub kind: HookOutcomeKind,
648}
649
650#[derive(Clone, Copy, Debug, Eq, PartialEq)]
652pub enum HookOutcomeKind {
653 Success,
654 Rejected,
655 DomainError,
656 InternalError,
657}
658
659impl From<&ExecutionOutcome> for HookOutcome {
660 fn from(outcome: &ExecutionOutcome) -> Self {
661 match outcome {
662 ExecutionOutcome::Success { status, .. }
663 | ExecutionOutcome::StreamingSuccess { status, .. } => Self {
664 status: *status,
665 kind: HookOutcomeKind::Success,
666 },
667 ExecutionOutcome::Upgrade { .. } => Self {
668 status: 101,
669 kind: HookOutcomeKind::Success,
670 },
671 ExecutionOutcome::Rejected { status, .. } => Self {
672 status: *status,
673 kind: HookOutcomeKind::Rejected,
674 },
675 ExecutionOutcome::DomainError(failure) => Self {
676 status: failure.status,
677 kind: HookOutcomeKind::DomainError,
678 },
679 ExecutionOutcome::InternalError { .. } => Self {
680 status: 500,
681 kind: HookOutcomeKind::InternalError,
682 },
683 }
684 }
685}
686
687pub trait HttpRequestParts {
692 fn value(&self, source: InputSource, name: &str, index: usize) -> Option<Cow<'_, str>>;
693 fn body(&self) -> &[u8];
694
695 fn take_body_stream(&self) -> Option<StreamingBody> {
697 None
698 }
699
700 fn extension(&self, _type_id: std::any::TypeId) -> Option<&dyn std::any::Any> {
706 None
707 }
708
709 fn method(&self) -> Option<HttpMethod> {
715 None
716 }
717
718 fn path(&self) -> Option<&str> {
720 None
721 }
722
723 fn peer_addr(&self) -> Option<std::net::SocketAddr> {
725 None
726 }
727
728 fn scheme(&self) -> Option<&str> {
730 None
731 }
732
733 fn host(&self) -> Option<&str> {
735 None
736 }
737}
738
739#[derive(Debug)]
744pub struct UploadBody {
745 stream: StreamingBody,
746 bytes_read: u64,
747 content_type: Option<String>,
750}
751
752impl UploadBody {
753 #[must_use]
754 pub fn new(stream: StreamingBody) -> Self {
755 Self {
756 stream,
757 bytes_read: 0,
758 content_type: None,
759 }
760 }
761
762 #[must_use]
764 pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
765 self.content_type = Some(content_type.into());
766 self
767 }
768
769 #[must_use]
771 pub fn content_type(&self) -> Option<&str> {
772 self.content_type.as_deref()
773 }
774
775 pub fn into_multipart(self) -> Result<MultipartStream, MultipartError> {
787 let content_type = self.content_type.ok_or(MultipartError::Malformed(
788 "missing multipart Content-Type header",
789 ))?;
790 MultipartStream::new(self.stream, &content_type)
791 }
792
793 #[must_use]
794 pub const fn exact_length(&self) -> Option<u64> {
795 self.stream.exact_length()
796 }
797
798 #[must_use]
799 pub const fn bytes_read(&self) -> u64 {
800 self.bytes_read
801 }
802
803 pub async fn next_chunk(&mut self) -> Option<Result<Vec<u8>, BodyStreamError>> {
805 let chunk = self.stream.next_chunk().await;
806 if let Some(Ok(bytes)) = &chunk {
807 self.bytes_read = self
808 .bytes_read
809 .saturating_add(u64::try_from(bytes.len()).unwrap_or(u64::MAX));
810 }
811 chunk
812 }
813
814 pub async fn collect(mut self, limit: usize) -> Result<Vec<u8>, BodyStreamError> {
820 let mut body = Vec::new();
821 while let Some(chunk) = self.next_chunk().await {
822 let chunk = chunk?;
823 if body.len().saturating_add(chunk.len()) > limit {
824 return Err(BodyStreamError::new(
825 "upload_collect_limit_exceeded",
826 format!("streaming upload exceeds the {limit}-byte collection limit"),
827 ));
828 }
829 body.extend_from_slice(&chunk);
830 self.stream.recycle(chunk);
831 }
832 Ok(body)
833 }
834}
835
836impl ApiSchema for UploadBody {
837 fn type_descriptor() -> TypeDescriptor {
838 TypeDescriptor::scalar("UploadBody", SchemaKind::Binary)
839 }
840}
841
842impl FromInvocation for UploadBody {
843 fn from_invocation(
844 input: &InvocationInput<'_>,
845 _name: &str,
846 _required: bool,
847 ) -> Result<Self, InputRejection> {
848 let InvocationInput::Http(request) = input else {
849 return Err(InputRejection::new(
850 400,
851 "streaming_input_requires_http",
852 "streaming request bodies are available only through HTTP",
853 ));
854 };
855 let stream = request
856 .take_body_stream()
857 .unwrap_or_else(|| StreamingBody::once(request.body().to_vec()));
858 let body = Self::new(stream);
859 Ok(
860 match request.value(InputSource::Header, "content-type", 0) {
861 Some(content_type) => body.with_content_type(content_type),
862 None => body,
863 },
864 )
865 }
866}
867
868#[derive(Clone, Copy)]
870pub enum InvocationInput<'input> {
871 Http(&'input dyn HttpRequestParts),
872 Arguments(&'input Value),
873}
874
875impl<'input> InvocationInput<'input> {
876 #[must_use]
877 pub const fn http(request: &'input dyn HttpRequestParts) -> Self {
878 Self::Http(request)
879 }
880
881 #[must_use]
882 pub const fn arguments(arguments: &'input Value) -> Self {
883 Self::Arguments(arguments)
884 }
885}
886
887pub struct ResolvedDependencies<'values> {
893 singletons: &'values [Option<DependencyValue>],
894 requests: &'values [Option<DependencyValue>],
895 slots: &'values [DependencySlot],
896}
897
898impl ResolvedDependencies<'_> {
899 pub fn get<T: 'static>(&self, index: usize) -> Result<Depends<T>, DependencyError> {
906 let slot = self.slots.get(index).copied().ok_or_else(|| {
907 DependencyError::internal(
908 "invalid_dependency_argument",
909 "handler requested an unknown compiled dependency argument",
910 )
911 })?;
912 resolve_dependency(slot, self.singletons, self.requests)
913 }
914
915 pub fn get_cloned<T: Clone + 'static>(&self, index: usize) -> Result<T, DependencyError> {
922 self.get::<T>(index).map(|dependency| (*dependency).clone())
923 }
924}
925
926#[derive(Clone, Debug, PartialEq)]
928pub struct InputRejection {
929 status: u16,
930 code: String,
931 message: String,
932 details: Option<Value>,
933}
934
935impl InputRejection {
936 #[must_use]
937 pub fn new(status: u16, code: impl Into<String>, message: impl Into<String>) -> Self {
938 Self {
939 status,
940 code: code.into(),
941 message: message.into(),
942 details: None,
943 }
944 }
945
946 #[must_use]
947 pub fn with_details(mut self, details: Value) -> Self {
948 self.details = Some(details);
949 self
950 }
951
952 #[must_use]
953 pub fn into_execution_outcome(self) -> ExecutionOutcome {
954 ExecutionOutcome::Rejected {
955 status: self.status,
956 code: self.code,
957 message: self.message,
958 details: self.details,
959 }
960 }
961}
962
963pub trait FromInvocation: Sized {
965 fn from_invocation(
972 input: &InvocationInput<'_>,
973 name: &str,
974 required: bool,
975 ) -> Result<Self, InputRejection>;
976}
977
978#[derive(Clone, Copy, Debug, Eq, PartialEq)]
986pub struct Extract<T>(pub T);
987
988impl<T: FromInvocation> FromInvocation for Extract<T> {
989 fn from_invocation(
990 input: &InvocationInput<'_>,
991 name: &str,
992 required: bool,
993 ) -> Result<Self, InputRejection> {
994 T::from_invocation(input, name, required).map(Self)
995 }
996}
997
998#[derive(Clone, Debug, Eq, PartialEq)]
1009pub struct RequestParts {
1010 pub method: HttpMethod,
1011 pub path: String,
1012 pub scheme: Option<String>,
1013 pub host: Option<String>,
1014 pub peer_addr: Option<std::net::SocketAddr>,
1015}
1016
1017impl FromInvocation for RequestParts {
1018 fn from_invocation(
1019 input: &InvocationInput<'_>,
1020 _name: &str,
1021 _required: bool,
1022 ) -> Result<Self, InputRejection> {
1023 let InvocationInput::Http(request) = input else {
1024 return Err(InputRejection::new(
1025 400,
1026 "transport_mismatch",
1027 "this operation reads HTTP request parts, which the invoking \
1028 transport does not carry",
1029 ));
1030 };
1031 let (Some(method), Some(path)) = (request.method(), request.path()) else {
1032 return Err(InputRejection::new(
1033 500,
1034 "request_parts_unavailable",
1035 "the HTTP adapter serving this request does not expose raw \
1036 request parts",
1037 ));
1038 };
1039 Ok(Self {
1040 method,
1041 path: path.to_owned(),
1042 scheme: request.scheme().map(str::to_owned),
1043 host: request.host().map(str::to_owned),
1044 peer_addr: request.peer_addr(),
1045 })
1046 }
1047}
1048
1049#[derive(Clone, Debug, Eq, PartialEq)]
1051pub struct Extension<T>(pub T);
1052
1053impl<T> FromInvocation for Extension<T>
1054where
1055 T: Clone + 'static,
1056{
1057 fn from_invocation(
1058 input: &InvocationInput<'_>,
1059 name: &str,
1060 _required: bool,
1061 ) -> Result<Self, InputRejection> {
1062 let InvocationInput::Http(request) = input else {
1063 return Err(InputRejection::new(
1064 500,
1065 "extension_transport_mismatch",
1066 "request extension is unavailable on this transport",
1067 ));
1068 };
1069 request
1070 .extension(std::any::TypeId::of::<T>())
1071 .and_then(|value| value.downcast_ref::<T>())
1072 .cloned()
1073 .map(Self)
1074 .ok_or_else(|| {
1075 InputRejection::new(
1076 500,
1077 "missing_request_extension",
1078 format!("request middleware did not install extension `{name}`"),
1079 )
1080 })
1081 }
1082}
1083
1084#[doc(hidden)]
1090pub type ProviderInputDecoder =
1091 Rc<dyn Fn(&InvocationInput<'_>) -> Result<DependencyValue, InputRejection>>;
1092
1093#[doc(hidden)]
1095pub struct ProviderInput {
1096 descriptor: InputDescriptor,
1097 value_type: core::any::TypeId,
1098 decode: ProviderInputDecoder,
1099}
1100
1101impl ProviderInput {
1102 #[doc(hidden)]
1104 #[must_use]
1105 pub fn new<T: 'static>(descriptor: InputDescriptor, decode: ProviderInputDecoder) -> Self {
1106 Self {
1107 descriptor,
1108 value_type: core::any::TypeId::of::<T>(),
1109 decode,
1110 }
1111 }
1112}
1113
1114pub struct RequestProvider {
1121 provider: Provider,
1122 inputs: Vec<ProviderInput>,
1123}
1124
1125impl RequestProvider {
1126 #[doc(hidden)]
1128 #[must_use]
1129 pub fn new(provider: Provider, inputs: Vec<ProviderInput>) -> Self {
1130 Self { provider, inputs }
1131 }
1132
1133 fn key(&self) -> blazingly_di::DependencyKey {
1134 self.provider.key()
1135 }
1136
1137 fn lifetime(&self) -> DependencyLifetime {
1138 self.provider.lifetime()
1139 }
1140
1141 fn dependencies(&self) -> &[blazingly_di::DependencyKey] {
1142 self.provider.dependencies()
1143 }
1144
1145 fn compile(
1146 &self,
1147 slots: &[DependencySlot],
1148 ) -> Result<CompiledProvider, blazingly_di::ProviderCompileError> {
1149 self.provider.compile(slots)
1150 }
1151}
1152
1153impl From<Provider> for RequestProvider {
1154 fn from(provider: Provider) -> Self {
1155 Self {
1156 provider,
1157 inputs: Vec::new(),
1158 }
1159 }
1160}
1161
1162impl<T> FromInvocation for Json<T>
1163where
1164 T: ApiSchema + DeserializeOwned,
1165{
1166 fn from_invocation(
1167 input: &InvocationInput<'_>,
1168 name: &str,
1169 required: bool,
1170 ) -> Result<Self, InputRejection> {
1171 extract_argument(input, name, InputSource::Json, required).map(Self)
1172 }
1173}
1174
1175impl<T> FromInvocation for Path<T>
1176where
1177 T: ApiSchema + DeserializeOwned,
1178{
1179 fn from_invocation(
1180 input: &InvocationInput<'_>,
1181 name: &str,
1182 required: bool,
1183 ) -> Result<Self, InputRejection> {
1184 extract_argument(input, name, InputSource::Path, required).map(Self)
1185 }
1186}
1187
1188impl<T> FromInvocation for Query<T>
1189where
1190 T: ApiSchema + DeserializeOwned,
1191{
1192 fn from_invocation(
1193 input: &InvocationInput<'_>,
1194 name: &str,
1195 required: bool,
1196 ) -> Result<Self, InputRejection> {
1197 extract_argument(input, name, InputSource::Query, required).map(Self)
1198 }
1199}
1200
1201impl<T> FromInvocation for Header<T>
1202where
1203 T: ApiSchema + DeserializeOwned,
1204{
1205 fn from_invocation(
1206 input: &InvocationInput,
1207 name: &str,
1208 required: bool,
1209 ) -> Result<Self, InputRejection> {
1210 extract_argument(input, name, InputSource::Header, required).map(Self)
1211 }
1212}
1213
1214impl<T> FromInvocation for Cookie<T>
1215where
1216 T: ApiSchema + DeserializeOwned,
1217{
1218 fn from_invocation(
1219 input: &InvocationInput,
1220 name: &str,
1221 required: bool,
1222 ) -> Result<Self, InputRejection> {
1223 extract_argument(input, name, InputSource::Cookie, required).map(Self)
1224 }
1225}
1226
1227impl<T> FromInvocation for Form<T>
1228where
1229 T: ApiSchema + DeserializeOwned,
1230{
1231 fn from_invocation(
1232 input: &InvocationInput,
1233 name: &str,
1234 required: bool,
1235 ) -> Result<Self, InputRejection> {
1236 extract_argument(input, name, InputSource::Form, required).map(Self)
1237 }
1238}
1239
1240impl<T> FromInvocation for Multipart<T>
1241where
1242 T: ApiSchema + DeserializeOwned,
1243{
1244 fn from_invocation(
1245 input: &InvocationInput,
1246 name: &str,
1247 required: bool,
1248 ) -> Result<Self, InputRejection> {
1249 let decoded = match input {
1250 InvocationInput::Http(request) => {
1251 let descriptor = cached_type_descriptor::<T>();
1252 let parts = parse_multipart_request(*request)?;
1253 let slots = UploadSlots::acquire();
1258 let value = multipart_argument_value(&parts, name, required, &descriptor, &slots)?;
1259 blazingly_json::from_value(value).map_err(|error| {
1260 decode_rejection(name, InputSource::Multipart, &error.to_string())
1261 })?
1262 }
1263 InvocationInput::Arguments(_) => {
1264 return extract_argument(input, name, InputSource::Multipart, required).map(Self);
1265 }
1266 };
1267 validate_decoded(decoded, InputSource::Multipart).map(Self)
1268 }
1269}
1270
1271pub trait FilePayload: Sized {
1273 #[doc(hidden)]
1274 fn from_uploads(uploads: Vec<UploadFile>, required: bool) -> Result<Self, InputRejection>;
1275}
1276
1277impl FilePayload for UploadFile {
1278 fn from_uploads(mut uploads: Vec<UploadFile>, required: bool) -> Result<Self, InputRejection> {
1279 if uploads.len() == 1 {
1280 return Ok(uploads.remove(0));
1281 }
1282 Err(file_count_rejection(required, uploads.len(), "exactly one"))
1283 }
1284}
1285
1286impl FilePayload for Option<UploadFile> {
1287 fn from_uploads(mut uploads: Vec<UploadFile>, required: bool) -> Result<Self, InputRejection> {
1288 match uploads.len() {
1289 0 if !required => Ok(None),
1290 1 => Ok(Some(uploads.remove(0))),
1291 count => Err(file_count_rejection(required, count, "zero or one")),
1292 }
1293 }
1294}
1295
1296impl FilePayload for Vec<UploadFile> {
1297 fn from_uploads(uploads: Vec<UploadFile>, required: bool) -> Result<Self, InputRejection> {
1298 if required && uploads.is_empty() {
1299 Err(file_count_rejection(required, 0, "one or more"))
1300 } else {
1301 Ok(uploads)
1302 }
1303 }
1304}
1305
1306impl<T: FilePayload> FromInvocation for File<T> {
1307 fn from_invocation(
1308 input: &InvocationInput,
1309 name: &str,
1310 required: bool,
1311 ) -> Result<Self, InputRejection> {
1312 let uploads = match input {
1313 InvocationInput::Http(request) => parse_multipart_request(*request)?
1314 .into_iter()
1315 .filter(|part| part.name == name)
1316 .map(MultipartPart::into_upload)
1317 .collect::<Vec<_>>(),
1318 InvocationInput::Arguments(arguments) => upload_arguments(arguments, name, required)?,
1319 };
1320 T::from_uploads(uploads, required).map(Self)
1321 }
1322}
1323
1324#[derive(Debug)]
1326pub enum ExecutionOutcome {
1327 Success {
1328 status: u16,
1329 headers: Vec<ResponseHeader>,
1330 body: Option<Vec<u8>>,
1331 background: Vec<BackgroundTask>,
1332 },
1333 StreamingSuccess {
1334 status: u16,
1335 headers: Vec<ResponseHeader>,
1336 body: StreamingBody,
1337 background: Vec<BackgroundTask>,
1338 },
1339 Upgrade {
1340 upgrade: HttpUpgrade,
1341 background: Vec<BackgroundTask>,
1342 },
1343 Rejected {
1344 status: u16,
1345 code: String,
1346 message: String,
1347 details: Option<Value>,
1348 },
1349 DomainError(OperationFailure),
1350 InternalError {
1351 code: String,
1352 message: String,
1353 },
1354}
1355
1356impl ExecutionOutcome {
1357 #[must_use]
1358 pub const fn is_error(&self) -> bool {
1359 !matches!(
1360 self,
1361 Self::Success { .. } | Self::StreamingSuccess { .. } | Self::Upgrade { .. }
1362 )
1363 }
1364}
1365
1366pub trait OperationOutput {
1368 fn into_execution_outcome(self) -> ExecutionOutcome;
1369}
1370
1371impl<T: Serialize> OperationOutput for Json<T> {
1372 fn into_execution_outcome(self) -> ExecutionOutcome {
1373 serialize_success(200, self.0)
1374 }
1375}
1376
1377impl<T: Serialize> OperationOutput for Created<T> {
1378 fn into_execution_outcome(self) -> ExecutionOutcome {
1379 serialize_success(201, self.0)
1380 }
1381}
1382
1383impl<T: Serialize> OperationOutput for Accepted<T> {
1384 fn into_execution_outcome(self) -> ExecutionOutcome {
1385 serialize_success(202, self.0)
1386 }
1387}
1388
1389impl<T> OperationOutput for PreparedJson<T> {
1390 fn into_execution_outcome(self) -> ExecutionOutcome {
1391 ExecutionOutcome::Success {
1392 status: 200,
1393 headers: Vec::new(),
1394 body: Some(self.into_bytes()),
1395 background: Vec::new(),
1396 }
1397 }
1398}
1399
1400impl OperationOutput for NoContent {
1401 fn into_execution_outcome(self) -> ExecutionOutcome {
1402 ExecutionOutcome::Success {
1403 status: 204,
1404 headers: Vec::new(),
1405 body: None,
1406 background: Vec::new(),
1407 }
1408 }
1409}
1410
1411impl OperationOutput for StreamingBody {
1412 fn into_execution_outcome(self) -> ExecutionOutcome {
1413 ExecutionOutcome::StreamingSuccess {
1414 status: 200,
1415 headers: vec![ResponseHeader::new(
1416 "content-type",
1417 "application/octet-stream",
1418 )],
1419 body: self,
1420 background: Vec::new(),
1421 }
1422 }
1423}
1424
1425impl OperationOutput for HttpUpgrade {
1426 fn into_execution_outcome(self) -> ExecutionOutcome {
1427 ExecutionOutcome::Upgrade {
1428 upgrade: self,
1429 background: Vec::new(),
1430 }
1431 }
1432}
1433
1434impl<T: OperationOutput> OperationOutput for Background<T> {
1440 fn into_execution_outcome(self) -> ExecutionOutcome {
1441 let (response, tasks) = self.into_parts();
1442 let mut outcome = response.into_execution_outcome();
1443 match &mut outcome {
1444 ExecutionOutcome::Success { background, .. }
1445 | ExecutionOutcome::StreamingSuccess { background, .. }
1446 | ExecutionOutcome::Upgrade { background, .. } => background.extend(tasks),
1447 ExecutionOutcome::Rejected { .. }
1448 | ExecutionOutcome::DomainError(_)
1449 | ExecutionOutcome::InternalError { .. } => {}
1450 }
1451 outcome
1452 }
1453}
1454
1455impl<const STATUS: u16, T: OperationOutput> OperationOutput for Status<STATUS, T> {
1456 fn into_execution_outcome(self) -> ExecutionOutcome {
1457 if !(200..=399).contains(&STATUS) {
1458 return ExecutionOutcome::InternalError {
1459 code: "invalid_response_status".to_owned(),
1460 message: "typed success status must be between 200 and 399".to_owned(),
1461 };
1462 }
1463 let mut outcome = self.0.into_execution_outcome();
1464 match &mut outcome {
1465 ExecutionOutcome::Success { status, .. }
1466 | ExecutionOutcome::StreamingSuccess { status, .. } => *status = STATUS,
1467 ExecutionOutcome::Upgrade { .. }
1468 | ExecutionOutcome::Rejected { .. }
1469 | ExecutionOutcome::DomainError(_)
1470 | ExecutionOutcome::InternalError { .. } => {}
1471 }
1472 outcome
1473 }
1474}
1475
1476impl<T: OperationOutput> OperationOutput for WithHeaders<T> {
1477 fn into_execution_outcome(self) -> ExecutionOutcome {
1478 let (response, headers) = self.into_parts();
1479 if !headers.iter().all(valid_response_header) {
1480 return ExecutionOutcome::InternalError {
1481 code: "invalid_response_header".to_owned(),
1482 message: "operation produced an invalid response header".to_owned(),
1483 };
1484 }
1485 let mut outcome = response.into_execution_outcome();
1486 match &mut outcome {
1487 ExecutionOutcome::Success {
1488 headers: outcome_headers,
1489 ..
1490 }
1491 | ExecutionOutcome::StreamingSuccess {
1492 headers: outcome_headers,
1493 ..
1494 } => outcome_headers.extend(headers),
1495 ExecutionOutcome::Upgrade { upgrade, .. } => upgrade.extend_headers(headers),
1496 ExecutionOutcome::DomainError(error) => error.headers.extend(headers),
1497 ExecutionOutcome::Rejected { .. } | ExecutionOutcome::InternalError { .. } => {}
1498 }
1499 outcome
1500 }
1501}
1502
1503impl<S, E> OperationOutput for Result<S, E>
1504where
1505 S: OperationOutput,
1506 E: ApiError,
1507{
1508 fn into_execution_outcome(self) -> ExecutionOutcome {
1509 match self {
1510 Ok(success) => success.into_execution_outcome(),
1511 Err(error) => match error.into_failure() {
1512 Ok(error) if error.headers.iter().all(valid_response_header) => {
1513 ExecutionOutcome::DomainError(error)
1514 }
1515 Ok(_) => ExecutionOutcome::InternalError {
1516 code: "invalid_response_header".to_owned(),
1517 message: "operation produced an invalid response header".to_owned(),
1518 },
1519 Err(error) => internal_build_error(error),
1520 },
1521 }
1522 }
1523}
1524
1525pub struct ExecutableOperation {
1527 descriptor: OperationDescriptor,
1528 dependency_requests: Vec<DependencyRequest>,
1529 dependency_plan: Option<CompiledOperationDependencies>,
1530 hooks: CompiledHooks,
1531 handler: Handler,
1532}
1533
1534impl ExecutableOperation {
1535 #[must_use]
1536 pub fn typed<F>(descriptor: OperationDescriptor, handler: F) -> Self
1537 where
1538 F: for<'input> Fn(InvocationInput<'input>) -> Result<OperationFuture, InputRejection>
1539 + 'static,
1540 {
1541 Self {
1542 descriptor,
1543 dependency_requests: Vec::new(),
1544 dependency_plan: Some(CompiledOperationDependencies::empty()),
1545 hooks: CompiledHooks::empty(),
1546 handler: Handler::Async(Rc::new(move |input, _| {
1547 handler(input).map_err(InputRejection::into_execution_outcome)
1548 })),
1549 }
1550 }
1551
1552 #[doc(hidden)]
1554 #[must_use]
1555 pub fn typed_with_dependencies<F>(
1556 descriptor: OperationDescriptor,
1557 dependency_requests: Vec<DependencyRequest>,
1558 handler: F,
1559 ) -> Self
1560 where
1561 F: for<'input, 'dependencies> Fn(
1562 InvocationInput<'input>,
1563 &'dependencies ResolvedDependencies<'dependencies>,
1564 ) -> Result<OperationFuture, ExecutionOutcome>
1565 + 'static,
1566 {
1567 let dependency_plan = dependency_requests
1568 .is_empty()
1569 .then(CompiledOperationDependencies::empty);
1570 Self {
1571 descriptor,
1572 dependency_requests,
1573 dependency_plan,
1574 hooks: CompiledHooks::empty(),
1575 handler: Handler::Async(Rc::new(handler)),
1576 }
1577 }
1578
1579 #[doc(hidden)]
1584 #[must_use]
1585 pub fn typed_sync_with_dependencies<Direct, Fallback>(
1586 descriptor: OperationDescriptor,
1587 dependency_requests: Vec<DependencyRequest>,
1588 direct: Direct,
1589 fallback: Fallback,
1590 ) -> Self
1591 where
1592 Direct: for<'input, 'dependencies> Fn(
1593 InvocationInput<'input>,
1594 &'dependencies ResolvedDependencies<'dependencies>,
1595 ) -> Result<ExecutionOutcome, ExecutionOutcome>
1596 + 'static,
1597 Fallback: for<'input, 'dependencies> Fn(
1598 InvocationInput<'input>,
1599 &'dependencies ResolvedDependencies<'dependencies>,
1600 ) -> Result<OperationFuture, ExecutionOutcome>
1601 + 'static,
1602 {
1603 let dependency_plan = dependency_requests
1604 .is_empty()
1605 .then(CompiledOperationDependencies::empty);
1606 Self {
1607 descriptor,
1608 dependency_requests,
1609 dependency_plan,
1610 hooks: CompiledHooks::empty(),
1611 handler: Handler::Sync {
1612 direct: Rc::new(direct),
1613 fallback: Rc::new(fallback),
1614 },
1615 }
1616 }
1617
1618 #[must_use]
1619 pub fn json<I, O, F, Fut>(descriptor: OperationDescriptor, handler: F) -> Self
1620 where
1621 I: ApiModel + DeserializeOwned + 'static,
1622 O: OperationOutput + 'static,
1623 F: Fn(Json<I>) -> Fut + 'static,
1624 Fut: Future<Output = O> + 'static,
1625 {
1626 Self::typed(descriptor, move |input| {
1627 let input = Json::<I>::from_invocation(&input, "body", true)?;
1628 let output = handler(input);
1629 Ok(Box::pin(async move { output.await.into_execution_outcome() }) as OperationFuture)
1630 })
1631 }
1632
1633 #[must_use]
1634 pub fn empty<O, F, Fut>(descriptor: OperationDescriptor, handler: F) -> Self
1635 where
1636 O: OperationOutput + 'static,
1637 F: Fn() -> Fut + 'static,
1638 Fut: Future<Output = O> + 'static,
1639 {
1640 Self::typed(descriptor, move |_| {
1641 let output = handler();
1642 Ok(Box::pin(async move { output.await.into_execution_outcome() }) as OperationFuture)
1643 })
1644 }
1645
1646 #[must_use]
1647 pub const fn descriptor(&self) -> &OperationDescriptor {
1648 &self.descriptor
1649 }
1650
1651 pub async fn invoke(&self, input: Value) -> ExecutionOutcome {
1652 self.invoke_input(InvocationInput::arguments(&input)).await
1653 }
1654
1655 pub async fn invoke_controlled(
1656 &self,
1657 input: Value,
1658 control: InvocationControl,
1659 ) -> ExecutionOutcome {
1660 self.invoke_input_controlled(InvocationInput::arguments(&input), control)
1661 .await
1662 }
1663
1664 pub async fn invoke_http(&self, request: &dyn HttpRequestParts) -> ExecutionOutcome {
1665 self.invoke_input(InvocationInput::http(request)).await
1666 }
1667
1668 pub async fn invoke_http_controlled(
1669 &self,
1670 request: &dyn HttpRequestParts,
1671 control: InvocationControl,
1672 ) -> ExecutionOutcome {
1673 self.invoke_input_controlled(InvocationInput::http(request), control)
1674 .await
1675 }
1676
1677 async fn invoke_input(&self, input: InvocationInput<'_>) -> ExecutionOutcome {
1678 let outcome = self.invoke_pipeline(input).await;
1679 if self.hooks.is_empty() {
1680 return outcome;
1681 }
1682 self.hooks.on_error(&outcome).await;
1683 self.hooks.on_response(&outcome).await;
1684 outcome
1685 }
1686
1687 async fn invoke_input_controlled(
1688 &self,
1689 input: InvocationInput<'_>,
1690 mut control: InvocationControl,
1691 ) -> ExecutionOutcome {
1692 let outcome = self.invoke_pipeline_controlled(input, &mut control).await;
1693 if self.hooks.is_empty() {
1696 return outcome;
1697 }
1698 self.hooks.on_error(&outcome).await;
1699 self.hooks.on_response(&outcome).await;
1700 outcome
1701 }
1702
1703 async fn invoke_pipeline(&self, input: InvocationInput<'_>) -> ExecutionOutcome {
1704 if let Err(error) = self.hooks.on_request().await {
1705 return dependency_error_outcome(error);
1706 }
1707 let Some(plan) = self.dependency_plan.as_ref() else {
1708 return internal_dependency_error(
1709 "uncompiled_dependency_plan",
1710 "operation dependency plan was not compiled",
1711 );
1712 };
1713 if self.hooks.is_empty() && plan.request_providers.is_empty() {
1714 let dependencies = ResolvedDependencies {
1715 singletons: &plan.singletons,
1716 requests: &[],
1717 slots: &plan.handler_slots,
1718 };
1719 if let Some(outcome) = self.handler.invoke_sync(input, &dependencies) {
1720 return outcome.unwrap_or_else(|outcome| outcome);
1721 }
1722 return match self.handler.prepare(input, &dependencies) {
1723 Ok(handler) => handler.await,
1724 Err(outcome) => outcome,
1725 };
1726 }
1727 let seeded = match plan.decode_inputs(input) {
1728 Ok(seeded) => seeded,
1729 Err(rejection) => return rejection.into_execution_outcome(),
1730 };
1731 let requests = match plan.resolve(seeded).await {
1732 Ok(requests) => requests,
1733 Err(error) => return dependency_error_outcome(error),
1734 };
1735 let dependencies = ResolvedDependencies {
1736 singletons: &plan.singletons,
1737 requests: requests.as_slice(),
1738 slots: &plan.handler_slots,
1739 };
1740 if self.hooks.is_empty()
1741 && let Some(outcome) = self.handler.invoke_sync(input, &dependencies)
1742 {
1743 let outcome = outcome.unwrap_or_else(|outcome| outcome);
1744 if let Err(finalizer_error) = plan.finalize(&requests).await {
1745 return dependency_error_outcome(finalizer_error);
1746 }
1747 return outcome;
1748 }
1749 if let Err(error) = self.hooks.pre_parse().await {
1750 if let Err(finalizer_error) = plan.finalize(&requests).await {
1751 return dependency_error_outcome(finalizer_error);
1752 }
1753 return dependency_error_outcome(error);
1754 }
1755 if let Err(error) = self.hooks.pre_validate().await {
1756 if let Err(finalizer_error) = plan.finalize(&requests).await {
1757 return dependency_error_outcome(finalizer_error);
1758 }
1759 return dependency_error_outcome(error);
1760 }
1761 let handler = match self.handler.prepare(input, &dependencies) {
1762 Ok(handler) => handler,
1763 Err(outcome) => {
1764 if let Err(finalizer_error) = plan.finalize(&requests).await {
1765 return dependency_error_outcome(finalizer_error);
1766 }
1767 return outcome;
1768 }
1769 };
1770 if let Err(error) = self.hooks.pre_handler().await {
1771 if let Err(finalizer_error) = plan.finalize(&requests).await {
1772 return dependency_error_outcome(finalizer_error);
1773 }
1774 return dependency_error_outcome(error);
1775 }
1776 let outcome = handler.await;
1777 if let Err(error) = self.hooks.pre_serialize().await {
1778 if let Err(finalizer_error) = plan.finalize(&requests).await {
1779 return dependency_error_outcome(finalizer_error);
1780 }
1781 return dependency_error_outcome(error);
1782 }
1783 if let Err(error) = plan.finalize(&requests).await {
1784 return dependency_error_outcome(error);
1785 }
1786 outcome
1787 }
1788
1789 async fn invoke_pipeline_controlled(
1790 &self,
1791 input: InvocationInput<'_>,
1792 control: &mut InvocationControl,
1793 ) -> ExecutionOutcome {
1794 match control.run(self.hooks.on_request()).await {
1795 Ok(Ok(())) => {}
1796 Ok(Err(error)) => return dependency_error_outcome(error),
1797 Err(abort) => return abort.into_execution_outcome(),
1798 }
1799 let Some(plan) = self.dependency_plan.as_ref() else {
1800 return internal_dependency_error(
1801 "uncompiled_dependency_plan",
1802 "operation dependency plan was not compiled",
1803 );
1804 };
1805 let seeded = match plan.decode_inputs(input) {
1806 Ok(seeded) => seeded,
1807 Err(rejection) => return rejection.into_execution_outcome(),
1808 };
1809 let requests = match plan.resolve_controlled(seeded, control).await {
1810 Ok(requests) => requests,
1811 Err(ControlledInvocationError::Dependency(error)) => {
1812 return dependency_error_outcome(error);
1813 }
1814 Err(ControlledInvocationError::Abort(abort)) => {
1815 return abort.into_execution_outcome();
1816 }
1817 };
1818 if let Err(outcome) =
1819 run_controlled_hook(control, plan, &requests, self.hooks.pre_parse()).await
1820 {
1821 return outcome;
1822 }
1823 if let Err(outcome) =
1824 run_controlled_hook(control, plan, &requests, self.hooks.pre_validate()).await
1825 {
1826 return outcome;
1827 }
1828 let dependencies = ResolvedDependencies {
1829 singletons: &plan.singletons,
1830 requests: requests.as_slice(),
1831 slots: &plan.handler_slots,
1832 };
1833 let handler = match self.handler.prepare(input, &dependencies) {
1834 Ok(handler) => handler,
1835 Err(outcome) => {
1836 if let Err(finalizer_error) = plan.finalize(&requests).await {
1837 return dependency_error_outcome(finalizer_error);
1838 }
1839 return outcome;
1840 }
1841 };
1842 if let Err(outcome) =
1843 run_controlled_hook(control, plan, &requests, self.hooks.pre_handler()).await
1844 {
1845 return outcome;
1846 }
1847 let outcome = match control.run(handler).await {
1848 Ok(outcome) => outcome,
1849 Err(abort) => {
1850 if let Err(finalizer_error) = plan.finalize(&requests).await {
1851 return dependency_error_outcome(finalizer_error);
1852 }
1853 return abort.into_execution_outcome();
1854 }
1855 };
1856 if let Err(hook_outcome) =
1857 run_controlled_hook(control, plan, &requests, self.hooks.pre_serialize()).await
1858 {
1859 return hook_outcome;
1860 }
1861 if let Err(error) = plan.finalize(&requests).await {
1862 return dependency_error_outcome(error);
1863 }
1864 outcome
1865 }
1866}
1867
1868async fn run_controlled_hook<HookFuture>(
1869 control: &mut InvocationControl,
1870 plan: &CompiledOperationDependencies,
1871 requests: &RequestDependencyValues,
1872 hook: HookFuture,
1873) -> Result<(), ExecutionOutcome>
1874where
1875 HookFuture: Future<Output = Result<(), DependencyError>>,
1876{
1877 let outcome = match control.run(hook).await {
1878 Ok(Ok(())) => return Ok(()),
1879 Ok(Err(error)) => dependency_error_outcome(error),
1880 Err(abort) => abort.into_execution_outcome(),
1881 };
1882 if let Err(finalizer_error) = plan.finalize(requests).await {
1883 return Err(dependency_error_outcome(finalizer_error));
1884 }
1885 Err(outcome)
1886}
1887
1888#[derive(Clone, Default)]
1889struct HookScope {
1890 on_request: Vec<OperationHook>,
1891 pre_parse: Vec<OperationHook>,
1892 pre_validate: Vec<OperationHook>,
1893 pre_handler: Vec<OperationHook>,
1894 pre_serialize: Vec<OperationHook>,
1895 on_error: Vec<ResponseHook>,
1896 on_response: Vec<ResponseHook>,
1897}
1898
1899impl HookScope {
1900 fn is_empty(&self) -> bool {
1901 self.on_request.is_empty()
1902 && self.pre_parse.is_empty()
1903 && self.pre_validate.is_empty()
1904 && self.pre_handler.is_empty()
1905 && self.pre_serialize.is_empty()
1906 && self.on_error.is_empty()
1907 && self.on_response.is_empty()
1908 }
1909
1910 fn inherited(&self, plugin: &PluginHooks) -> Self {
1911 let mut hooks = self.clone();
1912 hooks.on_request.extend(plugin.on_request.iter().cloned());
1913 hooks.pre_parse.extend(plugin.pre_parse.iter().cloned());
1914 hooks
1915 .pre_validate
1916 .extend(plugin.pre_validate.iter().cloned());
1917 hooks.pre_handler.extend(plugin.pre_handler.iter().cloned());
1918 hooks
1919 .pre_serialize
1920 .extend(plugin.pre_serialize.iter().cloned());
1921 hooks.on_error.extend(plugin.on_error.iter().cloned());
1922 hooks.on_response.extend(plugin.on_response.iter().cloned());
1923 hooks
1924 }
1925}
1926
1927struct PluginHooks {
1928 on_request: Vec<OperationHook>,
1929 pre_parse: Vec<OperationHook>,
1930 pre_validate: Vec<OperationHook>,
1931 pre_handler: Vec<OperationHook>,
1932 pre_serialize: Vec<OperationHook>,
1933 on_error: Vec<ResponseHook>,
1934 on_response: Vec<ResponseHook>,
1935}
1936
1937struct CompiledHooks {
1938 context: Option<HookContext>,
1939 scope: HookScope,
1940}
1941
1942impl CompiledHooks {
1943 fn empty() -> Self {
1944 Self {
1945 context: None,
1946 scope: HookScope::default(),
1947 }
1948 }
1949
1950 fn compile(operation: &OperationDescriptor, scope: HookScope) -> Self {
1951 Self {
1952 context: Some(HookContext {
1953 operation_id: Rc::from(operation.contract.id.as_str()),
1954 }),
1955 scope,
1956 }
1957 }
1958
1959 fn is_empty(&self) -> bool {
1960 self.scope.is_empty()
1961 }
1962
1963 async fn on_request(&self) -> Result<(), DependencyError> {
1964 for hook in &self.scope.on_request {
1965 hook(self.context()).await?;
1966 }
1967 Ok(())
1968 }
1969
1970 async fn pre_handler(&self) -> Result<(), DependencyError> {
1971 for hook in &self.scope.pre_handler {
1972 hook(self.context()).await?;
1973 }
1974 Ok(())
1975 }
1976
1977 async fn pre_parse(&self) -> Result<(), DependencyError> {
1978 for hook in &self.scope.pre_parse {
1979 hook(self.context()).await?;
1980 }
1981 Ok(())
1982 }
1983
1984 async fn pre_validate(&self) -> Result<(), DependencyError> {
1985 for hook in &self.scope.pre_validate {
1986 hook(self.context()).await?;
1987 }
1988 Ok(())
1989 }
1990
1991 async fn pre_serialize(&self) -> Result<(), DependencyError> {
1992 for hook in &self.scope.pre_serialize {
1993 hook(self.context()).await?;
1994 }
1995 Ok(())
1996 }
1997
1998 async fn on_error(&self, outcome: &ExecutionOutcome) {
1999 if matches!(
2000 outcome,
2001 ExecutionOutcome::Success { .. }
2002 | ExecutionOutcome::StreamingSuccess { .. }
2003 | ExecutionOutcome::Upgrade { .. }
2004 ) {
2005 return;
2006 }
2007 let outcome = HookOutcome::from(outcome);
2008 for hook in self.scope.on_error.iter().rev() {
2009 hook(self.context(), outcome).await;
2010 }
2011 }
2012
2013 async fn on_response(&self, outcome: &ExecutionOutcome) {
2014 let outcome = HookOutcome::from(outcome);
2015 for hook in self.scope.on_response.iter().rev() {
2016 hook(self.context(), outcome).await;
2017 }
2018 }
2019
2020 fn context(&self) -> HookContext {
2021 self.context.clone().unwrap_or_else(|| HookContext {
2022 operation_id: Rc::from("uncompiled"),
2023 })
2024 }
2025}
2026
2027const ASYNC_RUNNER_CODE: &str = "async_singleton_provider";
2033const CHAIN_UNKNOWN: u8 = 0;
2034const CHAIN_SYNC: u8 = 1;
2035const CHAIN_ASYNC: u8 = 2;
2036
2037fn is_async_runner(error: &DependencyError) -> bool {
2038 matches!(error, DependencyError::Internal { code, .. } if *code == ASYNC_RUNNER_CODE)
2039}
2040
2041#[derive(Clone)]
2042struct CompiledOperationDependencies {
2043 singletons: Rc<Vec<Option<DependencyValue>>>,
2044 request_providers: Vec<CompiledProvider>,
2045 handler_slots: Vec<DependencySlot>,
2046 input_decoders: Vec<ProviderInputDecoder>,
2050 sync_chain: Cell<u8>,
2051}
2052
2053impl CompiledOperationDependencies {
2054 fn empty() -> Self {
2055 Self {
2056 singletons: Rc::new(Vec::new()),
2057 request_providers: Vec::new(),
2058 handler_slots: Vec::new(),
2059 input_decoders: Vec::new(),
2060 sync_chain: Cell::new(CHAIN_UNKNOWN),
2061 }
2062 }
2063
2064 const fn slot_base(&self) -> usize {
2065 self.input_decoders.len()
2066 }
2067
2068 fn decode_inputs(
2074 &self,
2075 input: InvocationInput<'_>,
2076 ) -> Result<RequestDependencyValues, InputRejection> {
2077 let mut requests =
2078 RequestDependencyValues::new(self.slot_base() + self.request_providers.len());
2079 for (index, decode) in self.input_decoders.iter().enumerate() {
2080 let value = decode(&input)?;
2081 requests.set(index, value).map_err(|error| {
2082 InputRejection::new(500, "invalid_input_slot", error.to_string())
2083 })?;
2084 }
2085 Ok(requests)
2086 }
2087
2088 async fn resolve(
2096 &self,
2097 mut requests: RequestDependencyValues,
2098 ) -> Result<RequestDependencyValues, DependencyError> {
2099 let base = self.slot_base();
2100 if self.sync_chain.get() == CHAIN_ASYNC {
2101 return self.resolve_from(requests, 0).await;
2102 }
2103 for (index, provider) in self.request_providers.iter().enumerate() {
2104 match provider.run_sync(&self.singletons, requests.as_slice()) {
2105 Ok(value) => requests.set(base + index, value)?,
2106 Err(error) if is_async_runner(&error) => {
2107 self.sync_chain.set(CHAIN_ASYNC);
2108 return self.resolve_from(requests, index).await;
2109 }
2110 Err(error) => {
2111 self.finalize_prefix(&requests, index).await?;
2112 return Err(error);
2113 }
2114 }
2115 }
2116 self.sync_chain.set(CHAIN_SYNC);
2117 Ok(requests)
2118 }
2119
2120 async fn resolve_from(
2121 &self,
2122 mut requests: RequestDependencyValues,
2123 start: usize,
2124 ) -> Result<RequestDependencyValues, DependencyError> {
2125 let base = self.slot_base();
2126 for (index, provider) in self.request_providers.iter().enumerate().skip(start) {
2127 let value = match provider.run(&self.singletons, requests.as_slice()).await {
2128 Ok(value) => value,
2129 Err(error) => {
2130 self.finalize_prefix(&requests, index).await?;
2131 return Err(error);
2132 }
2133 };
2134 requests.set(base + index, value)?;
2135 }
2136 Ok(requests)
2137 }
2138
2139 async fn resolve_controlled(
2140 &self,
2141 requests: RequestDependencyValues,
2142 control: &mut InvocationControl,
2143 ) -> Result<RequestDependencyValues, ControlledInvocationError> {
2144 let base = self.slot_base();
2145 let mut requests = requests;
2146 for (index, provider) in self.request_providers.iter().enumerate() {
2147 let value = match control
2148 .run(provider.run(&self.singletons, requests.as_slice()))
2149 .await
2150 {
2151 Ok(Ok(value)) => value,
2152 Ok(Err(error)) => {
2153 self.finalize_prefix(&requests, index)
2154 .await
2155 .map_err(ControlledInvocationError::Dependency)?;
2156 return Err(ControlledInvocationError::Dependency(error));
2157 }
2158 Err(abort) => {
2159 self.finalize_prefix(&requests, index)
2160 .await
2161 .map_err(ControlledInvocationError::Dependency)?;
2162 return Err(ControlledInvocationError::Abort(abort));
2163 }
2164 };
2165 requests
2166 .set(base + index, value)
2167 .map_err(ControlledInvocationError::Dependency)?;
2168 }
2169 Ok(requests)
2170 }
2171
2172 async fn finalize(&self, requests: &RequestDependencyValues) -> Result<(), DependencyError> {
2173 self.finalize_prefix(requests, self.request_providers.len())
2174 .await
2175 }
2176
2177 async fn finalize_prefix(
2178 &self,
2179 requests: &RequestDependencyValues,
2180 initialized: usize,
2181 ) -> Result<(), DependencyError> {
2182 let base = self.slot_base();
2183 for (index, provider) in self.request_providers[..initialized]
2184 .iter()
2185 .enumerate()
2186 .rev()
2187 {
2188 let value = requests
2189 .as_slice()
2190 .get(base + index)
2191 .and_then(Option::as_ref)
2192 .ok_or_else(|| {
2193 DependencyError::internal(
2194 "invalid_dependency_slot",
2195 "compiled finalizer could not read its dependency slot",
2196 )
2197 })?;
2198 provider.finalize(value).await?;
2199 }
2200 Ok(())
2201 }
2202}
2203
2204enum ControlledInvocationError {
2205 Dependency(DependencyError),
2206 Abort(InvocationAbort),
2207}
2208
2209enum RequestDependencyValues {
2210 Inline {
2211 slots: [Option<DependencyValue>; INLINE_DEPENDENCY_SLOTS],
2212 len: usize,
2213 },
2214 Heap(Vec<Option<DependencyValue>>),
2215}
2216
2217impl RequestDependencyValues {
2218 fn new(len: usize) -> Self {
2219 if len <= INLINE_DEPENDENCY_SLOTS {
2220 Self::Inline {
2221 slots: core::array::from_fn(|_| None),
2222 len,
2223 }
2224 } else {
2225 Self::Heap(vec![None; len])
2226 }
2227 }
2228
2229 fn as_slice(&self) -> &[Option<DependencyValue>] {
2230 match self {
2231 Self::Inline { slots, len } => &slots[..*len],
2232 Self::Heap(slots) => slots,
2233 }
2234 }
2235
2236 fn set(&mut self, index: usize, value: DependencyValue) -> Result<(), DependencyError> {
2237 let slot = match self {
2238 Self::Inline { slots, len } => {
2239 slots.get_mut(..*len).and_then(|slots| slots.get_mut(index))
2240 }
2241 Self::Heap(slots) => slots.get_mut(index),
2242 }
2243 .ok_or_else(|| {
2244 DependencyError::internal(
2245 "invalid_dependency_slot",
2246 "compiled request provider produced an unknown slot",
2247 )
2248 })?;
2249 *slot = Some(value);
2250 Ok(())
2251 }
2252}
2253
2254pub struct Plugin {
2256 name: String,
2257 providers: Vec<RequestProvider>,
2258 security_schemes: Vec<SecuritySchemeDescriptor>,
2259 operations: Vec<ExecutableOperation>,
2260 plugins: Vec<Self>,
2261 hooks: PluginHooks,
2262 startup_hooks: Vec<LifecycleHook>,
2263 shutdown_hooks: Vec<LifecycleHook>,
2264 mount_prefix: Option<String>,
2265 id_namespace: Option<String>,
2266}
2267
2268impl Plugin {
2269 #[must_use]
2270 pub fn new(name: impl Into<String>) -> Self {
2271 Self {
2272 name: name.into(),
2273 providers: Vec::new(),
2274 security_schemes: Vec::new(),
2275 operations: Vec::new(),
2276 plugins: Vec::new(),
2277 hooks: PluginHooks {
2278 on_request: Vec::new(),
2279 pre_parse: Vec::new(),
2280 pre_validate: Vec::new(),
2281 pre_handler: Vec::new(),
2282 pre_serialize: Vec::new(),
2283 on_error: Vec::new(),
2284 on_response: Vec::new(),
2285 },
2286 startup_hooks: Vec::new(),
2287 shutdown_hooks: Vec::new(),
2288 mount_prefix: None,
2289 id_namespace: None,
2290 }
2291 }
2292
2293 #[must_use]
2302 pub fn mount(mut self, prefix: impl Into<String>) -> Self {
2303 self.mount_prefix = Some(prefix.into());
2304 self
2305 }
2306
2307 #[must_use]
2316 pub fn with_id_namespace(mut self, namespace: impl Into<String>) -> Self {
2317 self.id_namespace = Some(namespace.into());
2318 self
2319 }
2320
2321 #[must_use]
2322 pub fn provide(mut self, provider: impl Into<RequestProvider>) -> Self {
2323 self.providers.push(provider.into());
2324 self
2325 }
2326
2327 #[must_use]
2329 pub fn security_scheme(mut self, scheme: SecuritySchemeDescriptor) -> Self {
2330 self.security_schemes.push(scheme);
2331 self
2332 }
2333
2334 #[must_use]
2335 pub fn operation(mut self, operation: ExecutableOperation) -> Self {
2336 self.operations.push(operation);
2337 self
2338 }
2339
2340 #[must_use]
2341 pub fn routes(mut self, operations: impl IntoIterator<Item = ExecutableOperation>) -> Self {
2342 self.operations.extend(operations);
2343 self
2344 }
2345
2346 #[must_use]
2347 pub fn plugin(mut self, plugin: Self) -> Self {
2348 self.plugins.push(plugin);
2349 self
2350 }
2351
2352 #[must_use]
2354 pub fn on_request<Hook, HookFuture>(mut self, hook: Hook) -> Self
2355 where
2356 Hook: Fn(HookContext) -> HookFuture + 'static,
2357 HookFuture: Future<Output = Result<(), DependencyError>> + 'static,
2358 {
2359 self.hooks
2360 .on_request
2361 .push(Rc::new(move |context| Box::pin(hook(context))));
2362 self
2363 }
2364
2365 #[must_use]
2367 pub fn pre_parse<Hook, HookFuture>(mut self, hook: Hook) -> Self
2368 where
2369 Hook: Fn(HookContext) -> HookFuture + 'static,
2370 HookFuture: Future<Output = Result<(), DependencyError>> + 'static,
2371 {
2372 self.hooks
2373 .pre_parse
2374 .push(Rc::new(move |context| Box::pin(hook(context))));
2375 self
2376 }
2377
2378 #[must_use]
2380 pub fn pre_validate<Hook, HookFuture>(mut self, hook: Hook) -> Self
2381 where
2382 Hook: Fn(HookContext) -> HookFuture + 'static,
2383 HookFuture: Future<Output = Result<(), DependencyError>> + 'static,
2384 {
2385 self.hooks
2386 .pre_validate
2387 .push(Rc::new(move |context| Box::pin(hook(context))));
2388 self
2389 }
2390
2391 #[must_use]
2394 pub fn pre_handler<Hook, HookFuture>(mut self, hook: Hook) -> Self
2395 where
2396 Hook: Fn(HookContext) -> HookFuture + 'static,
2397 HookFuture: Future<Output = Result<(), DependencyError>> + 'static,
2398 {
2399 self.hooks
2400 .pre_handler
2401 .push(Rc::new(move |context| Box::pin(hook(context))));
2402 self
2403 }
2404
2405 #[must_use]
2408 pub fn pre_serialize<Hook, HookFuture>(mut self, hook: Hook) -> Self
2409 where
2410 Hook: Fn(HookContext) -> HookFuture + 'static,
2411 HookFuture: Future<Output = Result<(), DependencyError>> + 'static,
2412 {
2413 self.hooks
2414 .pre_serialize
2415 .push(Rc::new(move |context| Box::pin(hook(context))));
2416 self
2417 }
2418
2419 #[must_use]
2422 pub fn on_error<Hook, HookFuture>(mut self, hook: Hook) -> Self
2423 where
2424 Hook: Fn(HookContext, HookOutcome) -> HookFuture + 'static,
2425 HookFuture: Future<Output = ()> + 'static,
2426 {
2427 self.hooks.on_error.push(Rc::new(move |context, outcome| {
2428 Box::pin(hook(context, outcome))
2429 }));
2430 self
2431 }
2432
2433 #[must_use]
2435 pub fn on_response<Hook, HookFuture>(mut self, hook: Hook) -> Self
2436 where
2437 Hook: Fn(HookContext, HookOutcome) -> HookFuture + 'static,
2438 HookFuture: Future<Output = ()> + 'static,
2439 {
2440 self.hooks
2441 .on_response
2442 .push(Rc::new(move |context, outcome| {
2443 Box::pin(hook(context, outcome))
2444 }));
2445 self
2446 }
2447
2448 #[must_use]
2453 pub fn on_startup<Hook, HookFuture>(mut self, hook: Hook) -> Self
2454 where
2455 Hook: Fn() -> HookFuture + 'static,
2456 HookFuture: Future<Output = Result<(), DependencyError>> + 'static,
2457 {
2458 self.startup_hooks.push(Rc::new(move || Box::pin(hook())));
2459 self
2460 }
2461
2462 #[must_use]
2467 pub fn on_shutdown<Hook, HookFuture>(mut self, hook: Hook) -> Self
2468 where
2469 Hook: Fn() -> HookFuture + 'static,
2470 HookFuture: Future<Output = Result<(), DependencyError>> + 'static,
2471 {
2472 self.shutdown_hooks.push(Rc::new(move || Box::pin(hook())));
2473 self
2474 }
2475}
2476
2477struct TestOverrideEntry {
2478 plugin: Option<String>,
2479 provider: Provider,
2480 applied: bool,
2481}
2482
2483#[derive(Default)]
2489pub struct TestOverrides {
2490 entries: Vec<TestOverrideEntry>,
2491}
2492
2493impl TestOverrides {
2494 #[must_use]
2495 pub const fn new() -> Self {
2496 Self {
2497 entries: Vec::new(),
2498 }
2499 }
2500
2501 #[must_use]
2503 pub fn replace(mut self, provider: Provider) -> Self {
2504 self.insert(None, provider);
2505 self
2506 }
2507
2508 #[must_use]
2510 pub fn replace_in(mut self, plugin: impl Into<String>, provider: Provider) -> Self {
2511 self.insert(Some(plugin.into()), provider);
2512 self
2513 }
2514
2515 fn insert(&mut self, plugin: Option<String>, provider: Provider) {
2516 let type_id = provider.key().type_id();
2517 if let Some(existing) = self
2518 .entries
2519 .iter_mut()
2520 .find(|entry| entry.plugin == plugin && entry.provider.key().type_id() == type_id)
2521 {
2522 existing.provider = provider;
2523 existing.applied = false;
2524 } else {
2525 self.entries.push(TestOverrideEntry {
2526 plugin,
2527 provider,
2528 applied: false,
2529 });
2530 }
2531 }
2532
2533 fn apply(&mut self, plugin: &str, providers: Vec<RequestProvider>) -> Vec<RequestProvider> {
2534 let mut providers = providers
2535 .into_iter()
2536 .map(|provider| {
2537 let type_id = provider.key().type_id();
2538 let replacement = self
2539 .entries
2540 .iter()
2541 .position(|entry| {
2542 entry.plugin.as_deref() == Some(plugin)
2543 && entry.provider.key().type_id() == type_id
2544 })
2545 .or_else(|| {
2546 self.entries.iter().position(|entry| {
2547 entry.plugin.is_none() && entry.provider.key().type_id() == type_id
2548 })
2549 });
2550 replacement.map_or(provider, |index| {
2554 let entry = &mut self.entries[index];
2555 entry.applied = true;
2556 RequestProvider::from(entry.provider.clone())
2557 })
2558 })
2559 .collect::<Vec<_>>();
2560
2561 for index in 0..self.entries.len() {
2562 let should_add = {
2563 let entry = &self.entries[index];
2564 entry.plugin.as_deref() == Some(plugin)
2565 && !providers
2566 .iter()
2567 .any(|provider| provider.key().type_id() == entry.provider.key().type_id())
2568 };
2569 if !should_add {
2570 continue;
2571 }
2572 let entry = &mut self.entries[index];
2573 entry.applied = true;
2574 providers.push(RequestProvider::from(entry.provider.clone()));
2575 }
2576 providers
2577 }
2578
2579 fn validate(&self) -> Result<(), ExecutableBuildError> {
2580 let Some(entry) = self.entries.iter().find(|entry| !entry.applied) else {
2581 return Ok(());
2582 };
2583 Err(ExecutableBuildError::UnknownProviderOverride {
2584 plugin: entry.plugin.clone(),
2585 dependency: entry.provider.key().type_name(),
2586 })
2587 }
2588}
2589
2590#[derive(Debug)]
2592pub enum ExecutableBuildError {
2593 Definition(blazingly_core::BuildError),
2594 InvalidPluginName {
2595 plugin: String,
2596 },
2597 InvalidMountPrefix {
2598 plugin: String,
2599 prefix: String,
2600 },
2601 InvalidIdNamespace {
2602 plugin: String,
2603 namespace: String,
2604 },
2605 DuplicateProvider {
2606 plugin: String,
2607 dependency: &'static str,
2608 },
2609 MissingProvider {
2610 plugin: String,
2611 consumer: String,
2612 dependency: &'static str,
2613 },
2614 ProviderCycle {
2615 plugin: String,
2616 dependencies: Vec<&'static str>,
2617 },
2618 InvalidLifetime {
2619 plugin: String,
2620 singleton: &'static str,
2621 shorter_lived_dependency: &'static str,
2622 },
2623 ProviderCompilation {
2624 plugin: String,
2625 dependency: &'static str,
2626 message: String,
2627 },
2628 SingletonProviderFailed {
2629 plugin: String,
2630 dependency: &'static str,
2631 message: String,
2632 },
2633 UnknownProviderOverride {
2634 plugin: Option<String>,
2635 dependency: &'static str,
2636 },
2637 SingletonRequestInputs {
2638 plugin: String,
2639 dependency: &'static str,
2640 },
2641 ConflictingProviderInput {
2642 operation: String,
2643 name: String,
2644 source: &'static str,
2645 },
2646}
2647
2648impl fmt::Display for ExecutableBuildError {
2649 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2650 match self {
2651 Self::Definition(error) => error.fmt(formatter),
2652 Self::InvalidPluginName { plugin } => {
2653 write!(formatter, "invalid plugin name `{plugin}`")
2654 }
2655 Self::InvalidMountPrefix { plugin, prefix } => write!(
2656 formatter,
2657 "plugin `{plugin}` mount prefix {prefix:?} must start with '/', \
2658 not end with '/', and declare no path parameters"
2659 ),
2660 Self::InvalidIdNamespace { plugin, namespace } => write!(
2661 formatter,
2662 "plugin `{plugin}` id namespace {namespace:?} must contain only \
2663 ASCII letters, digits, '-' or '_'"
2664 ),
2665 Self::DuplicateProvider { plugin, dependency } => {
2666 write!(
2667 formatter,
2668 "plugin `{plugin}` registers dependency `{dependency}` more than once"
2669 )
2670 }
2671 Self::MissingProvider {
2672 plugin,
2673 consumer,
2674 dependency,
2675 } => write!(
2676 formatter,
2677 "plugin `{plugin}` cannot resolve dependency `{dependency}` required by `{consumer}`"
2678 ),
2679 Self::ProviderCycle {
2680 plugin,
2681 dependencies,
2682 } => write!(
2683 formatter,
2684 "plugin `{plugin}` contains a dependency cycle: {}",
2685 dependencies.join(" -> ")
2686 ),
2687 Self::InvalidLifetime {
2688 plugin,
2689 singleton,
2690 shorter_lived_dependency,
2691 } => write!(
2692 formatter,
2693 "singleton `{singleton}` in plugin `{plugin}` cannot depend on shorter-lived `{shorter_lived_dependency}`"
2694 ),
2695 Self::ProviderCompilation {
2696 plugin,
2697 dependency,
2698 message,
2699 } => write!(
2700 formatter,
2701 "provider `{dependency}` in plugin `{plugin}` could not be compiled: {message}"
2702 ),
2703 Self::SingletonProviderFailed {
2704 plugin,
2705 dependency,
2706 message,
2707 } => write!(
2708 formatter,
2709 "singleton provider `{dependency}` in plugin `{plugin}` failed: {message}"
2710 ),
2711 Self::UnknownProviderOverride { plugin, dependency } => match plugin {
2712 Some(plugin) => write!(
2713 formatter,
2714 "test override for `{dependency}` targets unknown plugin scope `{plugin}`"
2715 ),
2716 None => write!(
2717 formatter,
2718 "test override for `{dependency}` did not match a registered provider"
2719 ),
2720 },
2721 Self::SingletonRequestInputs { plugin, dependency } => write!(
2722 formatter,
2723 "singleton provider `{dependency}` in plugin `{plugin}` cannot declare request \
2724 inputs; a singleton is built once at compile time, before any request exists"
2725 ),
2726 Self::ConflictingProviderInput {
2727 operation,
2728 name,
2729 source,
2730 } => write!(
2731 formatter,
2732 "operation `{operation}` receives {source} input `{name}` at two different \
2733 types; one wire input cannot decode as both"
2734 ),
2735 }
2736 }
2737}
2738
2739impl std::error::Error for ExecutableBuildError {
2740 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2741 match self {
2742 Self::Definition(error) => Some(error),
2743 _ => None,
2744 }
2745 }
2746}
2747
2748impl From<blazingly_core::BuildError> for ExecutableBuildError {
2749 fn from(error: blazingly_core::BuildError) -> Self {
2750 Self::Definition(error)
2751 }
2752}
2753
2754struct ProviderRegistration {
2755 provider: RequestProvider,
2756 visible: HashMap<core::any::TypeId, usize>,
2757 plugin: String,
2758}
2759
2760struct ScopedOperation {
2761 operation: ExecutableOperation,
2762 visible: HashMap<core::any::TypeId, usize>,
2763 plugin: String,
2764 hooks: HookScope,
2765}
2766
2767pub struct ExecutableApp {
2769 definition: AppDefinition,
2770 operations: Vec<ExecutableOperation>,
2771 by_id: BTreeMap<OperationId, usize>,
2772 startup_hooks: Vec<LifecycleHook>,
2773 shutdown_hooks: Vec<LifecycleHook>,
2774}
2775
2776impl ExecutableApp {
2777 pub fn new(
2784 operations: impl IntoIterator<Item = ExecutableOperation>,
2785 ) -> Result<Self, ExecutableBuildError> {
2786 Self::from_plugin(Plugin::new("app").routes(operations))
2787 }
2788
2789 pub fn with_security_schemes(
2796 operations: impl IntoIterator<Item = ExecutableOperation>,
2797 schemes: impl IntoIterator<Item = SecuritySchemeDescriptor>,
2798 ) -> Result<Self, ExecutableBuildError> {
2799 let plugin = schemes
2800 .into_iter()
2801 .fold(Plugin::new("app"), Plugin::security_scheme)
2802 .routes(operations);
2803 Self::from_plugin(plugin)
2804 }
2805
2806 pub fn from_plugin(plugin: Plugin) -> Result<Self, ExecutableBuildError> {
2814 Self::from_plugin_with_overrides(plugin, TestOverrides::new())
2815 }
2816
2817 pub fn from_plugin_with_overrides(
2828 plugin: Plugin,
2829 mut overrides: TestOverrides,
2830 ) -> Result<Self, ExecutableBuildError> {
2831 let mut registrations = Vec::new();
2832 let mut scoped_operations = Vec::new();
2833 let mut security_schemes = Vec::new();
2834 let mut startup_hooks = Vec::new();
2835 let mut shutdown_hooks = Vec::new();
2836 let mut collector = PluginCollector {
2837 registrations: &mut registrations,
2838 operations: &mut scoped_operations,
2839 security_schemes: &mut security_schemes,
2840 startup_hooks: &mut startup_hooks,
2841 shutdown_hooks: &mut shutdown_hooks,
2842 overrides: &mut overrides,
2843 };
2844 collect_plugin(
2845 plugin,
2846 &HashMap::new(),
2847 &HookScope::default(),
2848 "",
2849 &MountPoint::default(),
2850 &mut collector,
2851 )?;
2852 overrides.validate()?;
2853 validate_provider_graph(®istrations)?;
2854 let (singletons, singleton_slots) = compile_singletons(®istrations)?;
2855 let singletons = Rc::new(singletons);
2856 let mut operations = Vec::with_capacity(scoped_operations.len());
2857 for mut scoped in scoped_operations {
2858 let input_plan = plan_operation_inputs(&scoped, ®istrations)?;
2863 scoped
2864 .operation
2865 .descriptor
2866 .contract
2867 .inputs
2868 .extend(input_plan.folded.iter().cloned());
2869 scoped.operation.hooks =
2870 CompiledHooks::compile(&scoped.operation.descriptor, scoped.hooks.clone());
2871 scoped.operation.dependency_plan = Some(compile_operation_dependencies(
2872 &scoped,
2873 ®istrations,
2874 &singleton_slots,
2875 Rc::clone(&singletons),
2876 &input_plan,
2877 )?);
2878 operations.push(scoped.operation);
2879 }
2880 let definition = security_schemes
2881 .into_iter()
2882 .fold(App::new(), App::security_scheme)
2883 .routes(
2884 operations
2885 .iter()
2886 .map(|operation| operation.descriptor.clone()),
2887 )
2888 .build()?;
2889 let by_id = operations
2890 .iter()
2891 .enumerate()
2892 .map(|(index, operation)| (operation.descriptor.contract.id.clone(), index))
2893 .collect();
2894
2895 Ok(Self {
2896 definition,
2897 operations,
2898 by_id,
2899 startup_hooks,
2900 shutdown_hooks,
2901 })
2902 }
2903
2904 #[must_use]
2905 pub const fn definition(&self) -> &AppDefinition {
2906 &self.definition
2907 }
2908
2909 #[must_use]
2910 pub fn operation(&self, id: &OperationId) -> Option<&ExecutableOperation> {
2911 self.by_id
2912 .get(id)
2913 .and_then(|index| self.operations.get(*index))
2914 }
2915
2916 #[must_use]
2917 pub fn operation_index(&self, id: &OperationId) -> Option<usize> {
2918 self.by_id.get(id).copied()
2919 }
2920
2921 #[must_use]
2922 pub fn operation_at(&self, index: usize) -> Option<&ExecutableOperation> {
2923 self.operations.get(index)
2924 }
2925
2926 #[must_use]
2927 pub fn operation_for_mcp_tool(&self, name: &str) -> Option<&ExecutableOperation> {
2928 self.operations.iter().find(|operation| {
2929 operation
2930 .descriptor
2931 .contract
2932 .mcp
2933 .as_ref()
2934 .is_some_and(|tool| tool.name == name)
2935 })
2936 }
2937
2938 pub async fn invoke(&self, id: &OperationId, input: Value) -> ExecutionOutcome {
2939 let Some(operation) = self.operation(id) else {
2940 return ExecutionOutcome::Rejected {
2941 status: 404,
2942 code: "operation_not_found".to_owned(),
2943 message: "operation not found".to_owned(),
2944 details: None,
2945 };
2946 };
2947 operation.invoke(input).await
2948 }
2949
2950 pub async fn invoke_controlled(
2951 &self,
2952 id: &OperationId,
2953 input: Value,
2954 control: InvocationControl,
2955 ) -> ExecutionOutcome {
2956 let Some(operation) = self.operation(id) else {
2957 return ExecutionOutcome::Rejected {
2958 status: 404,
2959 code: "operation_not_found".to_owned(),
2960 message: "operation not found".to_owned(),
2961 details: None,
2962 };
2963 };
2964 operation.invoke_controlled(input, control).await
2965 }
2966
2967 pub async fn startup(&self) -> Result<(), DependencyError> {
2974 for hook in &self.startup_hooks {
2975 hook().await?;
2976 }
2977 Ok(())
2978 }
2979
2980 pub async fn shutdown(&self) -> Result<(), DependencyError> {
2989 let mut first_error = None;
2990 for hook in self.shutdown_hooks.iter().rev() {
2991 if let Err(error) = hook().await
2992 && first_error.is_none()
2993 {
2994 first_error = Some(error);
2995 }
2996 }
2997 first_error.map_or(Ok(()), Err)
2998 }
2999}
3000
3001struct PluginCollector<'collector> {
3002 registrations: &'collector mut Vec<ProviderRegistration>,
3003 operations: &'collector mut Vec<ScopedOperation>,
3004 security_schemes: &'collector mut Vec<SecuritySchemeDescriptor>,
3005 startup_hooks: &'collector mut Vec<LifecycleHook>,
3006 shutdown_hooks: &'collector mut Vec<LifecycleHook>,
3007 overrides: &'collector mut TestOverrides,
3008}
3009
3010fn collect_plugin(
3011 plugin: Plugin,
3012 inherited: &HashMap<core::any::TypeId, usize>,
3013 inherited_hooks: &HookScope,
3014 parent_path: &str,
3015 parent_mount: &MountPoint,
3016 collector: &mut PluginCollector<'_>,
3017) -> Result<(), ExecutableBuildError> {
3018 let Plugin {
3019 name,
3020 providers,
3021 security_schemes: plugin_security_schemes,
3022 operations: plugin_operations,
3023 plugins,
3024 hooks: plugin_hooks,
3025 startup_hooks: plugin_startup_hooks,
3026 shutdown_hooks: plugin_shutdown_hooks,
3027 mount_prefix,
3028 id_namespace,
3029 } = plugin;
3030 let path = if parent_path.is_empty() {
3031 name.clone()
3032 } else {
3033 format!("{parent_path}/{name}")
3034 };
3035 if !valid_plugin_name(&name) {
3036 return Err(ExecutableBuildError::InvalidPluginName { plugin: path });
3037 }
3038 let mount = parent_mount.nested(&path, mount_prefix, id_namespace)?;
3039
3040 let mut visible = inherited.clone();
3041 collector.security_schemes.extend(plugin_security_schemes);
3042 collector.startup_hooks.extend(plugin_startup_hooks);
3043 collector.shutdown_hooks.extend(plugin_shutdown_hooks);
3044 let hooks = inherited_hooks.inherited(&plugin_hooks);
3045 let providers = collector.overrides.apply(&path, providers);
3046 let mut local = HashSet::new();
3047 let mut registration_ids = Vec::with_capacity(providers.len());
3048 for provider in providers {
3049 let key = provider.key();
3050 if !local.insert(key.type_id()) {
3051 return Err(ExecutableBuildError::DuplicateProvider {
3052 plugin: path,
3053 dependency: key.type_name(),
3054 });
3055 }
3056 if provider.lifetime() == DependencyLifetime::Singleton && !provider.inputs.is_empty() {
3057 return Err(ExecutableBuildError::SingletonRequestInputs {
3058 plugin: path,
3059 dependency: key.type_name(),
3060 });
3061 }
3062 let id = collector.registrations.len();
3063 collector.registrations.push(ProviderRegistration {
3064 provider,
3065 visible: HashMap::new(),
3066 plugin: path.clone(),
3067 });
3068 visible.insert(key.type_id(), id);
3069 registration_ids.push(id);
3070 }
3071 for id in registration_ids {
3072 collector.registrations[id].visible.clone_from(&visible);
3073 }
3074 for mut operation in plugin_operations {
3075 mount.apply(&mut operation.descriptor, &path)?;
3076 collector.operations.push(ScopedOperation {
3077 operation,
3078 visible: visible.clone(),
3079 plugin: path.clone(),
3080 hooks: hooks.clone(),
3081 });
3082 }
3083 for child in plugins {
3084 collect_plugin(child, &visible, &hooks, &path, &mount, collector)?;
3085 }
3086 Ok(())
3087}
3088
3089#[derive(Clone, Default)]
3097struct MountPoint {
3098 prefix: String,
3099 namespace: String,
3100}
3101
3102impl MountPoint {
3103 fn nested(
3104 &self,
3105 plugin: &str,
3106 prefix: Option<String>,
3107 namespace: Option<String>,
3108 ) -> Result<Self, ExecutableBuildError> {
3109 let mut nested = self.clone();
3110 if let Some(prefix) = prefix {
3111 if !valid_mount_prefix(&prefix) {
3112 return Err(ExecutableBuildError::InvalidMountPrefix {
3113 plugin: plugin.to_owned(),
3114 prefix,
3115 });
3116 }
3117 nested.prefix.push_str(&prefix);
3118 }
3119 if let Some(namespace) = namespace {
3120 if !valid_plugin_name(&namespace) {
3121 return Err(ExecutableBuildError::InvalidIdNamespace {
3122 plugin: plugin.to_owned(),
3123 namespace,
3124 });
3125 }
3126 if !nested.namespace.is_empty() {
3127 nested.namespace.push('.');
3128 }
3129 nested.namespace.push_str(&namespace);
3130 }
3131 Ok(nested)
3132 }
3133
3134 fn apply(
3135 &self,
3136 descriptor: &mut OperationDescriptor,
3137 plugin: &str,
3138 ) -> Result<(), ExecutableBuildError> {
3139 if !self.prefix.is_empty() {
3140 let declared = &mut descriptor.http.path;
3141 *declared = if declared == "/" {
3142 self.prefix.clone()
3143 } else {
3144 format!("{}{declared}", self.prefix)
3145 };
3146 }
3147 if !self.namespace.is_empty() {
3148 let namespaced = format!("{}.{}", self.namespace, descriptor.contract.id.as_str());
3149 descriptor.contract.id = OperationId::new(namespaced).map_err(|error| {
3150 ExecutableBuildError::InvalidIdNamespace {
3151 plugin: plugin.to_owned(),
3152 namespace: error.value().to_owned(),
3153 }
3154 })?;
3155 if let Some(tool) = &mut descriptor.contract.mcp {
3158 tool.name = format!("{}_{}", self.namespace.replace(['.', '-'], "_"), tool.name);
3159 }
3160 }
3161 Ok(())
3162 }
3163}
3164
3165fn valid_mount_prefix(prefix: &str) -> bool {
3166 prefix.starts_with('/')
3167 && !prefix.ends_with('/')
3168 && !prefix.contains(['{', '}'])
3169 && prefix.split('/').skip(1).all(|segment| !segment.is_empty())
3170}
3171
3172fn valid_plugin_name(name: &str) -> bool {
3173 !name.is_empty()
3174 && name
3175 .bytes()
3176 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))
3177}
3178
3179fn validate_provider_graph(
3180 registrations: &[ProviderRegistration],
3181) -> Result<(), ExecutableBuildError> {
3182 let mut states = vec![0_u8; registrations.len()];
3183 let mut stack = Vec::new();
3184 for id in 0..registrations.len() {
3185 validate_provider(id, registrations, &mut states, &mut stack)?;
3186 }
3187 Ok(())
3188}
3189
3190fn validate_provider(
3191 id: usize,
3192 registrations: &[ProviderRegistration],
3193 states: &mut [u8],
3194 stack: &mut Vec<usize>,
3195) -> Result<(), ExecutableBuildError> {
3196 if states[id] == 2 {
3197 return Ok(());
3198 }
3199 if states[id] == 1 {
3200 let cycle_start = stack
3201 .iter()
3202 .position(|candidate| *candidate == id)
3203 .unwrap_or(0);
3204 let mut dependencies = stack[cycle_start..]
3205 .iter()
3206 .map(|candidate| registrations[*candidate].provider.key().type_name())
3207 .collect::<Vec<_>>();
3208 dependencies.push(registrations[id].provider.key().type_name());
3209 return Err(ExecutableBuildError::ProviderCycle {
3210 plugin: registrations[id].plugin.clone(),
3211 dependencies,
3212 });
3213 }
3214
3215 states[id] = 1;
3216 stack.push(id);
3217 let registration = ®istrations[id];
3218 for dependency in registration.provider.dependencies() {
3219 let Some(dependency_id) = registration.visible.get(&dependency.type_id()).copied() else {
3220 return Err(ExecutableBuildError::MissingProvider {
3221 plugin: registration.plugin.clone(),
3222 consumer: registration.provider.key().type_name().to_owned(),
3223 dependency: dependency.type_name(),
3224 });
3225 };
3226 if registration.provider.lifetime() == DependencyLifetime::Singleton
3227 && registrations[dependency_id].provider.lifetime() != DependencyLifetime::Singleton
3228 {
3229 return Err(ExecutableBuildError::InvalidLifetime {
3230 plugin: registration.plugin.clone(),
3231 singleton: registration.provider.key().type_name(),
3232 shorter_lived_dependency: dependency.type_name(),
3233 });
3234 }
3235 validate_provider(dependency_id, registrations, states, stack)?;
3236 }
3237 stack.pop();
3238 states[id] = 2;
3239 Ok(())
3240}
3241
3242fn compile_singletons(
3243 registrations: &[ProviderRegistration],
3244) -> Result<SingletonCompilation, ExecutableBuildError> {
3245 let mut singleton_slots = vec![None; registrations.len()];
3246 let mut singleton_count = 0;
3247 for (id, registration) in registrations.iter().enumerate() {
3248 if registration.provider.lifetime() == DependencyLifetime::Singleton {
3249 singleton_slots[id] = Some(singleton_count);
3250 singleton_count += 1;
3251 }
3252 }
3253 let mut values = vec![None; singleton_count];
3254 let mut built = vec![false; registrations.len()];
3255 for id in 0..registrations.len() {
3256 if singleton_slots[id].is_some() {
3257 compile_singleton(id, registrations, &singleton_slots, &mut values, &mut built)?;
3258 }
3259 }
3260 Ok((values, singleton_slots))
3261}
3262
3263fn compile_singleton(
3264 id: usize,
3265 registrations: &[ProviderRegistration],
3266 singleton_slots: &[Option<usize>],
3267 values: &mut [Option<DependencyValue>],
3268 built: &mut [bool],
3269) -> Result<(), ExecutableBuildError> {
3270 if built[id] {
3271 return Ok(());
3272 }
3273 let registration = ®istrations[id];
3274 let mut dependency_slots = Vec::with_capacity(registration.provider.dependencies().len());
3275 for dependency in registration.provider.dependencies() {
3276 let dependency_id = registration.visible[&dependency.type_id()];
3277 compile_singleton(dependency_id, registrations, singleton_slots, values, built)?;
3278 let Some(slot) = singleton_slots[dependency_id] else {
3279 return Err(ExecutableBuildError::ProviderCompilation {
3280 plugin: registration.plugin.clone(),
3281 dependency: registration.provider.key().type_name(),
3282 message: "validated singleton dependency has no compiled slot".to_owned(),
3283 });
3284 };
3285 dependency_slots.push(DependencySlot::Singleton(slot));
3286 }
3287 let compiled = registration
3288 .provider
3289 .compile(&dependency_slots)
3290 .map_err(|error| ExecutableBuildError::ProviderCompilation {
3291 plugin: registration.plugin.clone(),
3292 dependency: registration.provider.key().type_name(),
3293 message: error.to_string(),
3294 })?;
3295 let value = compiled.run_sync(values, &[]).map_err(|error| {
3296 ExecutableBuildError::SingletonProviderFailed {
3297 plugin: registration.plugin.clone(),
3298 dependency: registration.provider.key().type_name(),
3299 message: error.to_string(),
3300 }
3301 })?;
3302 let Some(slot) = singleton_slots[id] else {
3303 return Err(ExecutableBuildError::ProviderCompilation {
3304 plugin: registration.plugin.clone(),
3305 dependency: registration.provider.key().type_name(),
3306 message: "singleton provider has no compiled slot".to_owned(),
3307 });
3308 };
3309 values[slot] = Some(value);
3310 built[id] = true;
3311 Ok(())
3312}
3313
3314struct OperationInputPlan {
3325 decoders: Vec<ProviderInputDecoder>,
3326 folded: Vec<InputDescriptor>,
3327 provider_inputs: HashMap<usize, Vec<usize>>,
3328}
3329
3330fn plan_operation_inputs(
3331 scoped: &ScopedOperation,
3332 registrations: &[ProviderRegistration],
3333) -> Result<OperationInputPlan, ExecutableBuildError> {
3334 let mut plan = OperationInputPlan {
3335 decoders: Vec::new(),
3336 folded: Vec::new(),
3337 provider_inputs: HashMap::new(),
3338 };
3339 let mut slots: HashMap<(&'static str, String), (core::any::TypeId, usize)> = HashMap::new();
3340 let mut ordered: Vec<InputDescriptor> = Vec::new();
3341 let mut visited = vec![false; registrations.len()];
3342 let mut pending = Vec::new();
3343 for request in &scoped.operation.dependency_requests {
3344 if let Some(id) = scoped.visible.get(&request.key().type_id()).copied() {
3345 pending.push(id);
3346 }
3347 }
3348 while let Some(id) = pending.pop() {
3349 if visited[id] {
3350 continue;
3351 }
3352 visited[id] = true;
3353 let registration = ®istrations[id];
3354 if registration.provider.lifetime() == DependencyLifetime::Singleton {
3357 continue;
3358 }
3359 let mut positions = Vec::with_capacity(registration.provider.inputs.len());
3360 for input in ®istration.provider.inputs {
3361 let key = (
3362 source_name(input.descriptor.source),
3363 input.descriptor.name.clone(),
3364 );
3365 let slot = if let Some((existing_type, slot)) = slots.get(&key) {
3366 if *existing_type != input.value_type {
3367 return Err(ExecutableBuildError::ConflictingProviderInput {
3368 operation: scoped.operation.descriptor.contract.id.as_str().to_owned(),
3369 name: input.descriptor.name.clone(),
3370 source: source_name(input.descriptor.source),
3371 });
3372 }
3373 *slot
3374 } else {
3375 let slot = plan.decoders.len();
3376 slots.insert(key, (input.value_type, slot));
3377 plan.decoders.push(Rc::clone(&input.decode));
3378 ordered.push(input.descriptor.clone());
3379 slot
3380 };
3381 positions.push(slot);
3382 }
3383 if !positions.is_empty() {
3384 plan.provider_inputs.insert(id, positions);
3385 }
3386 for dependency in registration.provider.dependencies() {
3387 if let Some(dependency_id) = registration.visible.get(&dependency.type_id()).copied() {
3388 pending.push(dependency_id);
3389 }
3390 }
3391 }
3392 plan.folded = ordered
3393 .into_iter()
3394 .filter(|descriptor| {
3395 !scoped
3396 .operation
3397 .descriptor
3398 .contract
3399 .inputs
3400 .iter()
3401 .any(|declared| {
3402 declared.source == descriptor.source && declared.name == descriptor.name
3403 })
3404 })
3405 .collect();
3406 Ok(plan)
3407}
3408
3409fn compile_operation_dependencies(
3410 scoped: &ScopedOperation,
3411 registrations: &[ProviderRegistration],
3412 singleton_slots: &[Option<usize>],
3413 singletons: Rc<Vec<Option<DependencyValue>>>,
3414 plan: &OperationInputPlan,
3415) -> Result<CompiledOperationDependencies, ExecutableBuildError> {
3416 let mut request_slots = HashMap::new();
3417 let mut request_providers = Vec::new();
3418 let mut handler_slots = Vec::with_capacity(scoped.operation.dependency_requests.len());
3419 for request in &scoped.operation.dependency_requests {
3420 let key = request.key();
3421 let Some(provider_id) = scoped.visible.get(&key.type_id()).copied() else {
3422 return Err(ExecutableBuildError::MissingProvider {
3423 plugin: scoped.plugin.clone(),
3424 consumer: scoped.operation.descriptor.contract.id.as_str().to_owned(),
3425 dependency: key.type_name(),
3426 });
3427 };
3428 handler_slots.push(compile_request_provider(
3429 provider_id,
3430 registrations,
3431 singleton_slots,
3432 &mut request_slots,
3433 &mut request_providers,
3434 plan,
3435 )?);
3436 }
3437 Ok(CompiledOperationDependencies {
3438 singletons,
3439 request_providers,
3440 handler_slots,
3441 input_decoders: plan.decoders.clone(),
3442 sync_chain: Cell::new(CHAIN_UNKNOWN),
3443 })
3444}
3445
3446fn compile_request_provider(
3447 id: usize,
3448 registrations: &[ProviderRegistration],
3449 singleton_slots: &[Option<usize>],
3450 request_slots: &mut HashMap<usize, usize>,
3451 request_providers: &mut Vec<CompiledProvider>,
3452 plan: &OperationInputPlan,
3453) -> Result<DependencySlot, ExecutableBuildError> {
3454 let registration = ®istrations[id];
3455 if registration.provider.lifetime() == DependencyLifetime::Singleton {
3456 let Some(slot) = singleton_slots[id] else {
3457 return Err(ExecutableBuildError::ProviderCompilation {
3458 plugin: registration.plugin.clone(),
3459 dependency: registration.provider.key().type_name(),
3460 message: "singleton provider has no compiled slot".to_owned(),
3461 });
3462 };
3463 return Ok(DependencySlot::Singleton(slot));
3464 }
3465 if registration.provider.lifetime() == DependencyLifetime::Request
3466 && let Some(slot) = request_slots.get(&id)
3467 {
3468 return Ok(DependencySlot::Request(*slot));
3469 }
3470
3471 let mut dependency_slots = Vec::with_capacity(registration.provider.dependencies().len());
3472 for dependency in registration.provider.dependencies() {
3473 let dependency_id = registration.visible[&dependency.type_id()];
3474 dependency_slots.push(compile_request_provider(
3475 dependency_id,
3476 registrations,
3477 singleton_slots,
3478 request_slots,
3479 request_providers,
3480 plan,
3481 )?);
3482 }
3483 if let Some(positions) = plan.provider_inputs.get(&id) {
3486 for position in positions {
3487 dependency_slots.push(DependencySlot::Request(*position));
3488 }
3489 }
3490 let provider = registration
3491 .provider
3492 .compile(&dependency_slots)
3493 .map_err(|error| ExecutableBuildError::ProviderCompilation {
3494 plugin: registration.plugin.clone(),
3495 dependency: registration.provider.key().type_name(),
3496 message: error.to_string(),
3497 })?;
3498 let slot = plan.decoders.len() + request_providers.len();
3499 request_providers.push(provider);
3500 if registration.provider.lifetime() == DependencyLifetime::Request {
3501 request_slots.insert(id, slot);
3502 }
3503 Ok(DependencySlot::Request(slot))
3504}
3505
3506fn resolve_dependency<T: 'static>(
3507 slot: DependencySlot,
3508 singletons: &[Option<DependencyValue>],
3509 requests: &[Option<DependencyValue>],
3510) -> Result<Depends<T>, DependencyError> {
3511 let value = match slot {
3512 DependencySlot::Singleton(index) => singletons.get(index),
3513 DependencySlot::Request(index) => requests.get(index),
3514 }
3515 .and_then(Option::as_ref)
3516 .ok_or_else(|| {
3517 DependencyError::internal(
3518 "invalid_dependency_slot",
3519 "compiled handler dependency slot was not initialized",
3520 )
3521 })?;
3522 Rc::clone(value)
3523 .downcast::<T>()
3524 .map(Depends::from_rc)
3525 .map_err(|_| {
3526 DependencyError::internal(
3527 "dependency_type_mismatch",
3528 "compiled handler dependency slot contained an unexpected type",
3529 )
3530 })
3531}
3532
3533#[doc(hidden)]
3534#[must_use]
3535pub fn dependency_error_outcome(error: DependencyError) -> ExecutionOutcome {
3536 match error {
3537 DependencyError::Rejected(failure) => ExecutionOutcome::DomainError(failure),
3538 DependencyError::Internal { code, message } => internal_dependency_error(code, message),
3539 }
3540}
3541
3542fn internal_dependency_error(
3543 code: impl Into<String>,
3544 message: impl Into<String>,
3545) -> ExecutionOutcome {
3546 ExecutionOutcome::InternalError {
3547 code: code.into(),
3548 message: message.into(),
3549 }
3550}
3551
3552fn serialize_success<T: Serialize>(status: u16, value: T) -> ExecutionOutcome {
3561 let mut body = Vec::with_capacity(blazingly_core::response_size_hint::<T>());
3562 match blazingly_json::to_writer(&mut body, &value) {
3563 Ok(()) => {
3564 blazingly_core::record_response_size::<T>(body.len());
3565 ExecutionOutcome::Success {
3566 status,
3567 headers: Vec::new(),
3568 body: Some(body),
3569 background: Vec::new(),
3570 }
3571 }
3572 Err(_) => ExecutionOutcome::InternalError {
3573 code: "serialization_failed".to_owned(),
3574 message: "operation response could not be serialized".to_owned(),
3575 },
3576 }
3577}
3578
3579fn internal_build_error(error: ResponseBuildError) -> ExecutionOutcome {
3580 ExecutionOutcome::InternalError {
3581 code: error.code,
3582 message: error.message,
3583 }
3584}
3585
3586fn valid_response_header(header: &ResponseHeader) -> bool {
3587 !header.name.is_empty()
3588 && header.name.bytes().all(is_header_name_byte)
3589 && header
3590 .value
3591 .bytes()
3592 .all(|byte| byte == b'\t' || (byte >= b' ' && byte != 127))
3593}
3594
3595const fn is_header_name_byte(byte: u8) -> bool {
3596 byte.is_ascii_alphanumeric()
3597 || matches!(
3598 byte,
3599 b'!' | b'#'
3600 | b'$'
3601 | b'%'
3602 | b'&'
3603 | b'\''
3604 | b'*'
3605 | b'+'
3606 | b'-'
3607 | b'.'
3608 | b'^'
3609 | b'_'
3610 | b'`'
3611 | b'|'
3612 | b'~'
3613 )
3614}
3615
3616struct MultipartPart<'body> {
3617 name: String,
3618 file_name: Option<String>,
3619 content_type: Option<String>,
3620 bytes: &'body [u8],
3621}
3622
3623impl MultipartPart<'_> {
3624 fn to_upload(&self) -> UploadFile {
3625 UploadFile {
3626 field_name: self.name.clone(),
3627 file_name: self.file_name.clone(),
3628 content_type: self.content_type.clone(),
3629 bytes: self.bytes.to_vec(),
3630 }
3631 }
3632
3633 fn into_upload(self) -> UploadFile {
3634 UploadFile {
3635 field_name: self.name,
3636 file_name: self.file_name,
3637 content_type: self.content_type,
3638 bytes: self.bytes.to_vec(),
3639 }
3640 }
3641}
3642
3643fn parse_multipart_request(
3644 request: &dyn HttpRequestParts,
3645) -> Result<Vec<MultipartPart<'_>>, InputRejection> {
3646 let content_type = request
3647 .value(InputSource::Header, "content-type", 0)
3648 .ok_or_else(|| multipart_rejection("missing multipart Content-Type header"))?;
3649 let boundary = multipart_boundary(&content_type)
3650 .ok_or_else(|| multipart_rejection("multipart boundary is missing or invalid"))?;
3651 parse_multipart(request.body(), &boundary)
3652}
3653
3654fn parse_multipart<'body>(
3655 body: &'body [u8],
3656 boundary: &str,
3657) -> Result<Vec<MultipartPart<'body>>, InputRejection> {
3658 let delimiter = format!("--{boundary}").into_bytes();
3659 if !body.starts_with(&delimiter) {
3660 return Err(multipart_rejection(
3661 "multipart body does not start with its declared boundary",
3662 ));
3663 }
3664 let mut position = delimiter.len();
3665 if body.get(position..position + 2) == Some(b"--") {
3666 return Ok(Vec::new());
3667 }
3668 if body.get(position..position + 2) != Some(b"\r\n") {
3669 return Err(multipart_rejection("multipart boundary is malformed"));
3670 }
3671 position += 2;
3672
3673 let mut parts = Vec::new();
3674 loop {
3675 let header_end = find_bytes(body, b"\r\n\r\n", position)
3676 .ok_or_else(|| multipart_rejection("multipart part headers are incomplete"))?;
3677 if header_end - position > MAX_MULTIPART_HEADER_BYTES {
3678 return Err(multipart_rejection(
3679 "multipart part headers exceed the configured limit",
3680 ));
3681 }
3682 let headers = std::str::from_utf8(&body[position..header_end])
3683 .map_err(|_| multipart_rejection("multipart part headers are not valid UTF-8"))?;
3684 let headers = multipart_part_headers(headers).map_err(multipart_rejection)?;
3685 let data_start = header_end + 4;
3686 let boundary_start = find_multipart_boundary(body, &delimiter, data_start)
3687 .ok_or_else(|| multipart_rejection("multipart part has no closing boundary"))?;
3688 parts.push(MultipartPart {
3689 name: headers.name,
3690 file_name: headers.file_name,
3691 content_type: headers.content_type,
3692 bytes: &body[data_start..boundary_start],
3693 });
3694 if parts.len() > MAX_MULTIPART_PARTS {
3695 return Err(multipart_rejection(
3696 "multipart body contains too many parts",
3697 ));
3698 }
3699
3700 position = boundary_start + 2 + delimiter.len();
3701 if body.get(position..position + 2) == Some(b"--") {
3702 return Ok(parts);
3703 }
3704 if body.get(position..position + 2) != Some(b"\r\n") {
3705 return Err(multipart_rejection("multipart boundary is malformed"));
3706 }
3707 position += 2;
3708 }
3709}
3710
3711fn find_multipart_boundary(body: &[u8], delimiter: &[u8], from: usize) -> Option<usize> {
3712 let mut position = from;
3713 while let Some(found) = find_bytes(body, b"\r\n--", position) {
3714 let delimiter_start = found + 2;
3715 if body.get(delimiter_start..delimiter_start + delimiter.len()) == Some(delimiter) {
3716 let suffix = delimiter_start + delimiter.len();
3717 if matches!(body.get(suffix..suffix + 2), Some(b"\r\n" | b"--")) {
3718 return Some(found);
3719 }
3720 }
3721 position = found + 2;
3722 }
3723 None
3724}
3725
3726fn multipart_argument_value(
3727 parts: &[MultipartPart<'_>],
3728 name: &str,
3729 required: bool,
3730 descriptor: &TypeDescriptor,
3731 slots: &UploadSlots,
3732) -> Result<Value, InputRejection> {
3733 if let Some(model) = &descriptor.model {
3734 let mut properties = blazingly_json::Map::new();
3735 for field in &model.fields {
3736 let matching = parts
3737 .iter()
3738 .filter(|part| part.name == field.name)
3739 .collect::<Vec<_>>();
3740 if let Some(value) = multipart_parts_value(&matching, &field.ty, slots)? {
3741 properties.insert(field.name.clone(), value);
3742 }
3743 }
3744 if properties.is_empty() && !required {
3745 return Ok(Value::Null);
3746 }
3747 return Ok(Value::Object(properties));
3748 }
3749
3750 let matching = parts
3751 .iter()
3752 .filter(|part| part.name == name)
3753 .collect::<Vec<_>>();
3754 multipart_parts_value(&matching, descriptor, slots)?.map_or_else(
3755 || {
3756 if required {
3757 Err(missing_input(name, InputSource::Multipart))
3758 } else {
3759 Ok(Value::Null)
3760 }
3761 },
3762 Ok,
3763 )
3764}
3765
3766fn multipart_parts_value(
3767 parts: &[&MultipartPart<'_>],
3768 descriptor: &TypeDescriptor,
3769 slots: &UploadSlots,
3770) -> Result<Option<Value>, InputRejection> {
3771 if let SchemaKind::Array(item_schema) = &descriptor.schema {
3772 let values = if let Some(item) = &descriptor.items {
3773 parts
3774 .iter()
3775 .map(|part| multipart_part_value(part, item, slots))
3776 .collect::<Result<Vec<_>, _>>()?
3777 } else {
3778 parts
3779 .iter()
3780 .map(|part| multipart_scalar_value(part, item_schema))
3781 .collect::<Result<Vec<_>, _>>()?
3782 };
3783 return Ok((!values.is_empty()).then_some(Value::Array(values)));
3784 }
3785 parts
3786 .first()
3787 .map(|part| multipart_part_value(part, descriptor, slots))
3788 .transpose()
3789}
3790
3791fn multipart_part_value(
3792 part: &MultipartPart<'_>,
3793 descriptor: &TypeDescriptor,
3794 slots: &UploadSlots,
3795) -> Result<Value, InputRejection> {
3796 if descriptor.schema == SchemaKind::Binary {
3797 return Ok(slots.park(part.to_upload()));
3802 }
3803 multipart_scalar_value(part, &descriptor.schema)
3804}
3805
3806fn multipart_scalar_value(
3807 part: &MultipartPart<'_>,
3808 schema: &SchemaKind,
3809) -> Result<Value, InputRejection> {
3810 let value = std::str::from_utf8(part.bytes)
3811 .map_err(|_| multipart_rejection("multipart text field is not valid UTF-8"))?;
3812 Ok(raw_scalar_value(value, schema))
3813}
3814
3815fn upload_arguments(
3816 arguments: &Value,
3817 name: &str,
3818 required: bool,
3819) -> Result<Vec<UploadFile>, InputRejection> {
3820 let value = arguments
3821 .as_object()
3822 .and_then(|arguments| arguments.get(name))
3823 .unwrap_or(arguments);
3824 if value.is_null() {
3825 return if required {
3826 Err(missing_input(name, InputSource::File))
3827 } else {
3828 Ok(Vec::new())
3829 };
3830 }
3831 match value {
3832 Value::Array(values) => values
3833 .iter()
3834 .map(|value| upload_from_value(value, name))
3835 .collect(),
3836 value => upload_from_value(value, name).map(|upload| vec![upload]),
3837 }
3838}
3839
3840fn upload_from_value(value: &Value, name: &str) -> Result<UploadFile, InputRejection> {
3841 if let Value::String(encoded) = value {
3842 return decode_base64_upload(encoded, name, None, None);
3843 }
3844 if let Value::Object(object) = value {
3845 let encoded = object
3846 .get("base64")
3847 .or_else(|| object.get("data"))
3848 .or_else(|| object.get("content"))
3849 .and_then(Value::as_str);
3850 if let Some(encoded) = encoded {
3851 return decode_base64_upload(
3852 encoded,
3853 object
3854 .get("field_name")
3855 .and_then(Value::as_str)
3856 .unwrap_or(name),
3857 object
3858 .get("file_name")
3859 .and_then(Value::as_str)
3860 .map(str::to_owned),
3861 object
3862 .get("content_type")
3863 .and_then(Value::as_str)
3864 .map(str::to_owned),
3865 );
3866 }
3867 }
3868 blazingly_json::from_value(value.clone())
3869 .map_err(|error| decode_rejection(name, InputSource::File, &error.to_string()))
3870}
3871
3872fn decode_base64_upload(
3873 encoded: &str,
3874 field_name: &str,
3875 file_name: Option<String>,
3876 content_type: Option<String>,
3877) -> Result<UploadFile, InputRejection> {
3878 let bytes = base64::engine::general_purpose::STANDARD
3879 .decode(encoded)
3880 .map_err(|error| decode_rejection(field_name, InputSource::File, &error.to_string()))?;
3881 Ok(UploadFile {
3882 field_name: field_name.to_owned(),
3883 file_name,
3884 content_type,
3885 bytes,
3886 })
3887}
3888
3889fn file_count_rejection(required: bool, actual: usize, expected: &str) -> InputRejection {
3890 InputRejection {
3891 status: 422,
3892 code: "invalid_file_count".to_owned(),
3893 message: format!("file input expected {expected} upload"),
3894 details: Some(json!({
3895 "source": "file",
3896 "required": required,
3897 "expected": expected,
3898 "actual": actual
3899 })),
3900 }
3901}
3902
3903fn multipart_rejection(reason: &str) -> InputRejection {
3904 InputRejection {
3905 status: 422,
3906 code: "invalid_multipart".to_owned(),
3907 message: "request body is not valid multipart form data".to_owned(),
3908 details: Some(json!({
3909 "source": "multipart",
3910 "reason": reason
3911 })),
3912 }
3913}
3914
3915thread_local! {
3916 static TYPE_DESCRIPTORS: std::cell::RefCell<Vec<(usize, Rc<TypeDescriptor>)>> =
3930 const { std::cell::RefCell::new(Vec::new()) };
3931}
3932
3933fn cached_type_descriptor<T: ApiSchema>() -> Rc<TypeDescriptor> {
3935 let key = <T as ApiSchema>::type_descriptor as fn() -> TypeDescriptor as usize;
3936 let cached = TYPE_DESCRIPTORS.with_borrow(|cache| {
3937 cache
3938 .iter()
3939 .find(|(cached_key, _)| *cached_key == key)
3940 .map(|(_, descriptor)| Rc::clone(descriptor))
3941 });
3942 if let Some(descriptor) = cached {
3943 return descriptor;
3944 }
3945 let descriptor = Rc::new(T::type_descriptor());
3948 TYPE_DESCRIPTORS.with_borrow_mut(|cache| cache.push((key, Rc::clone(&descriptor))));
3949 descriptor
3950}
3951
3952fn extract_argument<T>(
3953 input: &InvocationInput<'_>,
3954 name: &str,
3955 source: InputSource,
3956 required: bool,
3957) -> Result<T, InputRejection>
3958where
3959 T: ApiSchema + DeserializeOwned,
3960{
3961 if let InvocationInput::Http(request) = input
3964 && source == InputSource::Json
3965 {
3966 let mut deserializer = blazingly_json::Deserializer::from_slice(request.body());
3967 let decoded =
3968 serde_path_to_error::deserialize::<_, T>(&mut deserializer).map_err(|error| {
3969 decode_path_rejection(
3970 name,
3971 source,
3972 &error.path().to_string(),
3973 &error.inner().to_string(),
3974 )
3975 })?;
3976 return validate_decoded(decoded, source);
3977 }
3978
3979 let descriptor = cached_type_descriptor::<T>();
3980 let value = match input {
3981 InvocationInput::Http(request) => {
3982 raw_argument_value(*request, name, source, required, &descriptor)?
3983 }
3984 InvocationInput::Arguments(arguments) => {
3985 structured_argument_value(arguments, name, source, required, &descriptor)?
3986 }
3987 };
3988
3989 let decoded = blazingly_json::from_value::<T>(value)
3990 .map_err(|error| decode_rejection(name, source, &error.to_string()))?;
3991 validate_decoded(decoded, source)
3992}
3993
3994fn validate_decoded<T: ApiSchema>(decoded: T, source: InputSource) -> Result<T, InputRejection> {
3995 decoded.validate_input().map_err(|errors| InputRejection {
3996 status: 422,
3997 code: "validation_error".to_owned(),
3998 message: format!("{} input failed validation", source_name(source)),
3999 details: blazingly_json::to_value(errors).ok(),
4000 })?;
4001 Ok(decoded)
4002}
4003
4004fn raw_argument_value(
4005 request: &dyn HttpRequestParts,
4006 name: &str,
4007 source: InputSource,
4008 required: bool,
4009 descriptor: &TypeDescriptor,
4010) -> Result<Value, InputRejection> {
4011 if let Some(model) = &descriptor.model {
4012 let properties = model
4013 .fields
4014 .iter()
4015 .filter_map(|field| {
4016 raw_field_value(request, source, field)
4017 .map(|(name, value)| (name.to_owned(), value))
4018 })
4019 .collect();
4020 let properties: blazingly_json::Map<String, Value> = properties;
4021 if properties.is_empty() && !required {
4022 return Ok(Value::Null);
4023 }
4024 return Ok(Value::Object(properties));
4025 }
4026
4027 let Some(raw) = raw_typed_value(request, source, name, descriptor) else {
4028 return if required {
4029 Err(missing_input(name, source))
4030 } else {
4031 Ok(Value::Null)
4032 };
4033 };
4034 Ok(raw)
4035}
4036
4037fn structured_argument_value(
4038 arguments: &Value,
4039 name: &str,
4040 source: InputSource,
4041 required: bool,
4042 descriptor: &TypeDescriptor,
4043) -> Result<Value, InputRejection> {
4044 if descriptor.model.is_some() {
4045 let value = select_model_fields(arguments, name, descriptor);
4046 if value.as_object().is_some_and(blazingly_json::Map::is_empty) && !required {
4047 return Ok(Value::Null);
4048 }
4049 return Ok(value);
4050 }
4051
4052 let value = arguments
4053 .as_object()
4054 .and_then(|arguments| arguments.get(name))
4055 .cloned();
4056 if required {
4057 value.ok_or_else(|| missing_input(name, source))
4058 } else {
4059 Ok(value.unwrap_or(Value::Null))
4060 }
4061}
4062
4063fn select_model_fields(arguments: &Value, name: &str, descriptor: &TypeDescriptor) -> Value {
4064 let Some(arguments) = arguments.as_object() else {
4065 return arguments.clone();
4066 };
4067 if let Some(Value::Object(nested)) = arguments.get(name) {
4068 return Value::Object(nested.clone());
4069 }
4070 let Some(model) = &descriptor.model else {
4071 return Value::Object(arguments.clone());
4072 };
4073 Value::Object(
4074 model
4075 .fields
4076 .iter()
4077 .filter_map(|field| {
4078 field_input_names(field).find_map(|field_name| {
4079 arguments
4080 .get(field_name)
4081 .cloned()
4082 .map(|value| (field_name.to_owned(), value))
4083 })
4084 })
4085 .collect(),
4086 )
4087}
4088
4089fn raw_field_value<'field>(
4090 request: &dyn HttpRequestParts,
4091 source: InputSource,
4092 field: &'field blazingly_core::FieldDescriptor,
4093) -> Option<(&'field str, Value)> {
4094 field_input_names(field).find_map(|field_name| {
4095 raw_typed_value(request, source, field_name, &field.ty).map(|value| (field_name, value))
4096 })
4097}
4098
4099fn field_input_names(field: &blazingly_core::FieldDescriptor) -> impl Iterator<Item = &str> {
4100 std::iter::once(field.name.as_str()).chain(field.validation.iter().filter_map(
4101 |rule| match rule {
4102 blazingly_core::ValidationRule::Alias(alias) => Some(alias.as_str()),
4103 blazingly_core::ValidationRule::MinLength(_)
4104 | blazingly_core::ValidationRule::MaxLength(_)
4105 | blazingly_core::ValidationRule::Email
4106 | blazingly_core::ValidationRule::Custom(_)
4107 | blazingly_core::ValidationRule::Nested => None,
4108 },
4109 ))
4110}
4111
4112fn raw_typed_value(
4113 request: &dyn HttpRequestParts,
4114 source: InputSource,
4115 name: &str,
4116 descriptor: &TypeDescriptor,
4117) -> Option<Value> {
4118 if let SchemaKind::Array(item) = &descriptor.schema {
4119 let mut values = Vec::new();
4120 let mut index = 0;
4121 while let Some(value) = request.value(source, name, index) {
4122 values.push(raw_scalar_value(&value, item));
4123 index += 1;
4124 }
4125 return (!values.is_empty()).then_some(Value::Array(values));
4126 }
4127
4128 request
4129 .value(source, name, 0)
4130 .map(|value| raw_scalar_value(&value, &descriptor.schema))
4131}
4132
4133fn raw_scalar_value(value: &str, schema: &SchemaKind) -> Value {
4134 match schema {
4135 SchemaKind::String | SchemaKind::Binary | SchemaKind::Array(_) => {
4136 Value::String(value.to_owned())
4137 }
4138 SchemaKind::Integer | SchemaKind::Number | SchemaKind::Boolean => {
4139 blazingly_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_owned()))
4140 }
4141 SchemaKind::Object | SchemaKind::Any => {
4142 blazingly_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_owned()))
4143 }
4144 }
4145}
4146
4147fn missing_input(name: &str, source: InputSource) -> InputRejection {
4148 InputRejection {
4149 status: 422,
4150 code: "missing_input".to_owned(),
4151 message: format!("required {} input is missing", source_name(source)),
4152 details: Some(json!({
4153 "source": source_name(source),
4154 "name": name
4155 })),
4156 }
4157}
4158
4159fn decode_rejection(name: &str, source: InputSource, reason: &str) -> InputRejection {
4160 decode_path_rejection(name, source, "", reason)
4161}
4162
4163fn decode_path_rejection(
4164 name: &str,
4165 source: InputSource,
4166 path: &str,
4167 reason: &str,
4168) -> InputRejection {
4169 let (code, message) = if source == InputSource::Json {
4170 ("invalid_json", "request body is not valid JSON".to_owned())
4171 } else {
4172 (
4173 "invalid_input",
4174 format!("{} input could not be decoded", source_name(source)),
4175 )
4176 };
4177 let mut details = json!({
4178 "source": source_name(source),
4179 "name": name,
4180 "reason": reason
4181 });
4182 if !path.is_empty()
4183 && let Some(details) = details.as_object_mut()
4184 {
4185 #[cfg(feature = "validation")]
4189 {
4190 let violations = blazingly_validation::decode_violations(path, reason);
4191 details.insert(
4192 "field".to_owned(),
4193 Value::String(blazingly_validation::normalize_field_path(path)),
4194 );
4195 if let Ok(rendered) = blazingly_json::to_value(violations.violations()) {
4196 details.insert("violations".to_owned(), rendered);
4197 }
4198 }
4199 #[cfg(not(feature = "validation"))]
4200 details.insert("field".to_owned(), Value::String(path.to_owned()));
4201 }
4202 InputRejection {
4203 status: 422,
4204 code: code.to_owned(),
4205 message,
4206 details: Some(details),
4207 }
4208}
4209
4210const fn source_name(source: InputSource) -> &'static str {
4211 match source {
4212 InputSource::Path => "path",
4213 InputSource::Query => "query",
4214 InputSource::Header => "header",
4215 InputSource::Cookie => "cookie",
4216 InputSource::Json => "json",
4217 InputSource::Form => "form",
4218 InputSource::Multipart => "multipart",
4219 InputSource::File => "file",
4220 InputSource::Stream => "stream",
4221 }
4222}
4223
4224#[macro_export]
4225macro_rules! routes {
4226 ($($operation:ident),* $(,)?) => {
4227 ::std::vec![$($operation::executable()),*]
4228 };
4229}
4230
4231#[cfg(test)]
4232mod tests {
4233 use super::{
4234 FromInvocation, HttpRequestParts, InvocationInput, UploadBody, Value,
4235 multipart_argument_value, parse_multipart_request,
4236 };
4237 use blazingly_core::{
4238 ApiError, ApiModel, ApiSchema, FieldDescriptor, File, InputSource, ModelDescriptor,
4239 Multipart, MultipartError, SchemaKind, TypeDescriptor, UploadFile, UploadSlots,
4240 ValidationErrors,
4241 };
4242 use serde::Deserialize;
4243 use std::borrow::Cow;
4244
4245 const BOUNDARY: &str = "blazingly-test";
4246
4247 #[derive(Deserialize)]
4248 struct CoverUpload {
4249 title: String,
4250 attachments: Vec<UploadFile>,
4251 }
4252
4253 impl ApiModel for CoverUpload {
4254 fn model_descriptor() -> ModelDescriptor {
4255 ModelDescriptor::new(
4256 "CoverUpload",
4257 vec![
4258 FieldDescriptor::new(
4259 "title",
4260 true,
4261 TypeDescriptor::scalar("String", SchemaKind::String),
4262 Vec::new(),
4263 ),
4264 FieldDescriptor::new(
4265 "attachments",
4266 true,
4267 <Vec<UploadFile> as ApiSchema>::type_descriptor(),
4268 Vec::new(),
4269 ),
4270 ],
4271 )
4272 }
4273
4274 fn validate(&self) -> Result<(), ValidationErrors> {
4275 Ok(())
4276 }
4277 }
4278
4279 struct Request {
4280 content_type: String,
4281 body: Vec<u8>,
4282 }
4283
4284 impl HttpRequestParts for Request {
4285 fn value(&self, source: InputSource, name: &str, index: usize) -> Option<Cow<'_, str>> {
4286 if source == InputSource::Header
4287 && name.eq_ignore_ascii_case("content-type")
4288 && index == 0
4289 {
4290 Some(Cow::Borrowed(&self.content_type))
4291 } else {
4292 None
4293 }
4294 }
4295
4296 fn body(&self) -> &[u8] {
4297 &self.body
4298 }
4299 }
4300
4301 fn request(fills: &[(u8, usize)]) -> Request {
4303 let total: usize = fills.iter().map(|(_, size)| size).sum();
4304 let mut body = Vec::with_capacity(total + 512);
4305 body.extend_from_slice(
4306 format!(
4307 "--{BOUNDARY}\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\nrelease\r\n"
4308 )
4309 .as_bytes(),
4310 );
4311 for (index, &(fill, size)) in fills.iter().enumerate() {
4312 body.extend_from_slice(
4313 format!(
4314 "--{BOUNDARY}\r\nContent-Disposition: form-data; name=\"attachments\"; \
4315 filename=\"cover{index}.png\"\r\nContent-Type: image/png\r\n\r\n"
4316 )
4317 .as_bytes(),
4318 );
4319 body.resize(body.len() + size, fill);
4320 body.extend_from_slice(b"\r\n");
4321 }
4322 body.extend_from_slice(format!("--{BOUNDARY}--\r\n").as_bytes());
4323 Request {
4324 content_type: format!("multipart/form-data; boundary={BOUNDARY}"),
4325 body,
4326 }
4327 }
4328
4329 #[test]
4330 fn a_typed_upload_never_becomes_a_json_array() {
4331 let request = request(&[(b'x', 1 << 20)]);
4332 let parts = parse_multipart_request(&request).expect("the multipart body parses");
4333 let descriptor = <CoverUpload as ApiSchema>::type_descriptor();
4334 let slots = UploadSlots::acquire();
4335 let document = multipart_argument_value(&parts, "input", true, &descriptor, &slots)
4336 .expect("the document builds");
4337
4338 let encoded = blazingly_json::to_string(&document).expect("the document encodes");
4339 assert!(
4340 encoded.len() < 256,
4341 "a megabyte of upload left {} bytes in the document: {encoded}",
4342 encoded.len()
4343 );
4344 }
4345
4346 #[test]
4347 fn a_typed_multipart_model_carries_text_and_upload_bytes() {
4348 let request = request(&[(b'x', 4096)]);
4349 let Multipart(input) = Multipart::<CoverUpload>::from_invocation(
4350 &InvocationInput::Http(&request),
4351 "input",
4352 true,
4353 )
4354 .expect("the typed multipart body decodes");
4355
4356 assert_eq!(input.title, "release");
4357 assert_eq!(input.attachments.len(), 1);
4358 let attachment = &input.attachments[0];
4359 assert_eq!(attachment.field_name, "attachments");
4360 assert_eq!(attachment.file_name.as_deref(), Some("cover0.png"));
4361 assert_eq!(attachment.content_type.as_deref(), Some("image/png"));
4362 assert_eq!(attachment.bytes.len(), 4096);
4363 assert!(attachment.bytes.iter().all(|byte| *byte == b'x'));
4364 }
4365
4366 #[test]
4367 fn repeated_upload_parts_reach_the_handler_in_order() {
4368 let request = request(&[(b'a', 8), (b'b', 16)]);
4369 let Multipart(input) = Multipart::<CoverUpload>::from_invocation(
4370 &InvocationInput::Http(&request),
4371 "input",
4372 true,
4373 )
4374 .expect("the typed multipart body decodes");
4375
4376 assert_eq!(input.attachments.len(), 2);
4377 assert_eq!(input.attachments[0].bytes, vec![b'a'; 8]);
4378 assert_eq!(input.attachments[1].bytes, vec![b'b'; 16]);
4379 assert_eq!(
4380 input.attachments[1].file_name.as_deref(),
4381 Some("cover1.png")
4382 );
4383 }
4384
4385 struct HeadlessRequest {
4387 body: Vec<u8>,
4388 }
4389
4390 impl HttpRequestParts for HeadlessRequest {
4391 fn value(&self, _source: InputSource, _name: &str, _index: usize) -> Option<Cow<'_, str>> {
4392 None
4393 }
4394
4395 fn body(&self) -> &[u8] {
4396 &self.body
4397 }
4398 }
4399
4400 fn upload_body(request: &dyn HttpRequestParts) -> UploadBody {
4401 UploadBody::from_invocation(&InvocationInput::Http(request), "body", true)
4402 .expect("a streaming body is always available over HTTP")
4403 }
4404
4405 #[test]
4406 fn the_streaming_extractor_reads_the_body_the_buffered_one_would_have_bought() {
4407 let request = request(&[(b'a', 8), (b'b', 16)]);
4408 let File(buffered) = File::<Vec<UploadFile>>::from_invocation(
4409 &InvocationInput::Http(&request),
4410 "attachments",
4411 true,
4412 )
4413 .expect("the buffered extractor decodes");
4414
4415 let mut stream = upload_body(&request)
4416 .into_multipart()
4417 .expect("the request declares a boundary");
4418 let streamed = futures_lite::future::block_on(async {
4419 let mut parts = Vec::new();
4420 while let Some(field) = stream.next_field().await.expect("the document parses") {
4421 if field.name() != "attachments" {
4422 continue;
4423 }
4424 parts.push(field.into_upload(1 << 20).await.expect("the part fits"));
4425 }
4426 parts
4427 });
4428
4429 assert_eq!(streamed, buffered);
4430 assert_eq!(streamed.len(), 2);
4431 assert_eq!(streamed[0].bytes, vec![b'a'; 8]);
4432 assert_eq!(streamed[1].file_name.as_deref(), Some("cover1.png"));
4433 }
4434
4435 #[test]
4436 fn both_multipart_readers_reject_a_malformed_body_the_same_way() {
4437 let malformed = Request {
4438 content_type: format!("multipart/form-data; boundary={BOUNDARY}"),
4439 body: b"this is not a multipart document".to_vec(),
4440 };
4441
4442 let rejection = File::<UploadFile>::from_invocation(
4443 &InvocationInput::Http(&malformed),
4444 "attachments",
4445 true,
4446 )
4447 .expect_err("the buffered extractor rejects the body");
4448
4449 let mut stream = upload_body(&malformed)
4450 .into_multipart()
4451 .expect("the request declares a boundary");
4452 let failure = futures_lite::future::block_on(stream.next_field())
4453 .expect_err("the streaming extractor rejects the body")
4454 .into_failure()
4455 .expect("the failure projects");
4456
4457 assert_eq!(failure.status, rejection.status);
4458 assert_eq!(failure.code, rejection.code);
4459 assert_eq!(failure.message, rejection.message);
4460 let streamed_details: Value = blazingly_json::from_slice(
4461 &failure
4462 .details
4463 .expect("the streaming failure carries details"),
4464 )
4465 .expect("valid JSON");
4466 assert_eq!(
4467 streamed_details,
4468 rejection
4469 .details
4470 .expect("the buffered rejection carries details")
4471 );
4472 assert_eq!(failure.status, 422);
4473 assert_eq!(failure.code, "invalid_multipart");
4474 }
4475
4476 #[test]
4477 fn a_request_without_a_content_type_cannot_be_read_as_multipart() {
4478 let request = HeadlessRequest {
4479 body: request(&[(b'a', 4)]).body,
4480 };
4481 let error = upload_body(&request)
4482 .into_multipart()
4483 .map(|_| ())
4484 .expect_err("a multipart body needs a declared boundary");
4485 assert_eq!(
4486 error,
4487 MultipartError::Malformed("missing multipart Content-Type header")
4488 );
4489 assert_eq!(error.status(), 422);
4490 }
4491
4492 #[test]
4493 fn a_forged_slot_token_in_a_text_part_is_rejected() {
4494 let slots = UploadSlots::acquire();
4497 let token = slots.park(UploadFile::new("real", vec![1, 2, 3]));
4498 let forged = blazingly_json::to_string(&token).expect("the token encodes");
4499 drop(slots);
4500
4501 let error = blazingly_json::from_str::<blazingly_json::Value>(&forged)
4502 .map_err(|error| error.to_string())
4503 .and_then(|value| {
4504 blazingly_json::from_value::<UploadFile>(value).map_err(|error| error.to_string())
4505 })
4506 .expect_err("a token outside its extraction resolves to nothing");
4507 assert!(error.contains("no longer available"), "{error}");
4508 }
4509}
4510
4511#[cfg(test)]
4512mod blocking_pool_tests {
4513 use super::{
4514 BlockingError, BlockingPool, BlockingPoolConfig, CHAIN_ASYNC, CHAIN_SYNC, CHAIN_UNKNOWN,
4515 CompiledOperationDependencies, DependencyError, Depends, Provider, on_blocking_worker,
4516 };
4517 use blazingly_di::DependencySlot;
4518 use std::cell::Cell;
4519 use std::num::NonZeroUsize;
4520 use std::rc::Rc;
4521 use std::sync::atomic::{AtomicUsize, Ordering};
4522 use std::sync::{Arc, mpsc};
4523 use std::time::Duration;
4524
4525 fn pool(workers: usize, capacity: usize) -> BlockingPool {
4526 BlockingPool::new(BlockingPoolConfig::new(
4527 NonZeroUsize::new(workers).expect("non-zero workers"),
4528 NonZeroUsize::new(capacity).expect("non-zero capacity"),
4529 ))
4530 .expect("a local pool starts")
4531 }
4532
4533 #[test]
4539 fn a_full_queue_is_rejected_rather_than_queued_forever() {
4540 let pool = pool(1, 1);
4541 let (release, blocked) = mpsc::channel::<()>();
4542 let (started, running) = mpsc::channel::<()>();
4543 pool.submit(Box::new(move || {
4544 started.send(()).expect("signal the worker started");
4545 blocked.recv().expect("hold the only worker");
4546 }))
4547 .expect("the first job reaches the worker");
4548 running.recv().expect("the worker picked the job up");
4549
4550 pool.submit(Box::new(|| {}))
4551 .expect("the one queue slot accepts the second job");
4552 assert_eq!(
4553 pool.submit(Box::new(|| {})),
4554 Err(BlockingError::Saturated),
4555 "the worker and its one queued slot are occupied"
4556 );
4557 release.send(()).expect("release the worker");
4558 }
4559
4560 #[test]
4561 fn a_panicking_job_does_not_cost_the_pool_its_only_worker() {
4562 let pool = pool(1, 4);
4563 let previous = std::panic::take_hook();
4564 std::panic::set_hook(Box::new(|_| {}));
4565 pool.submit(Box::new(|| panic!("job panic")))
4566 .expect("the panicking job is scheduled");
4567 let (done, finished) = mpsc::channel::<()>();
4568 pool.submit(Box::new(move || done.send(()).expect("report completion")))
4569 .expect("the next job is scheduled");
4570 let outcome = finished.recv_timeout(Duration::from_secs(5));
4571 std::panic::set_hook(previous);
4572 outcome.expect("the worker survived the panic and ran the next job");
4573 }
4574
4575 #[test]
4576 fn dropping_the_last_handle_drains_the_queue_and_stops_the_workers() {
4577 let ran = Arc::new(AtomicUsize::new(0));
4578 let (done, finished) = mpsc::channel::<()>();
4579 {
4580 let pool = pool(2, 8);
4581 let clone = pool.clone();
4582 for _ in 0..4 {
4583 let ran = Arc::clone(&ran);
4584 let done = done.clone();
4585 clone
4586 .submit(Box::new(move || {
4587 ran.fetch_add(1, Ordering::SeqCst);
4588 done.send(()).expect("report completion");
4589 }))
4590 .expect("queued before shutdown");
4591 }
4592 drop(clone);
4593 }
4594 drop(done);
4595 for _ in 0..4 {
4596 finished
4597 .recv_timeout(Duration::from_secs(5))
4598 .expect("every queued job still ran");
4599 }
4600 assert_eq!(ran.load(Ordering::SeqCst), 4);
4601 }
4602
4603 #[test]
4604 fn only_pool_workers_report_themselves_as_workers() {
4605 assert!(!on_blocking_worker(), "the test thread is not a worker");
4606 let pool = pool(1, 1);
4607 let (report, observed) = mpsc::channel::<(bool, bool)>();
4608 pool.submit(Box::new(move || {
4609 let named = std::thread::current()
4610 .name()
4611 .is_some_and(|name| name.starts_with("blazingly-blocking-"));
4612 report
4613 .send((on_blocking_worker(), named))
4614 .expect("report from the worker");
4615 }))
4616 .expect("the probe is scheduled");
4617 assert_eq!(
4618 observed
4619 .recv_timeout(Duration::from_secs(5))
4620 .expect("the worker reported"),
4621 (true, true)
4622 );
4623 }
4624
4625 struct Config(u32);
4626 struct Client(u32);
4627
4628 fn plan(providers: Vec<blazingly_di::CompiledProvider>) -> CompiledOperationDependencies {
4629 CompiledOperationDependencies {
4630 singletons: Rc::new(Vec::new()),
4631 request_providers: providers,
4632 handler_slots: Vec::new(),
4633 input_decoders: Vec::new(),
4634 sync_chain: Cell::new(CHAIN_UNKNOWN),
4635 }
4636 }
4637
4638 fn resolve_plan(
4639 plan: &CompiledOperationDependencies,
4640 ) -> Result<crate::RequestDependencyValues, DependencyError> {
4641 let seeded = crate::RequestDependencyValues::new(plan.request_providers.len());
4642 futures_lite::future::block_on(plan.resolve(seeded))
4643 }
4644
4645 #[test]
4646 fn an_all_sync_chain_resolves_without_awaiting_and_stays_classified() {
4647 let plan = plan(vec![
4648 Provider::request(|| Config(7))
4649 .compile(&[])
4650 .expect("config compiles"),
4651 Provider::request(|config: Depends<Config>| Client(config.into_inner().0 + 1))
4652 .compile(&[DependencySlot::Request(0)])
4653 .expect("client compiles"),
4654 ]);
4655 for _ in 0..2 {
4656 let values = resolve_plan(&plan).expect("chain resolves");
4657 let client = values.as_slice()[1]
4658 .as_ref()
4659 .expect("client slot")
4660 .downcast_ref::<Client>()
4661 .expect("client type");
4662 assert_eq!(client.0, 8);
4663 }
4664 assert_eq!(plan.sync_chain.get(), CHAIN_SYNC);
4665 }
4666
4667 #[test]
4668 fn a_chain_with_an_async_provider_falls_back_and_remembers_it() {
4669 let plan = plan(vec![
4670 Provider::request(|| Config(7))
4671 .compile(&[])
4672 .expect("config compiles"),
4673 Provider::request_async(|config: Depends<Config>| async move {
4674 Client(config.into_inner().0 + 2)
4675 })
4676 .compile(&[DependencySlot::Request(0)])
4677 .expect("client compiles"),
4678 ]);
4679 for _ in 0..2 {
4680 let values = resolve_plan(&plan).expect("chain resolves");
4681 let client = values.as_slice()[1]
4682 .as_ref()
4683 .expect("client slot")
4684 .downcast_ref::<Client>()
4685 .expect("client type");
4686 assert_eq!(client.0, 9);
4687 }
4688 assert_eq!(plan.sync_chain.get(), CHAIN_ASYNC);
4689 }
4690
4691 #[test]
4692 fn a_synchronous_provider_failure_is_reported_rather_than_retried() {
4693 let attempts = Rc::new(Cell::new(0_u32));
4694 let counted = Rc::clone(&attempts);
4695 let plan = plan(vec![
4696 Provider::try_request(move || {
4697 counted.set(counted.get() + 1);
4698 Err::<Config, _>(DependencyError::internal("provider_failed", "no config"))
4699 })
4700 .compile(&[])
4701 .expect("config compiles"),
4702 ]);
4703 let Err(error) = resolve_plan(&plan) else {
4704 panic!("a failing provider must not resolve");
4705 };
4706 assert!(matches!(
4707 error,
4708 DependencyError::Internal {
4709 code: "provider_failed",
4710 ..
4711 }
4712 ));
4713 assert_eq!(attempts.get(), 1, "a failed sync provider must not re-run");
4714 }
4715}