Skip to main content

blazingly_executor/
lib.rs

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