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
38#[doc = include_str!("../docs/derive_to_parameters.md")]
39pub use salvo_oapi_macros::ToParameters;
40#[doc = include_str!("../docs/derive_to_response.md")]
41pub use salvo_oapi_macros::ToResponse;
42#[doc = include_str!("../docs/derive_to_responses.md")]
43pub use salvo_oapi_macros::ToResponses;
44#[doc = include_str!("../docs/derive_to_schema.md")]
45pub use salvo_oapi_macros::ToSchema;
46#[doc = include_str!("../docs/endpoint.md")]
47pub use salvo_oapi_macros::endpoint;
48pub(crate) use salvo_oapi_macros::schema;
49
50use std::collections::{BTreeMap, HashMap, LinkedList};
51use std::marker::PhantomData;
52
53use salvo_core::http::StatusError;
54use salvo_core::{extract::Extractible, writing};
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;
185 pub use serde_json;
186}
187
188#[rustfmt::skip]
189impl_to_schema_primitive!(
190 i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, bool, f32, f64, String, str, char
191);
192impl_to_schema!(&str);
193
194impl_to_schema!(std::net::Ipv4Addr);
195impl_to_schema!(std::net::Ipv6Addr);
196
197impl ToSchema for std::net::IpAddr {
198 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
199 crate::RefOr::Type(Schema::OneOf(
200 OneOf::default()
201 .item(std::net::Ipv4Addr::to_schema(components))
202 .item(std::net::Ipv6Addr::to_schema(components)),
203 ))
204 }
205}
206
207#[cfg(feature = "chrono")]
208impl_to_schema_primitive!(chrono::NaiveDate, chrono::Duration, chrono::NaiveDateTime);
209#[cfg(feature = "chrono")]
210impl<T: chrono::TimeZone> ToSchema for chrono::DateTime<T> {
211 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
212 schema!(#[inline] DateTime<T>).into()
213 }
214}
215#[cfg(feature = "compact_str")]
216impl_to_schema_primitive!(compact_str::CompactString);
217#[cfg(any(feature = "decimal", feature = "decimal-float"))]
218impl_to_schema!(rust_decimal::Decimal);
219#[cfg(feature = "url")]
220impl_to_schema!(url::Url);
221#[cfg(feature = "uuid")]
222impl_to_schema!(uuid::Uuid);
223#[cfg(feature = "ulid")]
224impl_to_schema!(ulid::Ulid);
225#[cfg(feature = "time")]
226impl_to_schema_primitive!(
227 time::Date,
228 time::PrimitiveDateTime,
229 time::OffsetDateTime,
230 time::Duration
231);
232#[cfg(feature = "smallvec")]
233impl<T: ToSchema + smallvec::Array> ToSchema for smallvec::SmallVec<T> {
234 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
235 schema!(#[inline] smallvec::SmallVec<T>).into()
236 }
237}
238#[cfg(feature = "indexmap")]
239impl<K: ToSchema, V: ToSchema> ToSchema for indexmap::IndexMap<K, V> {
240 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
241 schema!(#[inline] indexmap::IndexMap<K, V>).into()
242 }
243}
244
245impl<T: ToSchema> ToSchema for Vec<T> {
246 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
247 schema!(#[inline] Vec<T>).into()
248 }
249}
250
251impl<T: ToSchema> ToSchema for LinkedList<T> {
252 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
253 schema!(#[inline] LinkedList<T>).into()
254 }
255}
256
257impl<T: ToSchema> ToSchema for [T] {
258 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
259 schema!(
260 #[inline]
261 [T]
262 )
263 .into()
264 }
265}
266impl<T: ToSchema, const N: usize> ToSchema for [T; N] {
267 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
268 schema!(
269 #[inline]
270 [T; N]
271 )
272 .into()
273 }
274}
275
276impl<T: ToSchema> ToSchema for &[T] {
277 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
278 schema!(
279 #[inline]
280 &[T]
281 )
282 .into()
283 }
284}
285
286impl<T: ToSchema> ToSchema for Option<T> {
287 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
288 schema!(#[inline] Option<T>).into()
289 }
290}
291
292impl<T> ToSchema for PhantomData<T> {
293 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
294 Schema::Object(Box::default()).into()
295 }
296}
297
298impl<K: ToSchema, V: ToSchema> ToSchema for BTreeMap<K, V> {
299 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
300 schema!(#[inline]BTreeMap<K, V>).into()
301 }
302}
303
304impl<K: ToSchema, V: ToSchema> ToSchema for HashMap<K, V> {
305 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
306 schema!(#[inline]HashMap<K, V>).into()
307 }
308}
309
310impl ToSchema for StatusError {
311 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
312 let name = crate::naming::assign_name::<Self>(Default::default());
313 let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
314 if !components.schemas.contains_key(&name) {
315 components.schemas.insert(name.clone(), ref_or.clone());
316 let schema = Schema::from(
317 Object::new()
318 .property("code", u16::to_schema(components))
319 .required("code")
320 .required("name")
321 .property("name", String::to_schema(components))
322 .required("brief")
323 .property("brief", String::to_schema(components))
324 .required("detail")
325 .property("detail", String::to_schema(components))
326 .property("cause", String::to_schema(components)),
327 );
328 components.schemas.insert(name, schema);
329 }
330 ref_or
331 }
332}
333impl ToSchema for salvo_core::Error {
334 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
335 StatusError::to_schema(components)
336 }
337}
338
339impl<T, E> ToSchema for Result<T, E>
340where
341 T: ToSchema,
342 E: ToSchema,
343{
344 fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
345 let name = crate::naming::assign_name::<StatusError>(Default::default());
346 let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
347 if !components.schemas.contains_key(&name) {
348 components.schemas.insert(name.clone(), ref_or.clone());
349 let schema = OneOf::new()
350 .item(T::to_schema(components))
351 .item(E::to_schema(components));
352 components.schemas.insert(name, schema);
353 }
354 ref_or
355 }
356}
357
358impl ToSchema for serde_json::Value {
359 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
360 Schema::Object(Box::default()).into()
361 }
362}
363impl ToSchema for serde_json::Map<String, serde_json::Value> {
364 fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
365 schema!(#[inline]HashMap<K, V>).into()
366 }
367}
368
369pub trait ToParameters<'de>: Extractible<'de> {
454 fn to_parameters(components: &mut Components) -> Parameters;
457}
458
459pub trait ToParameter {
461 fn to_parameter(components: &mut Components) -> Parameter;
463}
464
465pub trait ToRequestBody {
493 fn to_request_body(components: &mut Components) -> RequestBody;
495}
496
497pub trait ToResponses {
520 fn to_responses(components: &mut Components) -> Responses;
522}
523
524impl<C> ToResponses for writing::Json<C>
525where
526 C: ToSchema,
527{
528 fn to_responses(components: &mut Components) -> Responses {
529 Responses::new().response(
530 "200",
531 Response::new("Response json format data")
532 .add_content("application/json", Content::new(C::to_schema(components))),
533 )
534 }
535}
536
537impl ToResponses for StatusError {
538 fn to_responses(components: &mut Components) -> Responses {
539 let mut responses = Responses::new();
540 let errors = vec![
541 Self::bad_request(),
542 Self::unauthorized(),
543 Self::payment_required(),
544 Self::forbidden(),
545 Self::not_found(),
546 Self::method_not_allowed(),
547 Self::not_acceptable(),
548 Self::proxy_authentication_required(),
549 Self::request_timeout(),
550 Self::conflict(),
551 Self::gone(),
552 Self::length_required(),
553 Self::precondition_failed(),
554 Self::payload_too_large(),
555 Self::uri_too_long(),
556 Self::unsupported_media_type(),
557 Self::range_not_satisfiable(),
558 Self::expectation_failed(),
559 Self::im_a_teapot(),
560 Self::misdirected_request(),
561 Self::unprocessable_entity(),
562 Self::locked(),
563 Self::failed_dependency(),
564 Self::upgrade_required(),
565 Self::precondition_required(),
566 Self::too_many_requests(),
567 Self::request_header_fields_toolarge(),
568 Self::unavailable_for_legalreasons(),
569 Self::internal_server_error(),
570 Self::not_implemented(),
571 Self::bad_gateway(),
572 Self::service_unavailable(),
573 Self::gateway_timeout(),
574 Self::http_version_not_supported(),
575 Self::variant_also_negotiates(),
576 Self::insufficient_storage(),
577 Self::loop_detected(),
578 Self::not_extended(),
579 Self::network_authentication_required(),
580 ];
581 for Self { code, brief, .. } in errors {
582 responses.insert(
583 code.as_str(),
584 Response::new(brief).add_content(
585 "application/json",
586 Content::new(Self::to_schema(components)),
587 ),
588 )
589 }
590 responses
591 }
592}
593impl ToResponses for salvo_core::Error {
594 fn to_responses(components: &mut Components) -> Responses {
595 StatusError::to_responses(components)
596 }
597}
598
599pub trait ToResponse {
619 fn to_response(components: &mut Components) -> RefOr<crate::Response>;
621}
622
623impl<C> ToResponse for writing::Json<C>
624where
625 C: ToSchema,
626{
627 fn to_response(components: &mut Components) -> RefOr<Response> {
628 let schema = <C as ToSchema>::to_schema(components);
629 Response::new("Response with json format data")
630 .add_content("application/json", Content::new(schema))
631 .into()
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use assert_json_diff::assert_json_eq;
638 use serde_json::json;
639
640 use super::*;
641
642 #[test]
643 fn test_primitive_schema() {
644 let mut components = Components::new();
645 for (name, schema, value) in [
646 (
647 "i8",
648 i8::to_schema(&mut components),
649 json!({"type": "integer", "format": "int8"}),
650 ),
651 (
652 "i16",
653 i16::to_schema(&mut components),
654 json!({"type": "integer", "format": "int16"}),
655 ),
656 (
657 "i32",
658 i32::to_schema(&mut components),
659 json!({"type": "integer", "format": "int32"}),
660 ),
661 (
662 "i64",
663 i64::to_schema(&mut components),
664 json!({"type": "integer", "format": "int64"}),
665 ),
666 (
667 "i128",
668 i128::to_schema(&mut components),
669 json!({"type": "integer"}),
670 ),
671 (
672 "isize",
673 isize::to_schema(&mut components),
674 json!({"type": "integer"}),
675 ),
676 (
677 "u8",
678 u8::to_schema(&mut components),
679 json!({"type": "integer", "format": "uint8", "minimum": 0.0}),
680 ),
681 (
682 "u16",
683 u16::to_schema(&mut components),
684 json!({"type": "integer", "format": "uint16", "minimum": 0.0}),
685 ),
686 (
687 "u32",
688 u32::to_schema(&mut components),
689 json!({"type": "integer", "format": "uint32", "minimum": 0.0}),
690 ),
691 (
692 "u64",
693 u64::to_schema(&mut components),
694 json!({"type": "integer", "format": "uint64", "minimum": 0.0}),
695 ),
696 (
697 "u128",
698 u128::to_schema(&mut components),
699 json!({"type": "integer", "minimum": 0.0}),
700 ),
701 (
702 "usize",
703 usize::to_schema(&mut components),
704 json!({"type": "integer", "minimum": 0.0 }),
705 ),
706 (
707 "bool",
708 bool::to_schema(&mut components),
709 json!({"type": "boolean"}),
710 ),
711 (
712 "str",
713 str::to_schema(&mut components),
714 json!({"type": "string"}),
715 ),
716 (
717 "String",
718 String::to_schema(&mut components),
719 json!({"type": "string"}),
720 ),
721 (
722 "char",
723 char::to_schema(&mut components),
724 json!({"type": "string"}),
725 ),
726 (
727 "f32",
728 f32::to_schema(&mut components),
729 json!({"type": "number", "format": "float"}),
730 ),
731 (
732 "f64",
733 f64::to_schema(&mut components),
734 json!({"type": "number", "format": "double"}),
735 ),
736 ] {
737 println!(
738 "{name}: {json}",
739 json = serde_json::to_string(&schema).unwrap()
740 );
741 let schema = serde_json::to_value(schema).unwrap();
742 assert_json_eq!(schema, value);
743 }
744 }
745}