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