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