use crate::{Form, Json, Query};
use indexmap::IndexMap;
pub use rweb_openapi::v3_0::*;
use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet, HashMap, HashSet, LinkedList, VecDeque},
convert::Infallible,
sync::Arc,
};
use warp::{Rejection, Reply};
pub type Responses = IndexMap<Cow<'static, str>, Response>;
#[derive(Debug)]
pub struct ComponentDescriptor {
components: IndexMap<Cow<'static, str>, Schema>,
}
impl ComponentDescriptor {
pub(crate) fn new() -> Self {
Self {
components: IndexMap::new(),
}
}
pub fn get_component(&self, name: &str) -> Option<&Schema> {
self.components.get(name)
}
pub fn get_unpack<'a>(&'a self, schema: &'a ComponentOrInlineSchema) -> &'a Schema {
match schema {
ComponentOrInlineSchema::Component { name } => self.get_component(name).unwrap(),
ComponentOrInlineSchema::Inline(s) => s,
}
}
pub fn describe_component(
&mut self,
name: &str,
desc: impl FnOnce(&mut ComponentDescriptor) -> Schema,
) -> ComponentOrInlineSchema {
if !self.components.contains_key(name) {
self.components
.insert(Cow::Owned(name.to_string()), Default::default());
self.components[name] = desc(self);
}
ComponentOrInlineSchema::Component {
name: Cow::Owned(name.to_string()),
}
}
pub(crate) fn build(self) -> IndexMap<Cow<'static, str>, ObjectOrReference<Schema>> {
self.components
.into_iter()
.map(|(k, v)| (k, ObjectOrReference::Object(v)))
.collect()
}
}
pub trait Entity {
fn type_name() -> Cow<'static, str>;
fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema;
}
pub trait ResponseEntity: Entity {
fn describe_responses(comp_d: &mut ComponentDescriptor) -> Responses;
}
macro_rules! delegate_entity {
( $T:tt $(< $( $tlt:tt $(< $( $tltt:tt ),+ >)? ),+ >)? => $D:tt $(< $( $plt:tt $(< $( $pltt:tt ),+ >)? ),+ >)? ) => {
impl Entity for $T $(< $( $tlt $(< $( $tltt ),+ >)? ),+ >)? {
fn type_name() -> Cow<'static, str> {
<$D $(< $( $plt $(< $( $pltt ),+ >)? ),+ >)? as Entity>::type_name()
}
fn describe(d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
<$D $(< $( $plt $(< $( $pltt ),+ >)? ),+ >)? as Entity>::describe(d)
}
}
};
( < $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ > $T:tt $(< $( $tlt:tt $(< $( $tltt:tt ),+ >)? ),+ >)? => $D:tt $(< $( $plt:tt $(< $( $pltt:tt ),+ >)? ),+ >)? ) => {
impl < $( $lt $( : $clt $(+ $dlt )* )? ),+ > Entity for $T $(< $( $tlt $(< $( $tltt ),+ >)? ),+ >)? {
fn type_name() -> Cow<'static, str> {
<$D $(< $( $plt $(< $( $pltt ),+ >)? ),+ >)? as Entity>::type_name()
}
fn describe(d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
<$D $(< $( $plt $(< $( $pltt ),+ >)? ),+ >)? as Entity>::describe(d)
}
}
};
}
impl Entity for () {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("unit")
}
#[inline]
fn describe(_: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::Object),
nullable: Some(true),
..Default::default()
})
}
}
macro_rules! integer {
($T:ty) => {
impl Entity for $T {
fn type_name() -> Cow<'static, str> {
if <$T>::MIN == 0 {
Cow::Borrowed("uinteger")
} else {
Cow::Borrowed("integer")
}
}
#[inline]
fn describe(_: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::Integer),
minimum: if <$T>::MIN == 0 {
Some(serde_json::json!(0))
} else {
None
},
..Default::default()
})
}
}
};
(
$(
$T:ty
),*
) => {
$(
integer!($T);
)*
};
}
integer!(u8, u16, u32, u64, u128, usize);
integer!(i8, i16, i32, i64, i128, isize);
macro_rules! number {
($T:ty) => {
impl Entity for $T {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("number")
}
#[inline]
fn describe(_: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::Number),
..Default::default()
})
}
}
};
}
number!(f32);
number!(f64);
impl Entity for bool {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("bool")
}
#[inline]
fn describe(_: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::Boolean),
..Default::default()
})
}
}
impl Entity for char {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("char")
}
#[inline]
fn describe(_: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::String),
..Default::default()
})
}
}
impl Entity for str {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("string")
}
#[inline]
fn describe(_: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::String),
..Default::default()
})
}
}
impl ResponseEntity for str {
fn describe_responses(comp_d: &mut ComponentDescriptor) -> Responses {
String::describe_responses(comp_d)
}
}
impl<T> Entity for Box<T>
where
T: ?Sized + Entity,
{
fn type_name() -> Cow<'static, str> {
T::type_name()
}
fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
T::describe(comp_d)
}
}
impl<T> ResponseEntity for Box<T>
where
T: ?Sized + ResponseEntity,
{
fn describe_responses(comp_d: &mut ComponentDescriptor) -> Responses {
T::describe_responses(comp_d)
}
}
impl<T> Entity for Arc<T>
where
T: ?Sized + Entity,
{
fn type_name() -> Cow<'static, str> {
T::type_name()
}
fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
T::describe(comp_d)
}
}
impl<T> ResponseEntity for Arc<T>
where
T: ?Sized + ResponseEntity,
{
fn describe_responses(comp_d: &mut ComponentDescriptor) -> Responses {
T::describe_responses(comp_d)
}
}
impl<'a, T> Entity for &'a T
where
T: ?Sized + Entity,
{
fn type_name() -> Cow<'static, str> {
T::type_name()
}
fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
T::describe(comp_d)
}
}
impl<'a, T> ResponseEntity for &'a T
where
T: ?Sized + ResponseEntity,
{
fn describe_responses(comp_d: &mut ComponentDescriptor) -> Responses {
T::describe_responses(comp_d)
}
}
impl<T: Entity> Entity for HashMap<String, T> {
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("Map-string_{}-", T::type_name()))
}
fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::Object),
additional_properties: Some(Box::new(T::describe(comp_d))),
..Default::default()
})
}
}
impl<T: Entity> Entity for [T] {
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("{}_List", T::type_name()))
}
fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::Array),
items: Some(Box::new(T::describe(comp_d))),
..Default::default()
})
}
}
impl<T: Entity, const N: usize> Entity for [T; N] {
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("{}_Array_{}", T::type_name(), N))
}
fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::Array),
items: Some(Box::new(T::describe(comp_d))),
min_items: Some(N),
max_items: Some(N),
..Default::default()
})
}
}
impl<T: Entity> Entity for BTreeSet<T> {
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("{}_Set", T::type_name()))
}
fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::Array),
items: Some(Box::new(T::describe(comp_d))),
unique_items: Some(true),
..Default::default()
})
}
}
impl<T> Entity for Option<T>
where
T: Entity,
{
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("{}_Opt", T::type_name()))
}
fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
let desc = T::describe(comp_d);
let schema = comp_d.get_unpack(&desc);
if schema.nullable == Some(true) {
desc
} else {
let mut schema = schema.clone();
schema.nullable = Some(true);
match desc {
ComponentOrInlineSchema::Component { .. } => {
comp_d.describe_component(&Self::type_name(), |_| schema)
}
ComponentOrInlineSchema::Inline(_) => ComponentOrInlineSchema::Inline(schema),
}
}
}
}
impl<T> ResponseEntity for Option<T>
where
T: ResponseEntity,
{
fn describe_responses(comp_d: &mut ComponentDescriptor) -> Responses {
T::describe_responses(comp_d)
.into_iter()
.map(|(k, r)| {
(
k,
Response {
content: r
.content
.into_iter()
.map(|(k, v)| {
(
k,
MediaType {
schema: v.schema.map(|desc| {
let schema = comp_d.get_unpack(&desc);
if schema.nullable == Some(true) {
desc
} else {
let mut schema = schema.clone();
schema.nullable = Some(true);
match desc {
ComponentOrInlineSchema::Component { name } => {
comp_d.describe_component(
&format!("{}_Opt", name),
|_| schema,
)
}
ComponentOrInlineSchema::Inline(_) => {
ComponentOrInlineSchema::Inline(schema)
}
}
}
}),
..v
},
)
})
.collect(),
..r
},
)
})
.collect()
}
}
delegate_entity!(String => str);
impl ResponseEntity for String {
fn describe_responses(comp_d: &mut ComponentDescriptor) -> Responses {
let mut content = IndexMap::new();
content.insert(
Cow::Borrowed("text/plain"),
MediaType {
schema: Some(Self::describe(comp_d)),
examples: None,
encoding: Default::default(),
},
);
let mut map = IndexMap::new();
map.insert(
Cow::Borrowed("200"),
Response {
content,
..Default::default()
},
);
map
}
}
impl<T, E> Entity for Result<T, E>
where
T: Entity,
E: Entity,
{
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("Result-{}_{}-", T::type_name(), E::type_name()))
}
fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
one_of: vec![
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::Object),
properties: indexmap::indexmap! {
Cow::Borrowed("Ok") => T::describe(comp_d),
},
required: vec![Cow::Borrowed("Ok")],
..Default::default()
}),
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::Object),
properties: indexmap::indexmap! {
Cow::Borrowed("Err") => E::describe(comp_d),
},
required: vec![Cow::Borrowed("Err")],
..Default::default()
}),
],
..Default::default()
})
}
}
impl<T, E> ResponseEntity for Result<T, E>
where
T: ResponseEntity,
E: ResponseEntity,
{
fn describe_responses(
comp_d: &mut ComponentDescriptor,
) -> IndexMap<Cow<'static, str>, Response> {
let mut map = T::describe_responses(comp_d);
map.extend(E::describe_responses(comp_d));
map
}
}
delegate_entity!(<V: Entity, S> HashSet<V, S> => BTreeSet<V>);
delegate_entity!(<T: Entity> Vec<T> => [T]);
delegate_entity!(<T: Entity> LinkedList<T> => [T]);
delegate_entity!(<T: Entity> VecDeque<T> => [T]);
delegate_entity!(<T: Entity> (T, T) => [T; 2]);
delegate_entity!(<T: Entity> (T, T, T) => [T; 3]);
delegate_entity!(<T: Entity> (T, T, T, T) => [T; 4]);
delegate_entity!(<T: Entity> (T, T, T, T, T) => [T; 5]);
delegate_entity!(<T: Entity> HashMap<Arc<String>, T> => HashMap<String, T>);
delegate_entity!(<T: Entity> HashMap<Cow<'_, String>, T> => HashMap<String, T>);
delegate_entity!(<T: Entity> BTreeMap<String, T> => HashMap<String, T>);
delegate_entity!(<T: Entity> BTreeMap<Arc<String>, T> => BTreeMap<String, T>);
delegate_entity!(<T: Entity> BTreeMap<Cow<'_, String>, T> => BTreeMap<String, T>);
delegate_entity!(<T: Entity> IndexMap<String, T> => HashMap<String, T>);
delegate_entity!(<T: Entity> IndexMap<Arc<String>, T> => IndexMap<String, T>);
delegate_entity!(<T: Entity> IndexMap<Cow<'_, String>, T> => IndexMap<String, T>);
delegate_entity!(Infallible => ());
impl ResponseEntity for Infallible {
#[inline]
fn describe_responses(_: &mut ComponentDescriptor) -> Responses {
Default::default()
}
}
delegate_entity!(<T: Entity> Json<T> => T);
impl<T> ResponseEntity for Json<T>
where
T: Entity,
{
fn describe_responses(comp_d: &mut ComponentDescriptor) -> Responses {
let schema = Self::describe(comp_d);
let mut content = IndexMap::new();
content.insert(
Cow::Borrowed("application/json"),
MediaType {
schema: Some(schema),
examples: None,
encoding: Default::default(),
},
);
let mut map = Responses::new();
map.insert(
Cow::Borrowed("200"),
Response {
content,
..Default::default()
},
);
map
}
}
type SerdeJsonValue = serde_json::Value;
delegate_entity!(SerdeJsonValue => ());
impl ResponseEntity for serde_json::Value {
fn describe_responses(comp_d: &mut ComponentDescriptor) -> Responses {
let schema = Self::describe(comp_d);
let mut content = IndexMap::new();
content.insert(
Cow::Borrowed("application/json"),
MediaType {
schema: Some(schema),
examples: None,
encoding: Default::default(),
},
);
let mut map = Responses::new();
map.insert(
Cow::Borrowed("200"),
Response {
content,
..Default::default()
},
);
map
}
}
delegate_entity!(<T: Entity> Query<T> => T);
delegate_entity!(<T: Entity> Form<T> => T);
delegate_entity!(Rejection => ());
impl ResponseEntity for Rejection {
fn describe_responses(_: &mut ComponentDescriptor) -> Responses {
Default::default()
}
}
type HttpError = http::Error;
delegate_entity!(HttpError => ());
impl ResponseEntity for http::Error {
fn describe_responses(_: &mut ComponentDescriptor) -> Responses {
Default::default()
}
}
type DynReply = dyn Reply;
delegate_entity!(DynReply => ());
impl ResponseEntity for dyn Reply {
fn describe_responses(_: &mut ComponentDescriptor) -> Responses {
Default::default()
}
}
impl Entity for std::net::Ipv4Addr {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("ipv4")
}
fn describe(_: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::String),
format: Self::type_name(),
..Default::default()
})
}
}
impl Entity for std::net::Ipv6Addr {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("ipv6")
}
fn describe(_: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::String),
format: Self::type_name(),
..Default::default()
})
}
}
#[cfg(feature = "uuid")]
impl Entity for uuid::Uuid {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("uuid")
}
fn describe(_: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::String),
format: Self::type_name(),
..Default::default()
})
}
}
#[cfg(feature = "enumset")]
mod enumsetrepr {
use super::*;
use enumset::*;
use serde::*;
#[derive(Deserialize, Clone)]
#[serde(untagged)]
enum EnumSetRepr {
BitFlags(u64),
List(Vec<String>),
}
impl EnumSetRepr {
fn detect<T: EnumSetType>() -> Self {
serde_json::from_value(serde_json::to_value(EnumSet::<T>::new()).unwrap()).unwrap()
}
}
impl<T: EnumSetType + Entity> Entity for EnumSet<T> {
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("{}_EnumSet", T::type_name()))
}
fn describe(comp_d: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
let t = T::describe(comp_d);
let s = comp_d.get_unpack(&t);
ComponentOrInlineSchema::Inline(match EnumSetRepr::detect::<T>() {
EnumSetRepr::BitFlags(_) => Schema {
schema_type: Some(Type::Integer),
description: s.description.clone(),
..Default::default()
},
EnumSetRepr::List(_) => Schema {
schema_type: Some(Type::Array),
items: Some(Box::new(t)),
..Default::default()
},
})
}
}
}
#[cfg(feature = "chrono")]
mod chrono_impls {
use chrono::*;
use super::*;
impl Entity for NaiveDateTime {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("date-time")
}
fn describe(_: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::String),
format: Self::type_name(),
..Default::default()
})
}
}
delegate_entity!(<T: TimeZone> DateTime<T> => NaiveDateTime);
impl Entity for chrono::NaiveDate {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("date")
}
fn describe(_: &mut ComponentDescriptor) -> ComponentOrInlineSchema {
ComponentOrInlineSchema::Inline(Schema {
schema_type: Some(Type::String),
format: Self::type_name(),
..Default::default()
})
}
}
delegate_entity!(<T: TimeZone> Date<T> => NaiveDate);
}