mirror of
https://gitlab.com/msrd0/gotham-restful.git
synced 2025-05-10 00:20:43 +00:00
use openapi_type::OpenapiType for gotham_restful
This commit is contained in:
parent
eecd192458
commit
ebea39fe0d
27 changed files with 148 additions and 866 deletions
|
@ -1,5 +1,5 @@
|
|||
use crate::OpenapiSchema;
|
||||
use indexmap::IndexMap;
|
||||
use openapi_type::OpenapiSchema;
|
||||
use openapiv3::{
|
||||
Components, OpenAPI, PathItem, ReferenceOr,
|
||||
ReferenceOr::{Item, Reference},
|
||||
|
@ -104,7 +104,7 @@ impl OpenapiBuilder {
|
|||
#[allow(dead_code)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::OpenapiType;
|
||||
use openapi_type::OpenapiType;
|
||||
|
||||
#[derive(OpenapiType)]
|
||||
struct Message {
|
||||
|
|
|
@ -4,4 +4,3 @@ pub mod builder;
|
|||
pub mod handler;
|
||||
pub mod operation;
|
||||
pub mod router;
|
||||
pub mod types;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use super::SECURITY_NAME;
|
||||
use crate::{response::OrAllTypes, EndpointWithSchema, IntoResponse, OpenapiSchema, RequestBody, ResponseSchema};
|
||||
use crate::{response::OrAllTypes, EndpointWithSchema, IntoResponse, RequestBody, ResponseSchema};
|
||||
use indexmap::IndexMap;
|
||||
use mime::Mime;
|
||||
use openapi_type::OpenapiSchema;
|
||||
use openapiv3::{
|
||||
MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr, ReferenceOr::Item,
|
||||
RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind, StatusCode, Type
|
||||
|
|
|
@ -3,9 +3,10 @@ use super::{
|
|||
handler::{OpenapiHandler, SwaggerUiHandler},
|
||||
operation::OperationDescription
|
||||
};
|
||||
use crate::{routing::*, EndpointWithSchema, OpenapiType, ResourceWithSchema, ResponseSchema};
|
||||
use crate::{routing::*, EndpointWithSchema, ResourceWithSchema, ResponseSchema};
|
||||
use gotham::{hyper::Method, pipeline::chain::PipelineHandleChain, router::builder::*};
|
||||
use once_cell::sync::Lazy;
|
||||
use openapi_type::OpenapiType;
|
||||
use regex::{Captures, Regex};
|
||||
use std::panic::RefUnwindSafe;
|
||||
|
||||
|
|
|
@ -1,476 +0,0 @@
|
|||
#[cfg(feature = "chrono")]
|
||||
use chrono::{Date, DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, Utc};
|
||||
use gotham::extractor::{NoopPathExtractor, NoopQueryStringExtractor};
|
||||
use indexmap::IndexMap;
|
||||
use openapiv3::{
|
||||
AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType,
|
||||
ReferenceOr::{Item, Reference},
|
||||
Schema, SchemaData, SchemaKind, StringType, Type, VariantOrUnknownOrEmpty
|
||||
};
|
||||
use std::{
|
||||
collections::{BTreeSet, HashMap, HashSet},
|
||||
hash::BuildHasher,
|
||||
num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}
|
||||
};
|
||||
#[cfg(feature = "uuid")]
|
||||
use uuid::Uuid;
|
||||
|
||||
/**
|
||||
This struct needs to be available for every type that can be part of an OpenAPI Spec. It is
|
||||
already implemented for primitive types, String, Vec, Option and the like. To have it available
|
||||
for your type, simply derive from [OpenapiType].
|
||||
*/
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct OpenapiSchema {
|
||||
/// The name of this schema. If it is None, the schema will be inlined.
|
||||
pub name: Option<String>,
|
||||
/// Whether this particular schema is nullable. Note that there is no guarantee that this will
|
||||
/// make it into the final specification, it might just be interpreted as a hint to make it
|
||||
/// an optional parameter.
|
||||
pub nullable: bool,
|
||||
/// The actual OpenAPI schema.
|
||||
pub schema: SchemaKind,
|
||||
/// Other schemas that this schema depends on. They will be included in the final OpenAPI Spec
|
||||
/// along with this schema.
|
||||
pub dependencies: IndexMap<String, OpenapiSchema>
|
||||
}
|
||||
|
||||
impl OpenapiSchema {
|
||||
/// Create a new schema that has no name.
|
||||
pub fn new(schema: SchemaKind) -> Self {
|
||||
Self {
|
||||
name: None,
|
||||
nullable: false,
|
||||
schema,
|
||||
dependencies: IndexMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this schema to an [openapiv3::Schema] that can be serialized to the OpenAPI Spec.
|
||||
pub fn into_schema(self) -> Schema {
|
||||
Schema {
|
||||
schema_data: SchemaData {
|
||||
nullable: self.nullable,
|
||||
title: self.name,
|
||||
..Default::default()
|
||||
},
|
||||
schema_kind: self.schema
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
This trait needs to be implemented by every type that is being used in the OpenAPI Spec. It gives
|
||||
access to the [OpenapiSchema] of this type. It is provided for primitive types, String and the
|
||||
like. For use on your own types, there is a derive macro:
|
||||
|
||||
```
|
||||
# #[macro_use] extern crate gotham_restful_derive;
|
||||
#
|
||||
#[derive(OpenapiType)]
|
||||
struct MyResponse {
|
||||
message: String
|
||||
}
|
||||
```
|
||||
*/
|
||||
pub trait OpenapiType {
|
||||
fn schema() -> OpenapiSchema;
|
||||
}
|
||||
|
||||
impl OpenapiType for () {
|
||||
fn schema() -> OpenapiSchema {
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::Object(ObjectType {
|
||||
additional_properties: Some(AdditionalProperties::Any(false)),
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenapiType for NoopPathExtractor {
|
||||
fn schema() -> OpenapiSchema {
|
||||
warn!("You're asking for the OpenAPI Schema for gotham::extractor::NoopPathExtractor. This is probably not what you want.");
|
||||
<()>::schema()
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenapiType for NoopQueryStringExtractor {
|
||||
fn schema() -> OpenapiSchema {
|
||||
warn!("You're asking for the OpenAPI Schema for gotham::extractor::NoopQueryStringExtractor. This is probably not what you want.");
|
||||
<()>::schema()
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenapiType for bool {
|
||||
fn schema() -> OpenapiSchema {
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::Boolean {}))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! int_types {
|
||||
($($int_ty:ty),*) => {$(
|
||||
impl OpenapiType for $int_ty
|
||||
{
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType::default())))
|
||||
}
|
||||
}
|
||||
)*};
|
||||
|
||||
(unsigned $($int_ty:ty),*) => {$(
|
||||
impl OpenapiType for $int_ty
|
||||
{
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType {
|
||||
minimum: Some(0),
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
}
|
||||
)*};
|
||||
|
||||
(gtzero $($int_ty:ty),*) => {$(
|
||||
impl OpenapiType for $int_ty
|
||||
{
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType {
|
||||
minimum: Some(1),
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
}
|
||||
)*};
|
||||
|
||||
(bits = $bits:expr, $($int_ty:ty),*) => {$(
|
||||
impl OpenapiType for $int_ty
|
||||
{
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType {
|
||||
format: VariantOrUnknownOrEmpty::Unknown(format!("int{}", $bits)),
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
}
|
||||
)*};
|
||||
|
||||
(unsigned bits = $bits:expr, $($int_ty:ty),*) => {$(
|
||||
impl OpenapiType for $int_ty
|
||||
{
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType {
|
||||
format: VariantOrUnknownOrEmpty::Unknown(format!("int{}", $bits)),
|
||||
minimum: Some(0),
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
}
|
||||
)*};
|
||||
|
||||
(gtzero bits = $bits:expr, $($int_ty:ty),*) => {$(
|
||||
impl OpenapiType for $int_ty
|
||||
{
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType {
|
||||
format: VariantOrUnknownOrEmpty::Unknown(format!("int{}", $bits)),
|
||||
minimum: Some(1),
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
int_types!(isize);
|
||||
int_types!(unsigned usize);
|
||||
int_types!(gtzero NonZeroUsize);
|
||||
int_types!(bits = 8, i8);
|
||||
int_types!(unsigned bits = 8, u8);
|
||||
int_types!(gtzero bits = 8, NonZeroU8);
|
||||
int_types!(bits = 16, i16);
|
||||
int_types!(unsigned bits = 16, u16);
|
||||
int_types!(gtzero bits = 16, NonZeroU16);
|
||||
int_types!(bits = 32, i32);
|
||||
int_types!(unsigned bits = 32, u32);
|
||||
int_types!(gtzero bits = 32, NonZeroU32);
|
||||
int_types!(bits = 64, i64);
|
||||
int_types!(unsigned bits = 64, u64);
|
||||
int_types!(gtzero bits = 64, NonZeroU64);
|
||||
int_types!(bits = 128, i128);
|
||||
int_types!(unsigned bits = 128, u128);
|
||||
int_types!(gtzero bits = 128, NonZeroU128);
|
||||
|
||||
macro_rules! num_types {
|
||||
($($num_ty:ty = $num_fmt:ident),*) => {$(
|
||||
impl OpenapiType for $num_ty
|
||||
{
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::Number(NumberType {
|
||||
format: VariantOrUnknownOrEmpty::Item(NumberFormat::$num_fmt),
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
num_types!(f32 = Float, f64 = Double);
|
||||
|
||||
macro_rules! str_types {
|
||||
($($str_ty:ty),*) => {$(
|
||||
impl OpenapiType for $str_ty
|
||||
{
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::String(StringType::default())))
|
||||
}
|
||||
}
|
||||
)*};
|
||||
|
||||
(format = $format:ident, $($str_ty:ty),*) => {$(
|
||||
impl OpenapiType for $str_ty
|
||||
{
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
use openapiv3::StringFormat;
|
||||
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
|
||||
format: VariantOrUnknownOrEmpty::Item(StringFormat::$format),
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
}
|
||||
)*};
|
||||
|
||||
(format_str = $format:expr, $($str_ty:ty),*) => {$(
|
||||
impl OpenapiType for $str_ty
|
||||
{
|
||||
fn schema() -> OpenapiSchema
|
||||
{
|
||||
OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
|
||||
format: VariantOrUnknownOrEmpty::Unknown($format.to_string()),
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
str_types!(String, &str);
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
str_types!(format = Date, Date<FixedOffset>, Date<Local>, Date<Utc>, NaiveDate);
|
||||
#[cfg(feature = "chrono")]
|
||||
str_types!(
|
||||
format = DateTime,
|
||||
DateTime<FixedOffset>,
|
||||
DateTime<Local>,
|
||||
DateTime<Utc>,
|
||||
NaiveDateTime
|
||||
);
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
str_types!(format_str = "uuid", Uuid);
|
||||
|
||||
impl<T: OpenapiType> OpenapiType for Option<T> {
|
||||
fn schema() -> OpenapiSchema {
|
||||
let schema = T::schema();
|
||||
let mut dependencies = schema.dependencies.clone();
|
||||
let schema = match schema.name.clone() {
|
||||
Some(name) => {
|
||||
let reference = Reference {
|
||||
reference: format!("#/components/schemas/{}", name)
|
||||
};
|
||||
dependencies.insert(name, schema);
|
||||
SchemaKind::AllOf { all_of: vec![reference] }
|
||||
},
|
||||
None => schema.schema
|
||||
};
|
||||
|
||||
OpenapiSchema {
|
||||
nullable: true,
|
||||
name: None,
|
||||
schema,
|
||||
dependencies
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: OpenapiType> OpenapiType for Vec<T> {
|
||||
fn schema() -> OpenapiSchema {
|
||||
let schema = T::schema();
|
||||
let mut dependencies = schema.dependencies.clone();
|
||||
|
||||
let items = match schema.name.clone() {
|
||||
Some(name) => {
|
||||
let reference = Reference {
|
||||
reference: format!("#/components/schemas/{}", name)
|
||||
};
|
||||
dependencies.insert(name, schema);
|
||||
reference
|
||||
},
|
||||
None => Item(Box::new(schema.into_schema()))
|
||||
};
|
||||
|
||||
OpenapiSchema {
|
||||
nullable: false,
|
||||
name: None,
|
||||
schema: SchemaKind::Type(Type::Array(ArrayType {
|
||||
items,
|
||||
min_items: None,
|
||||
max_items: None,
|
||||
unique_items: false
|
||||
})),
|
||||
dependencies
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: OpenapiType> OpenapiType for BTreeSet<T> {
|
||||
fn schema() -> OpenapiSchema {
|
||||
<Vec<T> as OpenapiType>::schema()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: OpenapiType, S: BuildHasher> OpenapiType for HashSet<T, S> {
|
||||
fn schema() -> OpenapiSchema {
|
||||
<Vec<T> as OpenapiType>::schema()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: OpenapiType, T: OpenapiType, S: BuildHasher> OpenapiType for HashMap<K, T, S> {
|
||||
fn schema() -> OpenapiSchema {
|
||||
let key_schema = K::schema();
|
||||
let mut dependencies = key_schema.dependencies.clone();
|
||||
|
||||
let keys = match key_schema.name.clone() {
|
||||
Some(name) => {
|
||||
let reference = Reference {
|
||||
reference: format!("#/components/schemas/{}", name)
|
||||
};
|
||||
dependencies.insert(name, key_schema);
|
||||
reference
|
||||
},
|
||||
None => Item(Box::new(key_schema.into_schema()))
|
||||
};
|
||||
|
||||
let schema = T::schema();
|
||||
dependencies.extend(schema.dependencies.iter().map(|(k, v)| (k.clone(), v.clone())));
|
||||
|
||||
let items = Box::new(match schema.name.clone() {
|
||||
Some(name) => {
|
||||
let reference = Reference {
|
||||
reference: format!("#/components/schemas/{}", name)
|
||||
};
|
||||
dependencies.insert(name, schema);
|
||||
reference
|
||||
},
|
||||
None => Item(schema.into_schema())
|
||||
});
|
||||
|
||||
let mut properties = IndexMap::new();
|
||||
properties.insert("default".to_owned(), keys);
|
||||
|
||||
OpenapiSchema {
|
||||
nullable: false,
|
||||
name: None,
|
||||
schema: SchemaKind::Type(Type::Object(ObjectType {
|
||||
properties,
|
||||
required: vec!["default".to_owned()],
|
||||
additional_properties: Some(AdditionalProperties::Schema(items)),
|
||||
..Default::default()
|
||||
})),
|
||||
dependencies
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenapiType for serde_json::Value {
|
||||
fn schema() -> OpenapiSchema {
|
||||
OpenapiSchema {
|
||||
nullable: true,
|
||||
name: None,
|
||||
schema: SchemaKind::Any(Default::default()),
|
||||
dependencies: Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use serde_json::Value;
|
||||
|
||||
type Unit = ();
|
||||
|
||||
macro_rules! assert_schema {
|
||||
($ty:ident $(<$($generic:ident),+>)* => $json:expr) => {
|
||||
paste::item! {
|
||||
#[test]
|
||||
fn [<test_schema_ $ty:lower $($(_ $generic:lower)+)*>]()
|
||||
{
|
||||
let schema = <$ty $(<$($generic),+>)* as OpenapiType>::schema().into_schema();
|
||||
let schema_json = serde_json::to_string(&schema).expect(&format!("Unable to serialize schema for {}", stringify!($ty)));
|
||||
assert_eq!(schema_json, $json);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
assert_schema!(Unit => r#"{"type":"object","additionalProperties":false}"#);
|
||||
assert_schema!(bool => r#"{"type":"boolean"}"#);
|
||||
|
||||
assert_schema!(isize => r#"{"type":"integer"}"#);
|
||||
assert_schema!(usize => r#"{"type":"integer","minimum":0}"#);
|
||||
assert_schema!(i8 => r#"{"type":"integer","format":"int8"}"#);
|
||||
assert_schema!(u8 => r#"{"type":"integer","format":"int8","minimum":0}"#);
|
||||
assert_schema!(i16 => r#"{"type":"integer","format":"int16"}"#);
|
||||
assert_schema!(u16 => r#"{"type":"integer","format":"int16","minimum":0}"#);
|
||||
assert_schema!(i32 => r#"{"type":"integer","format":"int32"}"#);
|
||||
assert_schema!(u32 => r#"{"type":"integer","format":"int32","minimum":0}"#);
|
||||
assert_schema!(i64 => r#"{"type":"integer","format":"int64"}"#);
|
||||
assert_schema!(u64 => r#"{"type":"integer","format":"int64","minimum":0}"#);
|
||||
assert_schema!(i128 => r#"{"type":"integer","format":"int128"}"#);
|
||||
assert_schema!(u128 => r#"{"type":"integer","format":"int128","minimum":0}"#);
|
||||
|
||||
assert_schema!(NonZeroUsize => r#"{"type":"integer","minimum":1}"#);
|
||||
assert_schema!(NonZeroU8 => r#"{"type":"integer","format":"int8","minimum":1}"#);
|
||||
assert_schema!(NonZeroU16 => r#"{"type":"integer","format":"int16","minimum":1}"#);
|
||||
assert_schema!(NonZeroU32 => r#"{"type":"integer","format":"int32","minimum":1}"#);
|
||||
assert_schema!(NonZeroU64 => r#"{"type":"integer","format":"int64","minimum":1}"#);
|
||||
assert_schema!(NonZeroU128 => r#"{"type":"integer","format":"int128","minimum":1}"#);
|
||||
|
||||
assert_schema!(f32 => r#"{"type":"number","format":"float"}"#);
|
||||
assert_schema!(f64 => r#"{"type":"number","format":"double"}"#);
|
||||
|
||||
assert_schema!(String => r#"{"type":"string"}"#);
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
assert_schema!(Uuid => r#"{"type":"string","format":"uuid"}"#);
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
mod chrono {
|
||||
use super::*;
|
||||
|
||||
assert_schema!(Date<FixedOffset> => r#"{"type":"string","format":"date"}"#);
|
||||
assert_schema!(Date<Local> => r#"{"type":"string","format":"date"}"#);
|
||||
assert_schema!(Date<Utc> => r#"{"type":"string","format":"date"}"#);
|
||||
assert_schema!(NaiveDate => r#"{"type":"string","format":"date"}"#);
|
||||
assert_schema!(DateTime<FixedOffset> => r#"{"type":"string","format":"date-time"}"#);
|
||||
assert_schema!(DateTime<Local> => r#"{"type":"string","format":"date-time"}"#);
|
||||
assert_schema!(DateTime<Utc> => r#"{"type":"string","format":"date-time"}"#);
|
||||
assert_schema!(NaiveDateTime => r#"{"type":"string","format":"date-time"}"#);
|
||||
}
|
||||
|
||||
assert_schema!(Option<String> => r#"{"nullable":true,"type":"string"}"#);
|
||||
assert_schema!(Vec<String> => r#"{"type":"array","items":{"type":"string"}}"#);
|
||||
assert_schema!(BTreeSet<String> => r#"{"type":"array","items":{"type":"string"}}"#);
|
||||
assert_schema!(HashSet<String> => r#"{"type":"array","items":{"type":"string"}}"#);
|
||||
assert_schema!(HashMap<i64, String> => r#"{"type":"object","properties":{"default":{"type":"integer","format":"int64"}},"required":["default"],"additionalProperties":{"type":"string"}}"#);
|
||||
assert_schema!(Value => r#"{"nullable":true}"#);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue