Skip to main content

blazingly_executor/
lib.rs

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/// Capacity and worker count for synchronous blocking handlers.
97#[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/// Reports whether the calling thread is a blocking-pool worker.
141///
142/// Callers already on a worker own that thread for the duration of their job,
143/// so they can run further synchronous work inline instead of queueing it
144/// behind themselves.
145#[must_use]
146pub fn on_blocking_worker() -> bool {
147    ON_BLOCKING_WORKER.with(Cell::get)
148}
149
150/// Workers parked on the shared injector, and the handles used to wake them.
151struct ParkedWorkers {
152    threads: Vec<Option<Thread>>,
153    idle: Vec<usize>,
154}
155
156/// A worker parks outside every lock this type owns: `receiver` is held only
157/// for a non-blocking `try_recv`, and `parked` only while a worker registers or
158/// a submitter claims one. Submission therefore never waits behind a sleeping
159/// worker.
160struct 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    /// Hands one queued job to one sleeping worker.
179    ///
180    /// The `SeqCst` pair with [`Self::register`] is load-bearing: a worker
181    /// registers before its second `try_recv`, so a submitter that reads zero
182    /// idle workers has already enqueued the job that recheck will find.
183    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                    // A job that unwinds must not cost the pool a worker.
233                    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        // Disconnect first so a woken worker drains the queue and then observes
266        // the shutdown instead of parking again.
267        drop(self.sender.take());
268        self.shared.wake_all();
269    }
270}
271
272/// A bounded process-wide pool used only by explicitly synchronous handlers.
273#[derive(Clone)]
274pub struct BlockingPool {
275    handle: Arc<PoolHandle>,
276}
277
278impl BlockingPool {
279    /// Starts a bounded worker pool.
280    ///
281    /// # Errors
282    ///
283    /// Returns an OS error when a worker thread cannot be started.
284    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
335/// Installs the process-wide blocking pool before the first sync invocation.
336///
337/// # Errors
338///
339/// Returns [`BlockingError::AlreadyConfigured`] after a pool has already been
340/// installed or initialized.
341pub 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/// Failure to schedule or execute a synchronous blocking handler.
349#[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
375/// Future resolved by a bounded blocking worker.
376pub 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/// Schedules owned synchronous work without blocking an async worker.
394#[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/// Runtime-neutral cooperative cancellation shared by adapters and operation
470/// execution.
471#[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    /// Marks the token cancelled and wakes every controlled invocation.
488    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
521/// Future completed when a [`CancellationToken`] is cancelled.
522pub 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/// Reason a controlled invocation stopped before completion.
550#[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/// Adapter-supplied cancellation and timeout signals for one invocation.
576///
577/// A timeout is represented as a future so native, Cloudflare, tests, and
578/// other adapters can use their own clock/runtime without leaking it into the
579/// operation graph.
580#[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/// Runtime-neutral metadata passed to compiled plugin hooks.
632#[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/// A body-free result summary passed to `on_response` hooks.
645#[derive(Clone, Copy, Debug, Eq, PartialEq)]
646pub struct HookOutcome {
647    pub status: u16,
648    pub kind: HookOutcomeKind,
649}
650
651/// Stable result classes visible to plugin response hooks.
652#[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
688/// Borrowed HTTP request values used by the compiled executor.
689///
690/// Production adapters implement this view over their native request without
691/// copying headers, query strings, or request bodies.
692pub trait HttpRequestParts {
693    fn value(&self, source: InputSource, name: &str, index: usize) -> Option<Cow<'_, str>>;
694    fn body(&self) -> &[u8];
695
696    /// Transfers an adapter-owned pull request body to a streaming extractor.
697    fn take_body_stream(&self) -> Option<StreamingBody> {
698        None
699    }
700
701    /// Returns transport context installed by HTTP middleware.
702    ///
703    /// The default keeps non-HTTP transports and existing adapters allocation
704    /// free. HTTP adapters only allocate extension storage when middleware
705    /// actually installs a value.
706    fn extension(&self, _type_id: std::any::TypeId) -> Option<&dyn std::any::Any> {
707        None
708    }
709
710    /// The request method, when the adapter exposes raw request parts.
711    ///
712    /// Every accessor in this family defaults to `None` so existing adapters
713    /// keep compiling; the first-party HTTP adapter forwards each one borrowed
714    /// from its receive buffer.
715    fn method(&self) -> Option<HttpMethod> {
716        None
717    }
718
719    /// The request path, without the query string.
720    fn path(&self) -> Option<&str> {
721        None
722    }
723
724    /// Address of the direct network peer, when known by the adapter.
725    fn peer_addr(&self) -> Option<std::net::SocketAddr> {
726        None
727    }
728
729    /// Effective transport scheme, after any trusted proxy normalization.
730    fn scheme(&self) -> Option<&str> {
731        None
732    }
733
734    /// Effective request host, after any trusted proxy normalization.
735    fn host(&self) -> Option<&str> {
736        None
737    }
738}
739
740/// Pull-based request body with adapter-enforced transport limits.
741///
742/// Each `next_chunk` call is the backpressure boundary. The native adapter
743/// does not read and queue another network chunk until the handler pulls.
744#[derive(Debug)]
745pub struct UploadBody {
746    stream: StreamingBody,
747    bytes_read: u64,
748    /// Captured at extraction, because the boundary a streaming multipart
749    /// reader needs lives in the request head, not in the body it is handed.
750    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    /// Records the request's declared media type.
764    #[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    /// The request's declared media type, when it had one.
771    #[must_use]
772    pub fn content_type(&self) -> Option<&str> {
773        self.content_type.as_deref()
774    }
775
776    /// Reads this body as a `multipart/form-data` document.
777    ///
778    /// The document is parsed while it arrives, so a handler that consumes each
779    /// part chunk by chunk never has the upload resident. This is the streaming
780    /// counterpart of the buffered `Multipart<T>` and `File<UploadFile>`
781    /// extractors, which materialize every part before the handler starts.
782    ///
783    /// # Errors
784    ///
785    /// Returns [`MultipartError::Malformed`] when the request did not declare
786    /// `multipart/form-data` with a usable boundary.
787    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    /// Pulls the next upload chunk.
805    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    /// Deliberately buffers the remaining upload with an explicit limit.
816    ///
817    /// # Errors
818    ///
819    /// Returns a producer error or `upload_collect_limit_exceeded`.
820    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/// Transport-neutral values supplied to typed operation extractors.
870#[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
888/// Slot-based dependency values visible to one operation handler.
889///
890/// Slots are compiled when the application is built. Calling [`Self::get`]
891/// performs one bounds check and one safe type check; it never looks up a type
892/// name or hashes a key on the request path.
893pub struct ResolvedDependencies<'values> {
894    singletons: &'values [Option<DependencyValue>],
895    requests: &'values [Option<DependencyValue>],
896    slots: &'values [DependencySlot],
897}
898
899impl ResolvedDependencies<'_> {
900    /// Reads a typed dependency from the handler's compiled argument slot.
901    ///
902    /// # Errors
903    ///
904    /// Returns an internal dependency error if generated metadata and the
905    /// compiled plan disagree.
906    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    /// Clones a dependency handle directly into a handler argument.
917    ///
918    /// # Errors
919    ///
920    /// Returns an internal dependency error if generated metadata and the
921    /// compiled plan disagree.
922    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/// A stable client-visible failure produced while extracting an argument.
928#[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
964/// Decodes one typed handler argument from an invocation.
965pub trait FromInvocation: Sized {
966    /// Extracts one handler argument.
967    ///
968    /// # Errors
969    ///
970    /// Returns a stable rejection when the value is missing, cannot be
971    /// decoded, or fails model validation.
972    fn from_invocation(
973        input: &InvocationInput<'_>,
974        name: &str,
975        required: bool,
976    ) -> Result<Self, InputRejection>;
977}
978
979/// Explicitly asks the operation macro to extract `T` from the invocation.
980///
981/// Bare handler argument types remain compiled dependency-injection requests;
982/// wrapping a downstream [`FromInvocation`] implementation in `Extract<T>`
983/// removes that ambiguity without teaching the macro about every application's
984/// extractor type. The wrapper delegates extraction verbatim, including a
985/// custom extractor's transport-specific rejection.
986#[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/// An owned snapshot of the raw request parts, taken before the handler runs.
1000///
1001/// The parts are read borrowed from the adapter's receive buffer through
1002/// [`HttpRequestParts`] and copied once here, because an async handler's
1003/// future outlives the borrow. `scheme` and `host` are the effective values
1004/// after any trusted proxy middleware, matching what `ConnectionInfo` reports.
1005///
1006/// The snapshot is HTTP's: extracting it on a transport that carries no
1007/// request line — an MCP tool call — rejects deterministically with
1008/// `transport_mismatch` instead of inventing values.
1009#[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/// Typed request-local value installed by transport middleware.
1051#[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/// Decodes one provider-declared request input into an erased slot value.
1086///
1087/// Generated by `#[provider]`, which is the only place the input's concrete
1088/// type is known; the executor stores and calls these without ever naming an
1089/// extractor.
1090#[doc(hidden)]
1091pub type ProviderInputDecoder =
1092    Rc<dyn Fn(&InvocationInput<'_>) -> Result<DependencyValue, InputRejection>>;
1093
1094/// One request input a provider declared alongside its dependencies.
1095#[doc(hidden)]
1096pub struct ProviderInput {
1097    descriptor: InputDescriptor,
1098    value_type: core::any::TypeId,
1099    decode: ProviderInputDecoder,
1100}
1101
1102impl ProviderInput {
1103    /// Records one declared input and its generated decoder.
1104    #[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
1115/// A provider together with the request inputs it declared.
1116///
1117/// A plain provider converts losslessly, so `Plugin::provide` accepts both
1118/// spellings; the inputs ride beside the dependency graph rather than inside
1119/// it because an input is decoded from the invocation, not resolved from
1120/// another provider.
1121pub struct RequestProvider {
1122    provider: Provider,
1123    inputs: Vec<ProviderInput>,
1124}
1125
1126impl RequestProvider {
1127    /// Pairs a compiled provider with its declared request inputs.
1128    #[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                // Upload bytes travel beside the document, not inside it: the
1255                // document only carries a slot token per binary part, and this
1256                // guard owns the parked bytes until `T` has taken them or the
1257                // decode has failed.
1258                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
1272/// Types accepted by the typed [`File`] extractor.
1273pub 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/// The protocol-neutral result of executing one operation.
1326#[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
1367/// A typed handler result that can become a shared operation outcome.
1368pub 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
1435/// Tasks ride on the response value, so an error outcome discards them: a
1436/// rejection, a domain error, and an internal error carry no response value and
1437/// therefore no task slot. An operation that must schedule work on a failing
1438/// path injects `blazingly_http::BackgroundTasks` instead, whose tasks are
1439/// attached to the response whatever the outcome is.
1440impl<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
1526/// A handler plus the operation descriptor shared by HTTP and MCP.
1527pub 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    /// Creates an operation whose generated handler uses compiled DI slots.
1554    #[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    /// Creates an operation with an allocation-free synchronous fast path.
1581    ///
1582    /// The fallback preserves the complete async hook and cancellation
1583    /// lifecycle when plugins add hooks around the operation.
1584    #[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        // Response hooks and dependency finalizers are cleanup. Once started,
1695        // they are shielded from the invocation cancellation signal.
1696        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
2028/// `blazingly-di` compiles a provider's runner as either sync or async and
2029/// exposes no predicate for which. `CompiledProvider::run_sync` reports this
2030/// code for an async runner *without* invoking the factory, so it doubles as a
2031/// side-effect-free probe; the classification is cached because the runner kind
2032/// is fixed at compile time.
2033const 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    /// Decoders for provider-declared request inputs, one per compiled input
2048    /// slot. Inputs occupy the first `input_decoders.len()` request slots, so
2049    /// provider `i`'s value lives at `input_decoders.len() + i`.
2050    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    /// Decodes every provider-declared request input into its slot.
2070    ///
2071    /// Runs before the provider chain, so a provider never observes a missing
2072    /// input; a decode failure is the same typed rejection the equivalent
2073    /// handler argument would have produced, not an internal error.
2074    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    /// Resolves the request-scoped chain, driving it synchronously whenever
2090    /// every provider in it is synchronous.
2091    ///
2092    /// `CompiledProvider::run` boxes a ready future per provider, which is pure
2093    /// overhead for a chain that never suspends. The first resolve probes with
2094    /// `run_sync` and remembers the answer; an async provider anywhere in the
2095    /// chain sends every later resolve straight back to the awaiting path.
2096    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
2255/// A lexical provider scope containing operations and nested plugins.
2256pub 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    /// Mounts every operation in this scope, and below it, under a path prefix.
2295    ///
2296    /// The prefix is joined in front of each declared route path when the
2297    /// application is compiled, so one module — a function returning a
2298    /// `Plugin` — can be mounted at two prefixes by calling it twice, without
2299    /// restating a handler. Prefixes nest: a mounted plugin inside a mounted
2300    /// plugin serves under both. The prefix must start with `/`, must not end
2301    /// with `/`, and declares no `{...}` parameters of its own.
2302    #[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    /// Namespaces every operation identity in this scope, and below it.
2309    ///
2310    /// A stable id names an operation in the contract, the documents, and
2311    /// compatibility reports, so mounting the same module twice needs two
2312    /// identities: `with_id_namespace("v1")` turns `notes.create` into
2313    /// `v1.notes.create`, which also groups the operations under their own
2314    /// section in a browser UI. An MCP tool declared by the operation is
2315    /// prefixed the same way, so both mounts stay callable as distinct tools.
2316    #[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    /// Registers an application security scheme from this plugin scope.
2329    #[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    /// Adds a fallible async hook that runs before dependency resolution.
2354    #[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    /// Adds a fallible async hook before typed input extraction begins.
2367    #[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    /// Adds a fallible async hook immediately before input validation.
2380    #[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    /// Adds a fallible async hook after input preparation and before the user
2393    /// handler future is polled.
2394    #[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    /// Adds a fallible async hook after the handler and before transport
2407    /// response projection.
2408    #[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    /// Adds an async observer for rejected, domain-error, and internal-error
2421    /// outcomes.
2422    #[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    /// Adds an async observer that runs after the handler and DI finalizers.
2435    #[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    /// Adds a fallible async application startup hook.
2450    ///
2451    /// Startup executes parent hooks before child hooks. A failure stops
2452    /// startup before the server accepts requests.
2453    #[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    /// Adds a fallible async application shutdown hook.
2464    ///
2465    /// Shutdown executes child hooks before parent hooks and continues after a
2466    /// failure so every registered cleanup receives a chance to run.
2467    #[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/// Typed provider replacements applied only while compiling a test app.
2485///
2486/// Global replacements substitute every registered provider with the same
2487/// output type. Scoped replacements use a full plugin path such as
2488/// `app/users`; they can also shadow an inherited provider inside that scope.
2489#[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    /// Replaces every registered provider with the same output type.
2503    #[must_use]
2504    pub fn replace(mut self, provider: Provider) -> Self {
2505        self.insert(None, provider);
2506        self
2507    }
2508
2509    /// Replaces or shadows a provider inside one exact plugin path.
2510    #[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                // A replacement is a plain provider: the mock supplies the
2552                // value itself, so the replaced provider's request inputs are
2553                // neither decoded nor required.
2554                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/// An application-definition or dependency-compilation failure.
2592#[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
2768/// A validated executable operation graph.
2769pub 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    /// Validates and compiles executable operations.
2779    ///
2780    /// # Errors
2781    ///
2782    /// Returns [`ExecutableBuildError`] for invalid routes or unresolved
2783    /// dependencies.
2784    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    /// Compiles executable operations with application security schemes.
2791    ///
2792    /// # Errors
2793    ///
2794    /// Returns [`ExecutableBuildError`] for invalid routes, security
2795    /// requirements, or unresolved dependencies.
2796    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    /// Compiles plugin scopes, singleton values, request provider plans, and
2808    /// executable operations.
2809    ///
2810    /// # Errors
2811    ///
2812    /// Returns [`ExecutableBuildError`] for invalid routes, plugin names,
2813    /// missing providers, cycles, invalid lifetimes, or singleton failures.
2814    pub fn from_plugin(plugin: Plugin) -> Result<Self, ExecutableBuildError> {
2815        Self::from_plugin_with_overrides(plugin, TestOverrides::new())
2816    }
2817
2818    /// Compiles a plugin graph after applying typed test-only provider
2819    /// replacements.
2820    ///
2821    /// Overrides are applied before graph validation and compilation, so mocks
2822    /// cannot bypass missing dependency, cycle, or lifetime diagnostics.
2823    ///
2824    /// # Errors
2825    ///
2826    /// Returns [`ExecutableBuildError`] for invalid overrides or for any normal
2827    /// plugin graph compilation failure.
2828    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(&registrations)?;
2855        let (singletons, singleton_slots) = compile_singletons(&registrations)?;
2856        let singletons = Rc::new(singletons);
2857        let mut operations = Vec::with_capacity(scoped_operations.len());
2858        for mut scoped in scoped_operations {
2859            // Provider-declared request inputs fold into the operation
2860            // contract before the definition is built, so path placeholders,
2861            // duplicate names, projections, and fingerprints all see them as
2862            // if the handler had declared them itself.
2863            let input_plan = plan_operation_inputs(&scoped, &registrations)?;
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                &registrations,
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    /// Runs application startup hooks in parent-before-child order.
2969    ///
2970    /// # Errors
2971    ///
2972    /// Returns the first startup hook failure and stops the remaining startup
2973    /// sequence.
2974    pub async fn startup(&self) -> Result<(), DependencyError> {
2975        for hook in &self.startup_hooks {
2976            hook().await?;
2977        }
2978        Ok(())
2979    }
2980
2981    /// Runs application shutdown hooks in child-before-parent order.
2982    ///
2983    /// Every hook runs even when an earlier hook fails. The first error in
2984    /// execution order is returned after all cleanup has completed.
2985    ///
2986    /// # Errors
2987    ///
2988    /// Returns the first shutdown hook failure.
2989    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/// The accumulated mount of a plugin scope: a path prefix and an id namespace.
3091///
3092/// Both nest — a mounted plugin inside a mounted plugin serves under the
3093/// joined prefix and the dotted namespace — and both are applied to each
3094/// operation descriptor exactly once, while the descriptor is owned here,
3095/// before `App::build` re-validates path placeholders and uniqueness against
3096/// the joined values.
3097#[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            // The tool name is the identity an MCP host calls; two mounts of
3157            // one module must stay two callable tools.
3158            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 = &registrations[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 = &registrations[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
3315/// The request inputs one operation's provider chain declared, compiled to
3316/// slots.
3317///
3318/// Inputs occupy the first `decoders.len()` request slots, deduplicated by
3319/// wire identity and decoded type, so the same header consumed by two
3320/// providers is decoded once. `folded` is the subset of descriptors the
3321/// handler did not already declare, appended to the operation contract so
3322/// every projection — `OpenAPI` parameters, MCP tool schemas, generated
3323/// documentation, fingerprints — sees a dependency-origin input exactly like
3324/// a handler-declared one.
3325struct 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 = &registrations[id];
3355        // A singleton is built before any request exists; its subtree was
3356        // already rejected if anything in it declared an input.
3357        if registration.provider.lifetime() == DependencyLifetime::Singleton {
3358            continue;
3359        }
3360        let mut positions = Vec::with_capacity(registration.provider.inputs.len());
3361        for input in &registration.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 = &registrations[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    // Declared request inputs follow the dependencies, in declaration order,
3485    // each pointing at its pre-decoded slot at the front of the request slice.
3486    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
3553/// Encodes a success body, reserving what the previous body of this shape
3554/// needed.
3555///
3556/// `blazingly_json::to_vec` starts from a 128-byte buffer and doubles, so an 18 KB
3557/// listing pays about nine reallocations and copies its own body roughly twice
3558/// over before it reaches the transport. The shape key is per-monomorphization,
3559/// so each response type learns its own size independently; a stale or
3560/// colliding hint changes only the initial capacity, never the bytes produced.
3561fn 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        // The upload is parked whole and the document carries only its slot
3799        // token. Encoding the bytes here would turn a megabyte of image into a
3800        // million `Value::Number`s that the very next step decodes back into
3801        // the `Vec<u8>` this part already owns.
3802        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    /// Type descriptors already built on this thread, keyed by the address of
3918    /// the monomorphized `type_descriptor` function.
3919    ///
3920    /// A descriptor is a compile-time constant of its type, but building one
3921    /// allocates a `String` per field name, a nested `TypeDescriptor` per field
3922    /// and a `Vec` per rule list — around thirty allocations for a
3923    /// seven-field query model, repeated on every single request.
3924    ///
3925    /// Keying on the function address is sound because two distinct types can
3926    /// only share one address if the linker folded their `type_descriptor`
3927    /// bodies together, and folding requires the bodies to be byte-identical
3928    /// and therefore to produce identical descriptors. Extra entries (the same
3929    /// type instantiated in two crates) cost only a duplicate cache line.
3930    static TYPE_DESCRIPTORS: std::cell::RefCell<Vec<(usize, Rc<TypeDescriptor>)>> =
3931        const { std::cell::RefCell::new(Vec::new()) };
3932}
3933
3934/// Returns the descriptor for `T`, building it at most once per thread.
3935fn 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    // Built outside the borrow: a generated descriptor may itself reach back
3947    // into this cache for a nested model.
3948    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    // A JSON body is decoded straight from the request bytes and never
3963    // consults the descriptor, so it is not built at all on that path.
3964    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        // A decode failure and a rule failure describe the same field, so they
4187        // report one `violations` shape and one path syntax. `field` is kept
4188        // alongside it for readers that predate the unified shape.
4189        #[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/// Collects annotated handlers into the operation list an application is built
4226/// from.
4227///
4228/// ```ignore
4229/// let app = ExecutableApp::new(routes![create_user, read_user, delete_user])?;
4230/// ```
4231///
4232/// Registration is explicit on purpose: there is no route scan and no
4233/// inventory. A handler that is written but not listed here is not served, and
4234/// does not appear in the contract, in OpenAPI, in the generated documentation,
4235/// or as an MCP tool. Listing the same operation twice, or two operations that
4236/// collide on method and path or on operation id, fails when the application is
4237/// built rather than at the first request.
4238#[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    /// Builds a body with one `title` text part and one upload part per fill.
4316    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    /// The same request as `request`, with the `Content-Type` header removed.
4400    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        // A nested-model text part is parsed as JSON, so a client can write
4509        // whatever it likes there. It must not be able to name a slot.
4510        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    /// The bound counts queued jobs, not jobs a worker already owns.
4548    ///
4549    /// This is the same contract `crates/blazingly/tests/blocking_pool.rs`
4550    /// pins process-wide; here it is checked against a pool the test owns, so
4551    /// the shape of the queue can change without the assertion moving.
4552    #[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}