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
6#[macro_use]
7mod cfg;
8
9mod openapi;
10pub use openapi::*;
11
12#[doc = include_str!("../docs/endpoint.md")]
13pub mod endpoint;
14pub use endpoint::{Endpoint, EndpointArgRegister, EndpointOutRegister, EndpointRegistry};
15pub mod extract;
16mod routing;
17pub use routing::RouterExt;
18pub mod naming;
20
21cfg_feature! {
22 #![feature ="swagger-ui"]
23 pub mod swagger_ui;
24}
25cfg_feature! {
26 #![feature ="scalar"]
27 pub mod scalar;
28}
29cfg_feature! {
30 #![feature ="rapidoc"]
31 pub mod rapidoc;
32}
33cfg_feature! {
34 #![feature ="redoc"]
35 pub mod redoc;
36}
37
38use std::collections::{BTreeMap, HashMap, LinkedList};
39use std::marker::PhantomData;
40
41use salvo_core::extract::Extractible;
42use salvo_core::http::StatusError;
43use salvo_core::writing;
44#[doc = include_str!("../docs/derive_to_parameters.md")]
45pub use salvo_oapi_macros::ToParameters;
46#[doc = include_str!("../docs/derive_to_response.md")]
47pub use salvo_oapi_macros::ToResponse;
48#[doc = include_str!("../docs/derive_to_responses.md")]
49pub use salvo_oapi_macros::ToResponses;
50#[doc = include_str!("../docs/derive_to_schema.md")]
51pub use salvo_oapi_macros::ToSchema;
52#[doc = include_str!("../docs/endpoint.md")]
53pub use salvo_oapi_macros::endpoint;
54pub(crate) use salvo_oapi_macros::schema;
55
56use crate::oapi::openapi::schema::OneOf;
57
58extern crate self as salvo_oapi;
60
61pub trait ToSchema {
127 fn to_schema(components: &mut Components) -> RefOr<schema::Schema>;
130
131 }
139
140pub type TupleUnit = ();
146
147impl ToSchema for TupleUnit {
148 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
149 schema::empty().into()
150 }
151}
152
153macro_rules! impl_to_schema {
154 ($ty:path) => {
155 impl_to_schema!( @impl_schema $ty );
156 };
157 (&$ty:path) => {
158 impl_to_schema!( @impl_schema &$ty );
159 };
160 (@impl_schema $($tt:tt)*) => {
161 impl ToSchema for $($tt)* {
162 fn to_schema(_components: &mut Components) -> crate::RefOr<crate::schema::Schema> {
163 schema!( $($tt)* ).into()
164 }
165 }
166 };
167}
168
169macro_rules! impl_to_schema_primitive {
170 ($($tt:path),*) => {
171 $( impl_to_schema!( $tt ); )*
172 };
173}
174
175#[doc(hidden)]
178pub mod oapi {
179 pub use super::*;
180}
181
182#[doc(hidden)]
183pub mod __private {
184 pub use {inventory, serde_json};
185}
186
187#[rustfmt::skip]
188impl_to_schema_primitive!(
189 i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, bool, f32, f64, String, str, char
190);
191impl_to_schema!(&str);
192
193impl_to_schema!(std::net::Ipv4Addr);
194impl_to_schema!(std::net::Ipv6Addr);
195
196impl ToSchema for std::net::IpAddr {
197 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
198 crate::RefOr::Type(Schema::OneOf(
199 OneOf::default()
200 .item(std::net::Ipv4Addr::to_schema(components))
201 .item(std::net::Ipv6Addr::to_schema(components)),
202 ))
203 }
204}
205
206#[cfg(feature = "chrono")]
207impl_to_schema_primitive!(chrono::NaiveDate, chrono::Duration, chrono::NaiveDateTime);
208#[cfg(feature = "chrono")]
209impl<T: chrono::TimeZone> ToSchema for chrono::DateTime<T> {
210 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
211 schema!(#[inline] DateTime<T>).into()
212 }
213}
214#[cfg(feature = "compact_str")]
215impl_to_schema_primitive!(compact_str::CompactString);
216#[cfg(any(feature = "decimal", feature = "decimal-float"))]
217impl_to_schema!(rust_decimal::Decimal);
218#[cfg(feature = "url")]
219impl_to_schema!(url::Url);
220#[cfg(feature = "uuid")]
221impl_to_schema!(uuid::Uuid);
222#[cfg(feature = "ulid")]
223impl_to_schema!(ulid::Ulid);
224#[cfg(feature = "time")]
225impl_to_schema_primitive!(
226 time::Date,
227 time::PrimitiveDateTime,
228 time::OffsetDateTime,
229 time::Duration
230);
231#[cfg(feature = "smallvec")]
232impl<T: ToSchema + smallvec::Array> ToSchema for smallvec::SmallVec<T> {
233 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
234 schema!(#[inline] smallvec::SmallVec<T>).into()
235 }
236}
237#[cfg(feature = "indexmap")]
238impl<K: ToSchema, V: ToSchema> ToSchema for indexmap::IndexMap<K, V> {
239 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
240 schema!(#[inline] indexmap::IndexMap<K, V>).into()
241 }
242}
243
244impl<T: ToSchema> ToSchema for Vec<T> {
245 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
246 schema!(#[inline] Vec<T>).into()
247 }
248}
249
250impl<T: ToSchema> ToSchema for LinkedList<T> {
251 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
252 schema!(#[inline] LinkedList<T>).into()
253 }
254}
255
256impl<T: ToSchema> ToSchema for [T] {
257 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
258 schema!(
259 #[inline]
260 [T]
261 )
262 .into()
263 }
264}
265impl<T: ToSchema, const N: usize> ToSchema for [T; N] {
266 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
267 schema!(
268 #[inline]
269 [T; N]
270 )
271 .into()
272 }
273}
274
275impl<T: ToSchema> ToSchema for &[T] {
276 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
277 schema!(
278 #[inline]
279 &[T]
280 )
281 .into()
282 }
283}
284
285impl<T: ToSchema> ToSchema for Option<T> {
286 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
287 schema!(#[inline] Option<T>).into()
288 }
289}
290
291impl<T> ToSchema for PhantomData<T> {
292 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
293 Schema::Object(Box::default()).into()
294 }
295}
296
297impl<K: ToSchema, V: ToSchema> ToSchema for BTreeMap<K, V> {
298 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
299 schema!(#[inline]BTreeMap<K, V>).into()
300 }
301}
302
303impl<K: ToSchema, V: ToSchema> ToSchema for HashMap<K, V> {
304 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
305 schema!(#[inline]HashMap<K, V>).into()
306 }
307}
308
309impl ToSchema for StatusError {
310 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
311 let name = crate::naming::assign_name::<Self>(Default::default());
312 let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
313 if !components.schemas.contains_key(&name) {
314 components.schemas.insert(name.clone(), ref_or.clone());
315 let schema = Schema::from(
316 Object::new()
317 .property("code", u16::to_schema(components))
318 .required("code")
319 .required("name")
320 .property("name", String::to_schema(components))
321 .required("brief")
322 .property("brief", String::to_schema(components))
323 .required("detail")
324 .property("detail", String::to_schema(components))
325 .property("cause", String::to_schema(components)),
326 );
327 components.schemas.insert(name, schema);
328 }
329 ref_or
330 }
331}
332impl ToSchema for salvo_core::Error {
333 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
334 StatusError::to_schema(components)
335 }
336}
337
338impl<T, E> ToSchema for Result<T, E>
339where
340 T: ToSchema,
341 E: ToSchema,
342{
343 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
344 let name = crate::naming::assign_name::<StatusError>(Default::default());
345 let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
346 if !components.schemas.contains_key(&name) {
347 components.schemas.insert(name.clone(), ref_or.clone());
348 let schema = OneOf::new()
349 .item(T::to_schema(components))
350 .item(E::to_schema(components));
351 components.schemas.insert(name, schema);
352 }
353 ref_or
354 }
355}
356
357impl ToSchema for serde_json::Value {
358 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
359 Schema::Object(Box::default()).into()
360 }
361}
362impl ToSchema for serde_json::Map<String, serde_json::Value> {
363 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
364 schema!(#[inline]HashMap<K, V>).into()
365 }
366}
367
368pub trait ToParameters<'de>: Extractible<'de> {
467 fn to_parameters(components: &mut Components) -> Parameters;
470}
471
472pub trait ToParameter {
474 fn to_parameter(components: &mut Components) -> Parameter;
476}
477
478pub trait ToRequestBody {
511 fn to_request_body(components: &mut Components) -> RequestBody;
513}
514
515pub trait ToResponses {
539 fn to_responses(components: &mut Components) -> Responses;
541}
542
543impl<C> ToResponses for writing::Json<C>
544where
545 C: ToSchema,
546{
547 fn to_responses(components: &mut Components) -> Responses {
548 Responses::new().response(
549 "200",
550 Response::new("Response json format data")
551 .add_content("application/json", Content::new(C::to_schema(components))),
552 )
553 }
554}
555
556impl ToResponses for StatusError {
557 fn to_responses(components: &mut Components) -> Responses {
558 let mut responses = Responses::new();
559 let errors = vec![
560 Self::bad_request(),
561 Self::unauthorized(),
562 Self::payment_required(),
563 Self::forbidden(),
564 Self::not_found(),
565 Self::method_not_allowed(),
566 Self::not_acceptable(),
567 Self::proxy_authentication_required(),
568 Self::request_timeout(),
569 Self::conflict(),
570 Self::gone(),
571 Self::length_required(),
572 Self::precondition_failed(),
573 Self::payload_too_large(),
574 Self::uri_too_long(),
575 Self::unsupported_media_type(),
576 Self::range_not_satisfiable(),
577 Self::expectation_failed(),
578 Self::im_a_teapot(),
579 Self::misdirected_request(),
580 Self::unprocessable_entity(),
581 Self::locked(),
582 Self::failed_dependency(),
583 Self::upgrade_required(),
584 Self::precondition_required(),
585 Self::too_many_requests(),
586 Self::request_header_fields_toolarge(),
587 Self::unavailable_for_legalreasons(),
588 Self::internal_server_error(),
589 Self::not_implemented(),
590 Self::bad_gateway(),
591 Self::service_unavailable(),
592 Self::gateway_timeout(),
593 Self::http_version_not_supported(),
594 Self::variant_also_negotiates(),
595 Self::insufficient_storage(),
596 Self::loop_detected(),
597 Self::not_extended(),
598 Self::network_authentication_required(),
599 ];
600 for Self { code, brief, .. } in errors {
601 responses.insert(
602 code.as_str(),
603 Response::new(brief).add_content(
604 "application/json",
605 Content::new(Self::to_schema(components)),
606 ),
607 )
608 }
609 responses
610 }
611}
612impl ToResponses for salvo_core::Error {
613 fn to_responses(components: &mut Components) -> Responses {
614 StatusError::to_responses(components)
615 }
616}
617
618pub trait ToResponse {
638 fn to_response(components: &mut Components) -> RefOr<crate::Response>;
640}
641
642impl<C> ToResponse for writing::Json<C>
643where
644 C: ToSchema,
645{
646 fn to_response(components: &mut Components) -> RefOr<Response> {
647 let schema = <C as ToSchema>::to_schema(components);
648 Response::new("Response with json format data")
649 .add_content("application/json", Content::new(schema))
650 .into()
651 }
652}
653
654#[cfg(test)]
655mod tests {
656 use assert_json_diff::assert_json_eq;
657 use serde_json::json;
658
659 use super::*;
660
661 #[test]
662 fn test_primitive_schema() {
663 let mut components = Components::new();
664 for (name, schema, value) in [
665 (
666 "i8",
667 i8::to_schema(&mut components),
668 json!({"type": "integer", "format": "int8"}),
669 ),
670 (
671 "i16",
672 i16::to_schema(&mut components),
673 json!({"type": "integer", "format": "int16"}),
674 ),
675 (
676 "i32",
677 i32::to_schema(&mut components),
678 json!({"type": "integer", "format": "int32"}),
679 ),
680 (
681 "i64",
682 i64::to_schema(&mut components),
683 json!({"type": "integer", "format": "int64"}),
684 ),
685 (
686 "i128",
687 i128::to_schema(&mut components),
688 json!({"type": "integer"}),
689 ),
690 (
691 "isize",
692 isize::to_schema(&mut components),
693 json!({"type": "integer"}),
694 ),
695 (
696 "u8",
697 u8::to_schema(&mut components),
698 json!({"type": "integer", "format": "uint8", "minimum": 0.0}),
699 ),
700 (
701 "u16",
702 u16::to_schema(&mut components),
703 json!({"type": "integer", "format": "uint16", "minimum": 0.0}),
704 ),
705 (
706 "u32",
707 u32::to_schema(&mut components),
708 json!({"type": "integer", "format": "uint32", "minimum": 0.0}),
709 ),
710 (
711 "u64",
712 u64::to_schema(&mut components),
713 json!({"type": "integer", "format": "uint64", "minimum": 0.0}),
714 ),
715 (
716 "u128",
717 u128::to_schema(&mut components),
718 json!({"type": "integer", "minimum": 0.0}),
719 ),
720 (
721 "usize",
722 usize::to_schema(&mut components),
723 json!({"type": "integer", "minimum": 0.0 }),
724 ),
725 (
726 "bool",
727 bool::to_schema(&mut components),
728 json!({"type": "boolean"}),
729 ),
730 (
731 "str",
732 str::to_schema(&mut components),
733 json!({"type": "string"}),
734 ),
735 (
736 "String",
737 String::to_schema(&mut components),
738 json!({"type": "string"}),
739 ),
740 (
741 "char",
742 char::to_schema(&mut components),
743 json!({"type": "string"}),
744 ),
745 (
746 "f32",
747 f32::to_schema(&mut components),
748 json!({"type": "number", "format": "float"}),
749 ),
750 (
751 "f64",
752 f64::to_schema(&mut components),
753 json!({"type": "number", "format": "double"}),
754 ),
755 ] {
756 println!(
757 "{name}: {json}",
758 json = serde_json::to_string(&schema).unwrap()
759 );
760 let schema = serde_json::to_value(schema).unwrap();
761 assert_json_eq!(schema, value);
762 }
763 }
764}