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;
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> {
468 fn to_parameters(components: &mut Components) -> Parameters;
471}
472
473pub trait ToParameter {
475 fn to_parameter(components: &mut Components) -> Parameter;
477}
478
479pub trait ToRequestBody {
512 fn to_request_body(components: &mut Components) -> RequestBody;
514}
515
516pub trait ToResponses {
540 fn to_responses(components: &mut Components) -> Responses;
542}
543
544impl<C> ToResponses for writing::Json<C>
545where
546 C: ToSchema,
547{
548 fn to_responses(components: &mut Components) -> Responses {
549 Responses::new().response(
550 "200",
551 Response::new("Response json format data")
552 .add_content("application/json", Content::new(C::to_schema(components))),
553 )
554 }
555}
556
557impl ToResponses for StatusError {
558 fn to_responses(components: &mut Components) -> Responses {
559 let mut responses = Responses::new();
560 let errors = vec![
561 Self::bad_request(),
562 Self::unauthorized(),
563 Self::payment_required(),
564 Self::forbidden(),
565 Self::not_found(),
566 Self::method_not_allowed(),
567 Self::not_acceptable(),
568 Self::proxy_authentication_required(),
569 Self::request_timeout(),
570 Self::conflict(),
571 Self::gone(),
572 Self::length_required(),
573 Self::precondition_failed(),
574 Self::payload_too_large(),
575 Self::uri_too_long(),
576 Self::unsupported_media_type(),
577 Self::range_not_satisfiable(),
578 Self::expectation_failed(),
579 Self::im_a_teapot(),
580 Self::misdirected_request(),
581 Self::unprocessable_entity(),
582 Self::locked(),
583 Self::failed_dependency(),
584 Self::upgrade_required(),
585 Self::precondition_required(),
586 Self::too_many_requests(),
587 Self::request_header_fields_toolarge(),
588 Self::unavailable_for_legalreasons(),
589 Self::internal_server_error(),
590 Self::not_implemented(),
591 Self::bad_gateway(),
592 Self::service_unavailable(),
593 Self::gateway_timeout(),
594 Self::http_version_not_supported(),
595 Self::variant_also_negotiates(),
596 Self::insufficient_storage(),
597 Self::loop_detected(),
598 Self::not_extended(),
599 Self::network_authentication_required(),
600 ];
601 for Self { code, brief, .. } in errors {
602 responses.insert(
603 code.as_str(),
604 Response::new(brief).add_content(
605 "application/json",
606 Content::new(Self::to_schema(components)),
607 ),
608 )
609 }
610 responses
611 }
612}
613impl ToResponses for salvo_core::Error {
614 fn to_responses(components: &mut Components) -> Responses {
615 StatusError::to_responses(components)
616 }
617}
618
619pub trait ToResponse {
639 fn to_response(components: &mut Components) -> RefOr<crate::Response>;
641}
642
643impl<C> ToResponse for writing::Json<C>
644where
645 C: ToSchema,
646{
647 fn to_response(components: &mut Components) -> RefOr<Response> {
648 let schema = <C as ToSchema>::to_schema(components);
649 Response::new("Response with json format data")
650 .add_content("application/json", Content::new(schema))
651 .into()
652 }
653}
654
655#[cfg(test)]
656mod tests {
657 use assert_json_diff::assert_json_eq;
658 use serde_json::json;
659
660 use super::*;
661
662 #[test]
663 fn test_primitive_schema() {
664 let mut components = Components::new();
665 for (name, schema, value) in [
666 (
667 "i8",
668 i8::to_schema(&mut components),
669 json!({"type": "integer", "format": "int8"}),
670 ),
671 (
672 "i16",
673 i16::to_schema(&mut components),
674 json!({"type": "integer", "format": "int16"}),
675 ),
676 (
677 "i32",
678 i32::to_schema(&mut components),
679 json!({"type": "integer", "format": "int32"}),
680 ),
681 (
682 "i64",
683 i64::to_schema(&mut components),
684 json!({"type": "integer", "format": "int64"}),
685 ),
686 (
687 "i128",
688 i128::to_schema(&mut components),
689 json!({"type": "integer"}),
690 ),
691 (
692 "isize",
693 isize::to_schema(&mut components),
694 json!({"type": "integer"}),
695 ),
696 (
697 "u8",
698 u8::to_schema(&mut components),
699 json!({"type": "integer", "format": "uint8", "minimum": 0.0}),
700 ),
701 (
702 "u16",
703 u16::to_schema(&mut components),
704 json!({"type": "integer", "format": "uint16", "minimum": 0.0}),
705 ),
706 (
707 "u32",
708 u32::to_schema(&mut components),
709 json!({"type": "integer", "format": "uint32", "minimum": 0.0}),
710 ),
711 (
712 "u64",
713 u64::to_schema(&mut components),
714 json!({"type": "integer", "format": "uint64", "minimum": 0.0}),
715 ),
716 (
717 "u128",
718 u128::to_schema(&mut components),
719 json!({"type": "integer", "minimum": 0.0}),
720 ),
721 (
722 "usize",
723 usize::to_schema(&mut components),
724 json!({"type": "integer", "minimum": 0.0 }),
725 ),
726 (
727 "bool",
728 bool::to_schema(&mut components),
729 json!({"type": "boolean"}),
730 ),
731 (
732 "str",
733 str::to_schema(&mut components),
734 json!({"type": "string"}),
735 ),
736 (
737 "String",
738 String::to_schema(&mut components),
739 json!({"type": "string"}),
740 ),
741 (
742 "char",
743 char::to_schema(&mut components),
744 json!({"type": "string"}),
745 ),
746 (
747 "f32",
748 f32::to_schema(&mut components),
749 json!({"type": "number", "format": "float"}),
750 ),
751 (
752 "f64",
753 f64::to_schema(&mut components),
754 json!({"type": "number", "format": "double"}),
755 ),
756 ] {
757 println!(
758 "{name}: {json}",
759 json = serde_json::to_string(&schema).unwrap()
760 );
761 let schema = serde_json::to_value(schema).unwrap();
762 assert_json_eq!(schema, value);
763 }
764 }
765}