1#![doc = include_str!("../docs/lib.md")]
2#![doc(html_favicon_url = "https://salvo.rs/favicon-32x32.png")]
3#![doc(html_logo_url = "https://salvo.rs/images/logo.svg")]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![cfg_attr(test, allow(clippy::unwrap_used))]
6
7use salvo_core::cfg_feature;
8
9#[cfg(any(
10 feature = "swagger-ui",
11 feature = "scalar",
12 feature = "rapidoc",
13 feature = "redoc"
14))]
15mod html;
16mod openapi;
17pub use openapi::*;
18
19#[doc = include_str!("../docs/endpoint.md")]
20pub mod endpoint;
21pub use endpoint::{Endpoint, EndpointArgRegister, EndpointOutRegister, EndpointRegistry};
22pub mod extract;
23mod routing;
24pub use routing::RouterExt;
25pub mod naming;
27
28cfg_feature! {
29 #![feature ="swagger-ui"]
30 pub mod swagger_ui;
31}
32cfg_feature! {
33 #![feature ="scalar"]
34 pub mod scalar;
35}
36cfg_feature! {
37 #![feature ="rapidoc"]
38 pub mod rapidoc;
39}
40cfg_feature! {
41 #![feature ="redoc"]
42 pub mod redoc;
43}
44
45#[cfg(feature = "rfc9457")]
46use std::any::TypeId;
47use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, LinkedList};
48use std::marker::PhantomData;
49
50use salvo_core::extract::Extractible;
51use salvo_core::http::StatusError;
52#[cfg(feature = "rfc9457")]
53use salvo_core::http::{NoExtensions, Problem};
54use salvo_core::writing;
55#[doc = include_str!("../docs/derive_to_parameters.md")]
56pub use salvo_oapi_macros::ToParameters;
57#[doc = include_str!("../docs/derive_to_response.md")]
58pub use salvo_oapi_macros::ToResponse;
59#[doc = include_str!("../docs/derive_to_responses.md")]
60pub use salvo_oapi_macros::ToResponses;
61#[doc = include_str!("../docs/derive_to_schema.md")]
62pub use salvo_oapi_macros::ToSchema;
63#[doc = include_str!("../docs/endpoint.md")]
64pub use salvo_oapi_macros::endpoint;
65pub(crate) use salvo_oapi_macros::schema;
66
67use crate::oapi::openapi::schema::OneOf;
68
69extern crate self as salvo_oapi;
71
72pub trait ToSchema {
138 fn to_schema(components: &mut Components) -> RefOr<schema::Schema>;
141}
142
143pub trait ComposeSchema {
180 fn compose(
186 components: &mut Components,
187 generics: Vec<RefOr<schema::Schema>>,
188 ) -> RefOr<schema::Schema>;
189}
190
191#[derive(Debug, Clone, Default)]
196pub struct SchemaReference {
197 pub name: std::borrow::Cow<'static, str>,
199 pub inline: bool,
201 pub references: Vec<Self>,
203}
204
205impl SchemaReference {
206 pub fn new(name: impl Into<std::borrow::Cow<'static, str>>) -> Self {
208 Self {
209 name: name.into(),
210 inline: false,
211 references: Vec::new(),
212 }
213 }
214
215 #[must_use]
217 pub fn inline(mut self, inline: bool) -> Self {
218 self.inline = inline;
219 self
220 }
221
222 #[must_use]
224 pub fn reference(mut self, reference: Self) -> Self {
225 self.references.push(reference);
226 self
227 }
228
229 #[must_use]
233 pub fn display_name(&self) -> String {
234 if self.references.is_empty() {
235 self.name.as_ref().to_owned()
236 } else {
237 let generic_names: Vec<String> =
238 self.references.iter().map(|r| r.display_name()).collect();
239 format!("{}<{}>", self.name, generic_names.join(", "))
240 }
241 }
242
243 #[must_use]
245 pub fn generic_params(&self) -> &[Self] {
246 &self.references
247 }
248
249 #[must_use]
251 pub fn child_references(&self) -> Vec<&Self> {
252 let mut result = Vec::new();
253 for reference in &self.references {
254 result.push(reference);
255 result.extend(reference.child_references());
256 }
257 result
258 }
259}
260
261pub type TupleUnit = ();
267
268impl ToSchema for TupleUnit {
269 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
270 schema::empty().into()
271 }
272}
273impl ComposeSchema for TupleUnit {
274 fn compose(
275 _components: &mut Components,
276 _generics: Vec<RefOr<schema::Schema>>,
277 ) -> RefOr<schema::Schema> {
278 schema::empty().into()
279 }
280}
281
282macro_rules! impl_to_schema {
283 ($ty:path) => {
284 impl_to_schema!( @impl_schema $ty );
285 };
286 (&$ty:path) => {
287 impl_to_schema!( @impl_schema &$ty );
288 };
289 (@impl_schema $($tt:tt)*) => {
290 impl ToSchema for $($tt)* {
291 fn to_schema(_components: &mut Components) -> crate::RefOr<crate::schema::Schema> {
292 schema!( $($tt)* ).into()
293 }
294 }
295 impl ComposeSchema for $($tt)* {
296 fn compose(_components: &mut Components, _generics: Vec<crate::RefOr<crate::schema::Schema>>) -> crate::RefOr<crate::schema::Schema> {
297 schema!( $($tt)* ).into()
298 }
299 }
300 };
301}
302
303macro_rules! impl_to_schema_primitive {
304 ($($tt:path),*) => {
305 $( impl_to_schema!( $tt ); )*
306 };
307}
308
309#[doc(hidden)]
312pub mod oapi {
313 pub use super::*;
314}
315
316#[doc(hidden)]
317pub mod __private {
318 pub use inventory;
319 pub use serde_json;
320}
321
322#[rustfmt::skip]
323impl_to_schema_primitive!(
324 i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, bool, f32, f64, String, str, char
325);
326impl_to_schema!(&str);
327
328impl_to_schema!(std::net::Ipv4Addr);
329impl_to_schema!(std::net::Ipv6Addr);
330
331impl_to_schema_primitive!(
332 std::ffi::OsStr,
333 std::ffi::OsString,
334 std::path::Path,
335 std::path::PathBuf
336);
337impl_to_schema!(&std::ffi::OsStr);
338impl_to_schema!(&std::path::Path);
339
340impl ToSchema for std::net::IpAddr {
341 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
342 crate::RefOr::Type(Schema::OneOf(
343 OneOf::default()
344 .item(std::net::Ipv4Addr::to_schema(components))
345 .item(std::net::Ipv6Addr::to_schema(components)),
346 ))
347 }
348}
349impl ComposeSchema for std::net::IpAddr {
350 fn compose(
351 components: &mut Components,
352 _generics: Vec<RefOr<schema::Schema>>,
353 ) -> RefOr<schema::Schema> {
354 Self::to_schema(components)
355 }
356}
357
358#[cfg(feature = "chrono")]
359impl_to_schema_primitive!(chrono::NaiveDate, chrono::Duration, chrono::NaiveDateTime);
360#[cfg(feature = "chrono")]
361impl<T: chrono::TimeZone> ToSchema for chrono::DateTime<T> {
362 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
363 schema!(#[inline] DateTime<T>).into()
364 }
365}
366#[cfg(feature = "chrono")]
367impl<T: chrono::TimeZone> ComposeSchema for chrono::DateTime<T> {
368 fn compose(
369 _components: &mut Components,
370 _generics: Vec<RefOr<schema::Schema>>,
371 ) -> RefOr<schema::Schema> {
372 schema!(#[inline] DateTime<T>).into()
373 }
374}
375#[cfg(feature = "compact_str")]
376impl_to_schema_primitive!(compact_str::CompactString);
377#[cfg(any(feature = "decimal", feature = "decimal-float"))]
378impl_to_schema!(rust_decimal::Decimal);
379#[cfg(feature = "url")]
380impl_to_schema!(url::Url);
381#[cfg(feature = "uuid")]
382impl_to_schema!(uuid::Uuid);
383#[cfg(feature = "ulid")]
384impl_to_schema!(ulid::Ulid);
385#[cfg(feature = "time")]
386impl_to_schema_primitive!(
387 time::Date,
388 time::PrimitiveDateTime,
389 time::OffsetDateTime,
390 time::Duration
391);
392#[cfg(feature = "smallvec")]
393impl<T: ToSchema + smallvec::Array> ToSchema for smallvec::SmallVec<T> {
394 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
395 schema!(#[inline] smallvec::SmallVec<T>).into()
396 }
397}
398#[cfg(feature = "smallvec")]
399impl<T: ComposeSchema + smallvec::Array> ComposeSchema for smallvec::SmallVec<T> {
400 fn compose(
401 components: &mut Components,
402 generics: Vec<RefOr<schema::Schema>>,
403 ) -> RefOr<schema::Schema> {
404 let t_schema = generics
405 .first()
406 .cloned()
407 .unwrap_or_else(|| T::compose(components, vec![]));
408 schema::Array::new().items(t_schema).into()
409 }
410}
411#[cfg(feature = "indexmap")]
412impl<K: ToSchema, V: ToSchema> ToSchema for indexmap::IndexMap<K, V> {
413 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
414 schema!(#[inline] indexmap::IndexMap<K, V>).into()
415 }
416}
417#[cfg(feature = "indexmap")]
418impl<K: ComposeSchema, V: ComposeSchema> ComposeSchema for indexmap::IndexMap<K, V> {
419 fn compose(
420 components: &mut Components,
421 generics: Vec<RefOr<schema::Schema>>,
422 ) -> RefOr<schema::Schema> {
423 let v_schema = generics
424 .get(1)
425 .cloned()
426 .unwrap_or_else(|| V::compose(components, vec![]));
427 schema::Object::new().additional_properties(v_schema).into()
428 }
429}
430
431impl<T: ToSchema> ToSchema for Vec<T> {
432 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
433 schema!(#[inline] Vec<T>).into()
434 }
435}
436impl<T: ComposeSchema> ComposeSchema for Vec<T> {
437 fn compose(
438 components: &mut Components,
439 generics: Vec<RefOr<schema::Schema>>,
440 ) -> RefOr<schema::Schema> {
441 let t_schema = generics
442 .first()
443 .cloned()
444 .unwrap_or_else(|| T::compose(components, vec![]));
445 schema::Array::new().items(t_schema).into()
446 }
447}
448
449impl<T: ToSchema> ToSchema for LinkedList<T> {
450 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
451 schema!(#[inline] LinkedList<T>).into()
452 }
453}
454impl<T: ComposeSchema> ComposeSchema for LinkedList<T> {
455 fn compose(
456 components: &mut Components,
457 generics: Vec<RefOr<schema::Schema>>,
458 ) -> RefOr<schema::Schema> {
459 let t_schema = generics
460 .first()
461 .cloned()
462 .unwrap_or_else(|| T::compose(components, vec![]));
463 schema::Array::new().items(t_schema).into()
464 }
465}
466
467impl<T: ToSchema> ToSchema for HashSet<T> {
468 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
469 schema::Array::new()
470 .items(T::to_schema(components))
471 .unique_items(true)
472 .into()
473 }
474}
475impl<T: ComposeSchema> ComposeSchema for HashSet<T> {
476 fn compose(
477 components: &mut Components,
478 generics: Vec<RefOr<schema::Schema>>,
479 ) -> RefOr<schema::Schema> {
480 let t_schema = generics
481 .first()
482 .cloned()
483 .unwrap_or_else(|| T::compose(components, vec![]));
484 schema::Array::new()
485 .items(t_schema)
486 .unique_items(true)
487 .into()
488 }
489}
490
491impl<T: ToSchema> ToSchema for BTreeSet<T> {
492 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
493 schema::Array::new()
494 .items(T::to_schema(components))
495 .unique_items(true)
496 .into()
497 }
498}
499impl<T: ComposeSchema> ComposeSchema for BTreeSet<T> {
500 fn compose(
501 components: &mut Components,
502 generics: Vec<RefOr<schema::Schema>>,
503 ) -> RefOr<schema::Schema> {
504 let t_schema = generics
505 .first()
506 .cloned()
507 .unwrap_or_else(|| T::compose(components, vec![]));
508 schema::Array::new()
509 .items(t_schema)
510 .unique_items(true)
511 .into()
512 }
513}
514
515#[cfg(feature = "indexmap")]
516impl<T: ToSchema> ToSchema for indexmap::IndexSet<T> {
517 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
518 schema::Array::new()
519 .items(T::to_schema(components))
520 .unique_items(true)
521 .into()
522 }
523}
524#[cfg(feature = "indexmap")]
525impl<T: ComposeSchema> ComposeSchema for indexmap::IndexSet<T> {
526 fn compose(
527 components: &mut Components,
528 generics: Vec<RefOr<schema::Schema>>,
529 ) -> RefOr<schema::Schema> {
530 let t_schema = generics
531 .first()
532 .cloned()
533 .unwrap_or_else(|| T::compose(components, vec![]));
534 schema::Array::new()
535 .items(t_schema)
536 .unique_items(true)
537 .into()
538 }
539}
540
541impl<T: ToSchema> ToSchema for Box<T> {
542 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
543 T::to_schema(components)
544 }
545}
546impl<T: ComposeSchema> ComposeSchema for Box<T> {
547 fn compose(
548 components: &mut Components,
549 generics: Vec<RefOr<schema::Schema>>,
550 ) -> RefOr<schema::Schema> {
551 T::compose(components, generics)
552 }
553}
554
555impl<T: ToSchema + ToOwned> ToSchema for std::borrow::Cow<'_, T> {
556 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
557 T::to_schema(components)
558 }
559}
560impl<T: ComposeSchema + ToOwned> ComposeSchema for std::borrow::Cow<'_, T> {
561 fn compose(
562 components: &mut Components,
563 generics: Vec<RefOr<schema::Schema>>,
564 ) -> RefOr<schema::Schema> {
565 T::compose(components, generics)
566 }
567}
568
569impl<T: ToSchema> ToSchema for std::cell::RefCell<T> {
570 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
571 T::to_schema(components)
572 }
573}
574impl<T: ComposeSchema> ComposeSchema for std::cell::RefCell<T> {
575 fn compose(
576 components: &mut Components,
577 generics: Vec<RefOr<schema::Schema>>,
578 ) -> RefOr<schema::Schema> {
579 T::compose(components, generics)
580 }
581}
582
583impl<T: ToSchema> ToSchema for std::rc::Rc<T> {
584 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
585 T::to_schema(components)
586 }
587}
588impl<T: ComposeSchema> ComposeSchema for std::rc::Rc<T> {
589 fn compose(
590 components: &mut Components,
591 generics: Vec<RefOr<schema::Schema>>,
592 ) -> RefOr<schema::Schema> {
593 T::compose(components, generics)
594 }
595}
596
597impl<T: ToSchema> ToSchema for std::sync::Arc<T> {
598 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
599 T::to_schema(components)
600 }
601}
602impl<T: ComposeSchema> ComposeSchema for std::sync::Arc<T> {
603 fn compose(
604 components: &mut Components,
605 generics: Vec<RefOr<schema::Schema>>,
606 ) -> RefOr<schema::Schema> {
607 T::compose(components, generics)
608 }
609}
610
611impl<T: ToSchema> ToSchema for [T] {
612 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
613 schema!(
614 #[inline]
615 [T]
616 )
617 .into()
618 }
619}
620impl<T: ComposeSchema> ComposeSchema for [T] {
621 fn compose(
622 components: &mut Components,
623 generics: Vec<RefOr<schema::Schema>>,
624 ) -> RefOr<schema::Schema> {
625 let t_schema = generics
626 .first()
627 .cloned()
628 .unwrap_or_else(|| T::compose(components, vec![]));
629 schema::Array::new().items(t_schema).into()
630 }
631}
632
633impl<T: ToSchema, const N: usize> ToSchema for [T; N] {
634 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
635 schema!(
636 #[inline]
637 [T; N]
638 )
639 .into()
640 }
641}
642impl<T: ComposeSchema, const N: usize> ComposeSchema for [T; N] {
643 fn compose(
644 components: &mut Components,
645 generics: Vec<RefOr<schema::Schema>>,
646 ) -> RefOr<schema::Schema> {
647 let t_schema = generics
648 .first()
649 .cloned()
650 .unwrap_or_else(|| T::compose(components, vec![]));
651 schema::Array::new().items(t_schema).into()
652 }
653}
654
655impl<T: ToSchema> ToSchema for &[T] {
656 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
657 schema!(
658 #[inline]
659 &[T]
660 )
661 .into()
662 }
663}
664impl<T: ComposeSchema> ComposeSchema for &[T] {
665 fn compose(
666 components: &mut Components,
667 generics: Vec<RefOr<schema::Schema>>,
668 ) -> RefOr<schema::Schema> {
669 let t_schema = generics
670 .first()
671 .cloned()
672 .unwrap_or_else(|| T::compose(components, vec![]));
673 schema::Array::new().items(t_schema).into()
674 }
675}
676
677impl<T: ToSchema> ToSchema for Option<T> {
678 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
679 schema!(#[inline] Option<T>).into()
680 }
681}
682impl<T: ComposeSchema> ComposeSchema for Option<T> {
683 fn compose(
684 components: &mut Components,
685 generics: Vec<RefOr<schema::Schema>>,
686 ) -> RefOr<schema::Schema> {
687 let t_schema = generics
688 .first()
689 .cloned()
690 .unwrap_or_else(|| T::compose(components, vec![]));
691 schema::OneOf::new()
692 .item(t_schema)
693 .item(schema::Object::new().schema_type(schema::BasicType::Null))
694 .into()
695 }
696}
697
698impl<T> ToSchema for PhantomData<T> {
699 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
700 Schema::Object(Box::default()).into()
701 }
702}
703impl<T> ComposeSchema for PhantomData<T> {
704 fn compose(
705 _components: &mut Components,
706 _generics: Vec<RefOr<schema::Schema>>,
707 ) -> RefOr<schema::Schema> {
708 Schema::Object(Box::default()).into()
709 }
710}
711
712impl<K: ToSchema, V: ToSchema> ToSchema for BTreeMap<K, V> {
713 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
714 schema!(#[inline]BTreeMap<K, V>).into()
715 }
716}
717impl<K: ComposeSchema, V: ComposeSchema> ComposeSchema for BTreeMap<K, V> {
718 fn compose(
719 components: &mut Components,
720 generics: Vec<RefOr<schema::Schema>>,
721 ) -> RefOr<schema::Schema> {
722 let v_schema = generics
723 .get(1)
724 .cloned()
725 .unwrap_or_else(|| V::compose(components, vec![]));
726 schema::Object::new().additional_properties(v_schema).into()
727 }
728}
729
730impl<K: ToSchema, V: ToSchema> ToSchema for HashMap<K, V> {
731 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
732 schema!(#[inline]HashMap<K, V>).into()
733 }
734}
735impl<K: ComposeSchema, V: ComposeSchema> ComposeSchema for HashMap<K, V> {
736 fn compose(
737 components: &mut Components,
738 generics: Vec<RefOr<schema::Schema>>,
739 ) -> RefOr<schema::Schema> {
740 let v_schema = generics
741 .get(1)
742 .cloned()
743 .unwrap_or_else(|| V::compose(components, vec![]));
744 schema::Object::new().additional_properties(v_schema).into()
745 }
746}
747
748impl ToSchema for StatusError {
749 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
750 let name = crate::naming::assign_name::<Self>(Default::default());
751 let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
752 if !components.schemas.contains_key(&name) {
753 components.schemas.insert(name.clone(), ref_or.clone());
754 let schema = Schema::from(
755 Object::new()
756 .property("code", u16::to_schema(components))
757 .required("code")
758 .required("name")
759 .property("name", String::to_schema(components))
760 .required("brief")
761 .property("brief", String::to_schema(components))
762 .required("detail")
763 .property("detail", String::to_schema(components))
764 .property("cause", String::to_schema(components)),
765 );
766 components.schemas.insert(name, schema);
767 }
768 ref_or
769 }
770}
771impl ComposeSchema for StatusError {
772 fn compose(
773 components: &mut Components,
774 _generics: Vec<RefOr<schema::Schema>>,
775 ) -> RefOr<schema::Schema> {
776 Self::to_schema(components)
777 }
778}
779
780#[cfg(feature = "rfc9457")]
781fn problem_base_schema(components: &mut Components) -> RefOr<schema::Schema> {
782 let uri_reference = || {
783 Object::new()
784 .schema_type(schema::BasicType::String)
785 .format(SchemaFormat::Custom("uri-reference".into()))
786 };
787 let status = Object::new()
788 .schema_type(schema::BasicType::Integer)
789 .format(SchemaFormat::KnownFormat(schema::KnownFormat::Int32))
790 .minimum(100)
791 .maximum(599);
792 Object::new()
793 .property("type", uri_reference())
794 .required("type")
795 .property("title", String::to_schema(components))
796 .required("title")
797 .property("status", status)
798 .required("status")
799 .property("detail", String::to_schema(components))
800 .property("instance", uri_reference())
801 .into()
802}
803
804#[cfg(feature = "rfc9457")]
805fn problem_schema_with_extensions(
806 components: &Components,
807 base: RefOr<schema::Schema>,
808 extensions: RefOr<schema::Schema>,
809) -> RefOr<schema::Schema> {
810 let extension_schema = match &extensions {
811 RefOr::Type(schema) => Some(schema),
812 RefOr::Ref(reference) => reference
813 .ref_location
814 .strip_prefix("#/components/schemas/")
815 .and_then(|name| components.schemas.get(name))
816 .and_then(|schema| match schema {
817 RefOr::Type(schema) => Some(schema),
818 RefOr::Ref(_) => None,
819 }),
820 };
821
822 if let Some(schema::Schema::Object(extension)) = extension_schema {
823 let RefOr::Type(schema::Schema::Object(base)) = base else {
824 unreachable!("problem base schema must be an object")
825 };
826 let mut extension = extension.clone();
827 extension.properties.extend(base.properties);
828 extension.required.extend(base.required);
829 return RefOr::Type(schema::Schema::Object(extension));
830 }
831
832 schema::AllOf::new().item(base).item(extensions).into()
833}
834
835#[cfg(feature = "rfc9457")]
836impl ToSchema for NoExtensions {
837 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
838 Object::new().schema_type(schema::BasicType::Object).into()
839 }
840}
841
842#[cfg(feature = "rfc9457")]
843impl ComposeSchema for NoExtensions {
844 fn compose(
845 components: &mut Components,
846 _generics: Vec<RefOr<schema::Schema>>,
847 ) -> RefOr<schema::Schema> {
848 Self::to_schema(components)
849 }
850}
851
852#[cfg(feature = "rfc9457")]
853impl<Extensions> ToSchema for Problem<Extensions>
854where
855 Extensions: ToSchema + 'static,
856{
857 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
858 let name_rule = if TypeId::of::<Extensions>() == TypeId::of::<NoExtensions>() {
859 crate::naming::NameRule::Force("Problem")
860 } else {
861 Default::default()
862 };
863 let name = crate::naming::assign_name::<Self>(name_rule);
864 let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
865 if !components.schemas.contains_key(&name) {
866 components.schemas.insert(name.clone(), ref_or.clone());
867 let schema = if TypeId::of::<Extensions>() == TypeId::of::<NoExtensions>() {
868 problem_base_schema(components)
869 } else {
870 let extensions = Extensions::to_schema(components);
871 let base = problem_base_schema(components);
872 problem_schema_with_extensions(components, base, extensions)
873 };
874 components.schemas.insert(name, schema);
875 }
876 ref_or
877 }
878}
879
880#[cfg(feature = "rfc9457")]
881impl<Extensions> ComposeSchema for Problem<Extensions>
882where
883 Extensions: ComposeSchema + 'static,
884{
885 fn compose(
886 components: &mut Components,
887 generics: Vec<RefOr<schema::Schema>>,
888 ) -> RefOr<schema::Schema> {
889 let base = problem_base_schema(components);
890 if TypeId::of::<Extensions>() == TypeId::of::<NoExtensions>() {
891 base
892 } else {
893 let extensions = generics
894 .first()
895 .cloned()
896 .unwrap_or_else(|| Extensions::compose(components, vec![]));
897 problem_schema_with_extensions(components, base, extensions)
898 }
899 }
900}
901
902impl ToSchema for salvo_core::Error {
903 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
904 StatusError::to_schema(components)
905 }
906}
907impl ComposeSchema for salvo_core::Error {
908 fn compose(
909 components: &mut Components,
910 _generics: Vec<RefOr<schema::Schema>>,
911 ) -> RefOr<schema::Schema> {
912 Self::to_schema(components)
913 }
914}
915
916impl<T, E> ToSchema for Result<T, E>
917where
918 T: ToSchema,
919 E: ToSchema,
920{
921 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
922 let name = crate::naming::assign_name::<StatusError>(Default::default());
923 let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
924 if !components.schemas.contains_key(&name) {
925 components.schemas.insert(name.clone(), ref_or.clone());
926 let schema = OneOf::new()
927 .item(T::to_schema(components))
928 .item(E::to_schema(components));
929 components.schemas.insert(name, schema);
930 }
931 ref_or
932 }
933}
934impl<T, E> ComposeSchema for Result<T, E>
935where
936 T: ComposeSchema,
937 E: ComposeSchema,
938{
939 fn compose(
940 components: &mut Components,
941 generics: Vec<RefOr<schema::Schema>>,
942 ) -> RefOr<schema::Schema> {
943 let t_schema = generics
944 .first()
945 .cloned()
946 .unwrap_or_else(|| T::compose(components, vec![]));
947 let e_schema = generics
948 .get(1)
949 .cloned()
950 .unwrap_or_else(|| E::compose(components, vec![]));
951 OneOf::new().item(t_schema).item(e_schema).into()
952 }
953}
954
955impl ToSchema for serde_json::Value {
956 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
957 Schema::Object(Box::default()).into()
958 }
959}
960impl ComposeSchema for serde_json::Value {
961 fn compose(
962 _components: &mut Components,
963 _generics: Vec<RefOr<schema::Schema>>,
964 ) -> RefOr<schema::Schema> {
965 Schema::Object(Box::default()).into()
966 }
967}
968
969impl ToSchema for serde_json::Map<String, serde_json::Value> {
970 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
971 Schema::Object(Box::new(schema::Object::new())).into()
972 }
973}
974impl ComposeSchema for serde_json::Map<String, serde_json::Value> {
975 fn compose(
976 _components: &mut Components,
977 _generics: Vec<RefOr<schema::Schema>>,
978 ) -> RefOr<schema::Schema> {
979 Schema::Object(Box::new(schema::Object::new())).into()
980 }
981}
982
983impl ToSchema for serde_json::value::RawValue {
984 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
985 Schema::Object(Box::default()).into()
986 }
987}
988impl ComposeSchema for serde_json::value::RawValue {
989 fn compose(
990 _components: &mut Components,
991 _generics: Vec<RefOr<schema::Schema>>,
992 ) -> RefOr<schema::Schema> {
993 Schema::Object(Box::default()).into()
994 }
995}
996
997pub trait ToParameters<'de>: Extractible<'de> {
1096 fn to_parameters(components: &mut Components) -> Parameters;
1099}
1100
1101pub trait ToParameter {
1103 fn to_parameter(components: &mut Components) -> Parameter;
1105}
1106
1107pub trait ToRequestBody {
1140 fn to_request_body(components: &mut Components) -> RequestBody;
1142}
1143
1144pub trait ToResponses {
1168 fn to_responses(components: &mut Components) -> Responses;
1170}
1171
1172impl<C> ToResponses for writing::Json<C>
1173where
1174 C: ToSchema,
1175{
1176 fn to_responses(components: &mut Components) -> Responses {
1177 Responses::new().response(
1178 "200",
1179 Response::new("JSON response body")
1180 .add_content("application/json", Content::new(C::to_schema(components))),
1181 )
1182 }
1183}
1184
1185impl ToResponses for StatusError {
1186 fn to_responses(components: &mut Components) -> Responses {
1187 let mut responses = Responses::new();
1188 let errors = vec![
1189 Self::bad_request(),
1190 Self::unauthorized(),
1191 Self::payment_required(),
1192 Self::forbidden(),
1193 Self::not_found(),
1194 Self::method_not_allowed(),
1195 Self::not_acceptable(),
1196 Self::proxy_authentication_required(),
1197 Self::request_timeout(),
1198 Self::conflict(),
1199 Self::gone(),
1200 Self::length_required(),
1201 Self::precondition_failed(),
1202 Self::payload_too_large(),
1203 Self::uri_too_long(),
1204 Self::unsupported_media_type(),
1205 Self::range_not_satisfiable(),
1206 Self::expectation_failed(),
1207 Self::im_a_teapot(),
1208 Self::misdirected_request(),
1209 Self::unprocessable_entity(),
1210 Self::locked(),
1211 Self::failed_dependency(),
1212 Self::upgrade_required(),
1213 Self::precondition_required(),
1214 Self::too_many_requests(),
1215 Self::request_header_fields_too_large(),
1216 Self::unavailable_for_legal_reasons(),
1217 Self::internal_server_error(),
1218 Self::not_implemented(),
1219 Self::bad_gateway(),
1220 Self::service_unavailable(),
1221 Self::gateway_timeout(),
1222 Self::http_version_not_supported(),
1223 Self::variant_also_negotiates(),
1224 Self::insufficient_storage(),
1225 Self::loop_detected(),
1226 Self::not_extended(),
1227 Self::network_authentication_required(),
1228 ];
1229 for Self { code, brief, .. } in errors {
1230 responses.insert(
1231 code.as_str(),
1232 Response::new(brief).add_content(
1233 "application/json",
1234 Content::new(Self::to_schema(components)),
1235 ),
1236 )
1237 }
1238 responses
1239 }
1240}
1241
1242#[cfg(feature = "rfc9457")]
1243impl<Extensions> ToResponses for Problem<Extensions>
1244where
1245 Extensions: ToSchema + 'static,
1246{
1247 fn to_responses(components: &mut Components) -> Responses {
1248 Responses::new().response(
1249 "default",
1250 Response::new("RFC 9457 problem details response").add_content(
1251 salvo_core::http::PROBLEM_JSON,
1252 Content::new(Self::to_schema(components)),
1253 ),
1254 )
1255 }
1256}
1257impl ToResponses for salvo_core::Error {
1258 fn to_responses(components: &mut Components) -> Responses {
1259 StatusError::to_responses(components)
1260 }
1261}
1262
1263pub trait ToResponse {
1283 fn to_response(components: &mut Components) -> RefOr<crate::Response>;
1285}
1286
1287impl<C> ToResponse for writing::Json<C>
1288where
1289 C: ToSchema,
1290{
1291 fn to_response(components: &mut Components) -> RefOr<Response> {
1292 let schema = <C as ToSchema>::to_schema(components);
1293 Response::new("Response with json format data")
1294 .add_content("application/json", Content::new(schema))
1295 .into()
1296 }
1297}
1298
1299#[cfg(test)]
1300mod tests {
1301 use assert_json_diff::assert_json_eq;
1302 use serde_json::json;
1303
1304 use super::*;
1305
1306 #[cfg(feature = "rfc9457")]
1307 #[test]
1308 fn test_problem_schema_and_response_media_type() {
1309 let mut components = Components::new();
1310 let schema_ref = salvo_core::http::PlainProblem::to_schema(&mut components);
1311 let RefOr::Ref(schema_ref) = schema_ref else {
1312 panic!("problem schema should use a component reference");
1313 };
1314 let name = schema_ref
1315 .ref_location
1316 .rsplit('/')
1317 .next()
1318 .expect("component reference should have a name");
1319 let schema = components
1320 .schemas
1321 .get(name)
1322 .expect("problem component should exist");
1323 let schema = serde_json::to_value(schema).expect("schema should serialize");
1324
1325 assert_eq!(schema["type"], "object");
1326 assert_eq!(schema["properties"]["type"]["format"], "uri-reference");
1327 assert_eq!(schema["properties"]["instance"]["format"], "uri-reference");
1328 assert_eq!(schema["properties"]["status"]["minimum"], 100);
1329 assert_eq!(schema["properties"]["status"]["maximum"], 599);
1330 assert_eq!(schema["required"], json!(["type", "title", "status"]));
1331
1332 let responses = salvo_core::http::PlainProblem::to_responses(&mut components);
1333 let response = responses
1334 .get("default")
1335 .expect("problem should register a default response");
1336 let response = serde_json::to_value(response).expect("response should serialize");
1337 assert!(
1338 response["content"]
1339 .get("application/problem+json")
1340 .is_some()
1341 );
1342 }
1343
1344 #[cfg(feature = "rfc9457")]
1345 #[test]
1346 fn test_problem_schema_composes_typed_extensions() {
1347 #[derive(serde::Serialize, ToSchema)]
1348 #[serde(deny_unknown_fields)]
1349 #[allow(dead_code)]
1350 struct ValidationExtensions {
1351 errors: Vec<String>,
1352 }
1353
1354 let mut components = Components::new();
1355 let schema_ref = Problem::<ValidationExtensions>::to_schema(&mut components);
1356 let RefOr::Ref(schema_ref) = schema_ref else {
1357 panic!("problem schema should use a component reference");
1358 };
1359 let name = schema_ref
1360 .ref_location
1361 .rsplit('/')
1362 .next()
1363 .expect("component reference should have a name");
1364 let schema = components
1365 .schemas
1366 .get(name)
1367 .expect("typed problem component should exist");
1368 let schema = serde_json::to_value(schema).expect("schema should serialize");
1369
1370 assert!(schema.get("allOf").is_none());
1371 assert_eq!(schema["type"], "object");
1372 assert_eq!(schema["additionalProperties"], false);
1373 assert_eq!(schema["properties"]["errors"]["type"], "array");
1374 assert_eq!(schema["properties"]["status"]["type"], "integer");
1375 assert_eq!(
1376 schema["required"],
1377 json!(["errors", "type", "title", "status"])
1378 );
1379 }
1380
1381 #[test]
1382 fn test_primitive_schema() {
1383 let mut components = Components::new();
1384
1385 let non_strict = cfg!(feature = "non-strict-integers");
1389
1390 for (name, schema, value) in [
1391 (
1392 "i8",
1393 i8::to_schema(&mut components),
1394 if non_strict {
1395 json!({"type": "integer", "format": "int8"})
1396 } else {
1397 json!({"type": "integer", "format": "int32"})
1398 },
1399 ),
1400 (
1401 "i16",
1402 i16::to_schema(&mut components),
1403 if non_strict {
1404 json!({"type": "integer", "format": "int16"})
1405 } else {
1406 json!({"type": "integer", "format": "int32"})
1407 },
1408 ),
1409 (
1410 "i32",
1411 i32::to_schema(&mut components),
1412 json!({"type": "integer", "format": "int32"}),
1413 ),
1414 (
1415 "i64",
1416 i64::to_schema(&mut components),
1417 json!({"type": "integer", "format": "int64"}),
1418 ),
1419 (
1420 "i128",
1421 i128::to_schema(&mut components),
1422 json!({"type": "integer"}),
1423 ),
1424 (
1425 "isize",
1426 isize::to_schema(&mut components),
1427 json!({"type": "integer"}),
1428 ),
1429 (
1430 "u8",
1431 u8::to_schema(&mut components),
1432 if non_strict {
1433 json!({"type": "integer", "format": "uint8", "minimum": 0})
1434 } else {
1435 json!({"type": "integer", "format": "int32", "minimum": 0})
1436 },
1437 ),
1438 (
1439 "u16",
1440 u16::to_schema(&mut components),
1441 if non_strict {
1442 json!({"type": "integer", "format": "uint16", "minimum": 0})
1443 } else {
1444 json!({"type": "integer", "format": "int32", "minimum": 0})
1445 },
1446 ),
1447 (
1448 "u32",
1449 u32::to_schema(&mut components),
1450 if non_strict {
1451 json!({"type": "integer", "format": "uint32", "minimum": 0})
1452 } else {
1453 json!({"type": "integer", "format": "int32", "minimum": 0})
1454 },
1455 ),
1456 (
1457 "u64",
1458 u64::to_schema(&mut components),
1459 if non_strict {
1460 json!({"type": "integer", "format": "uint64", "minimum": 0})
1461 } else {
1462 json!({"type": "integer", "format": "int64", "minimum": 0})
1463 },
1464 ),
1465 (
1466 "u128",
1467 u128::to_schema(&mut components),
1468 json!({"type": "integer", "minimum": 0}),
1469 ),
1470 (
1471 "usize",
1472 usize::to_schema(&mut components),
1473 json!({"type": "integer", "minimum": 0}),
1474 ),
1475 (
1476 "bool",
1477 bool::to_schema(&mut components),
1478 json!({"type": "boolean"}),
1479 ),
1480 (
1481 "str",
1482 str::to_schema(&mut components),
1483 json!({"type": "string"}),
1484 ),
1485 (
1486 "String",
1487 String::to_schema(&mut components),
1488 json!({"type": "string"}),
1489 ),
1490 (
1491 "char",
1492 char::to_schema(&mut components),
1493 json!({"type": "string"}),
1494 ),
1495 (
1496 "OsStr",
1497 std::ffi::OsStr::to_schema(&mut components),
1498 json!({"type": "string"}),
1499 ),
1500 (
1501 "&OsStr",
1502 <&std::ffi::OsStr>::to_schema(&mut components),
1503 json!({"type": "string"}),
1504 ),
1505 (
1506 "OsString",
1507 std::ffi::OsString::to_schema(&mut components),
1508 json!({"type": "string"}),
1509 ),
1510 (
1511 "Path",
1512 std::path::Path::to_schema(&mut components),
1513 json!({"type": "string"}),
1514 ),
1515 (
1516 "&Path",
1517 <&std::path::Path>::to_schema(&mut components),
1518 json!({"type": "string"}),
1519 ),
1520 (
1521 "PathBuf",
1522 std::path::PathBuf::to_schema(&mut components),
1523 json!({"type": "string"}),
1524 ),
1525 (
1526 "f32",
1527 f32::to_schema(&mut components),
1528 json!({"type": "number", "format": "float"}),
1529 ),
1530 (
1531 "f64",
1532 f64::to_schema(&mut components),
1533 json!({"type": "number", "format": "double"}),
1534 ),
1535 ] {
1536 println!(
1537 "{name}: {json}",
1538 json = serde_json::to_string(&schema).unwrap()
1539 );
1540 let schema = serde_json::to_value(schema).unwrap();
1541 assert_json_eq!(schema, value);
1542 }
1543 }
1544}