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#[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#[derive(Clone)]
134pub struct BlockingPool {
135 sender: SyncSender<BlockingJob>,
136}
137
138impl BlockingPool {
139 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
185pub 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#[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
225pub 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#[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#[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 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
377pub 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#[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#[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#[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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
502pub struct HookOutcome {
503 pub status: u16,
504 pub kind: HookOutcomeKind,
505}
506
507#[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
544pub trait HttpRequestParts {
549 fn value(&self, source: InputSource, name: &str, index: usize) -> Option<Cow<'_, str>>;
550 fn body(&self) -> &[u8];
551
552 fn take_body_stream(&self) -> Option<StreamingBody> {
554 None
555 }
556
557 fn extension(&self, _type_id: std::any::TypeId) -> Option<&dyn std::any::Any> {
563 None
564 }
565}
566
567#[derive(Debug)]
572pub struct UploadBody {
573 stream: StreamingBody,
574 bytes_read: u64,
575 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 #[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 #[must_use]
599 pub fn content_type(&self) -> Option<&str> {
600 self.content_type.as_deref()
601 }
602
603 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 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 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#[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
715pub struct ResolvedDependencies<'values> {
721 singletons: &'values [Option<DependencyValue>],
722 requests: &'values [Option<DependencyValue>],
723 slots: &'values [DependencySlot],
724}
725
726impl ResolvedDependencies<'_> {
727 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 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#[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
791pub trait FromInvocation: Sized {
793 fn from_invocation(
800 input: &InvocationInput<'_>,
801 name: &str,
802 required: bool,
803 ) -> Result<Self, InputRejection>;
804}
805
806#[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 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
950pub 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#[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
1045pub 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
1113impl<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
1204pub 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 #[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 #[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 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
1841pub 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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#[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 #[must_use]
2058 pub fn replace(mut self, provider: Provider) -> Self {
2059 self.insert(None, provider);
2060 self
2061 }
2062
2063 #[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#[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
2278pub 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 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 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 pub fn from_plugin(plugin: Plugin) -> Result<Self, ExecutableBuildError> {
2325 Self::from_plugin_with_overrides(plugin, TestOverrides::new())
2326 }
2327
2328 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(®istrations)?;
2364 let (singletons, singleton_slots) = compile_singletons(®istrations)?;
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 ®istrations,
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 pub async fn startup(&self) -> Result<(), DependencyError> {
2472 for hook in &self.startup_hooks {
2473 hook().await?;
2474 }
2475 Ok(())
2476 }
2477
2478 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 = ®istrations[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 = ®istrations[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 = ®istrations[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
2850fn 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 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 static TYPE_DESCRIPTORS: std::cell::RefCell<Vec<(usize, Rc<TypeDescriptor>)>> =
3228 const { std::cell::RefCell::new(Vec::new()) };
3229}
3230
3231fn 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 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 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 #[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 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 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 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}