mod all_of;
mod any_of;
mod array;
mod object;
mod one_of;
pub use all_of::AllOf;
pub use any_of::AnyOf;
pub use array::{Array, ToArray};
pub use object::Object;
pub use one_of::OneOf;
use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
use serde::{Deserialize, Serialize};
use crate::RefOr;
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Schemas(pub BTreeMap<String, RefOr<Schema>>);
impl<K, R> From<BTreeMap<K, R>> for Schemas
where
K: Into<String>,
R: Into<RefOr<Schema>>,
{
fn from(inner: BTreeMap<K, R>) -> Self {
Self(inner.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
}
}
impl<K, R, const N: usize> From<[(K, R); N]> for Schemas
where
K: Into<String>,
R: Into<RefOr<Schema>>,
{
fn from(inner: [(K, R); N]) -> Self {
Self(
<[(K, R)]>::into_vec(Box::new(inner))
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
)
}
}
impl Deref for Schemas {
type Target = BTreeMap<String, RefOr<Schema>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Schemas {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl IntoIterator for Schemas {
type Item = (String, RefOr<Schema>);
type IntoIter = <BTreeMap<String, RefOr<Schema>> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Schemas {
pub fn new() -> Self {
Default::default()
}
pub fn schema<K: Into<String>, V: Into<RefOr<Schema>>>(mut self, key: K, value: V) -> Self {
self.insert(key, value);
self
}
pub fn insert<K: Into<String>, V: Into<RefOr<Schema>>>(&mut self, key: K, value: V) {
self.0.insert(key.into(), value.into());
}
pub fn append(&mut self, other: &mut Schemas) {
let items = std::mem::take(&mut other.0);
for item in items {
self.insert(item.0, item.1);
}
}
pub fn extend<I, K, V>(&mut self, iter: I)
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<RefOr<Schema>>,
{
for (k, v) in iter.into_iter() {
self.insert(k, v);
}
}
}
pub fn empty() -> Schema {
Schema::Object(Object::new().nullable(true).default_value(serde_json::Value::Null))
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(untagged, rename_all = "camelCase")]
pub enum Schema {
Array(Array),
Object(Object),
OneOf(OneOf),
AllOf(AllOf),
AnyOf(AnyOf),
}
impl Default for Schema {
fn default() -> Self {
Schema::Object(Default::default())
}
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Discriminator {
pub property_name: String,
#[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
pub mapping: BTreeMap<String, String>,
}
impl Discriminator {
pub fn new<I: Into<String>>(property_name: I) -> Self {
Self {
property_name: property_name.into(),
mapping: BTreeMap::new(),
}
}
}
fn is_false(value: &bool) -> bool {
!*value
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(untagged)]
pub enum AdditionalProperties<T> {
RefOr(RefOr<T>),
FreeForm(bool),
}
impl<T> From<RefOr<T>> for AdditionalProperties<T> {
fn from(value: RefOr<T>) -> Self {
Self::RefOr(value)
}
}
impl From<Object> for AdditionalProperties<Schema> {
fn from(value: Object) -> Self {
Self::RefOr(RefOr::T(Schema::Object(value)))
}
}
impl From<Array> for AdditionalProperties<Schema> {
fn from(value: Array) -> Self {
Self::RefOr(RefOr::T(Schema::Array(value)))
}
}
impl From<Ref> for AdditionalProperties<Schema> {
fn from(value: Ref) -> Self {
Self::RefOr(RefOr::Ref(value))
}
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
pub struct Ref {
#[serde(rename = "$ref")]
pub ref_location: String,
}
impl Ref {
pub fn new<I: Into<String>>(ref_location: I) -> Self {
Self {
ref_location: ref_location.into(),
}
}
pub fn from_schema_name<I: Into<String>>(schema_name: I) -> Self {
Self::new(format!("#/components/schemas/{}", schema_name.into()))
}
pub fn from_response_name<I: Into<String>>(response_name: I) -> Self {
Self::new(format!("#/components/responses/{}", response_name.into()))
}
}
impl From<Ref> for RefOr<Schema> {
fn from(r: Ref) -> Self {
Self::Ref(r)
}
}
impl<T> From<T> for RefOr<T> {
fn from(t: T) -> Self {
Self::T(t)
}
}
impl Default for RefOr<Schema> {
fn default() -> Self {
Self::T(Schema::Object(Object::new()))
}
}
impl ToArray for RefOr<Schema> {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum SchemaType {
Object,
String,
Integer,
Number,
Boolean,
Array,
}
impl Default for SchemaType {
fn default() -> Self {
Self::Object
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "lowercase", untagged)]
pub enum SchemaFormat {
KnownFormat(KnownFormat),
Custom(String),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum KnownFormat {
Int8,
Int16,
Int32,
Int64,
UInt8,
UInt16,
UInt32,
UInt64,
Float,
Double,
Byte,
Binary,
Date,
#[serde(rename = "date-time")]
DateTime,
Password,
#[cfg(any(feature = "decimal", feature = "decimal-float"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "decimal", feature = "decimal-float"))))]
Decimal,
#[cfg(feature = "url")]
#[cfg_attr(docsrs, doc(cfg(feature = "url")))]
Url,
#[cfg(feature = "ulid")]
#[cfg_attr(docsrs, doc(cfg(feature = "ulid")))]
Ulid,
#[cfg(feature = "uuid")]
#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
Uuid,
}
#[cfg(test)]
mod tests {
use assert_json_diff::assert_json_eq;
use serde_json::{json, Value};
use super::*;
use crate::*;
#[test]
fn create_schema_serializes_json() -> Result<(), serde_json::Error> {
let openapi = OpenApi::new("My api", "1.0.0").components(
Components::new()
.add_schema("Person", Ref::new("#/components/PersonModel"))
.add_schema(
"Credential",
Schema::from(
Object::new()
.property(
"id",
Object::new()
.schema_type(SchemaType::Integer)
.format(SchemaFormat::KnownFormat(KnownFormat::Int32))
.description("Id of credential")
.default_value(json!(1i32)),
)
.property(
"name",
Object::new()
.schema_type(SchemaType::String)
.description("Name of credential"),
)
.property(
"status",
Object::new()
.schema_type(SchemaType::String)
.default_value(json!("Active"))
.description("Credential status")
.enum_values(["Active", "NotActive", "Locked", "Expired"]),
)
.property("history", Array::new(Ref::from_schema_name("UpdateHistory")))
.property("tags", Object::with_type(SchemaType::String).to_array()),
),
),
);
let serialized = serde_json::to_string_pretty(&openapi)?;
println!("serialized json:\n {serialized}");
let value = serde_json::to_value(&openapi)?;
let credential = get_json_path(&value, "components.schemas.Credential.properties");
let person = get_json_path(&value, "components.schemas.Person");
assert!(
credential.get("id").is_some(),
"could not find path: components.schemas.Credential.properties.id"
);
assert!(
credential.get("status").is_some(),
"could not find path: components.schemas.Credential.properties.status"
);
assert!(
credential.get("name").is_some(),
"could not find path: components.schemas.Credential.properties.name"
);
assert!(
credential.get("history").is_some(),
"could not find path: components.schemas.Credential.properties.history"
);
assert_json_eq!(
credential.get("id").unwrap_or(&serde_json::value::Value::Null),
json!({"type":"integer","format":"int32","description":"Id of credential","default":1})
);
assert_json_eq!(
credential.get("name").unwrap_or(&serde_json::value::Value::Null),
json!({"type":"string","description":"Name of credential"})
);
assert_json_eq!(
credential.get("status").unwrap_or(&serde_json::value::Value::Null),
json!({"default":"Active","description":"Credential status","enum":["Active","NotActive","Locked","Expired"],"type":"string"})
);
assert_json_eq!(
credential.get("history").unwrap_or(&serde_json::value::Value::Null),
json!({"items":{"$ref":"#/components/schemas/UpdateHistory"},"type":"array"})
);
assert_eq!(person, &json!({"$ref":"#/components/PersonModel"}));
Ok(())
}
#[test]
fn test_property_order() {
let json_value = Object::new()
.property(
"id",
Object::new()
.schema_type(SchemaType::Integer)
.format(SchemaFormat::KnownFormat(KnownFormat::Int32))
.description("Id of credential")
.default_value(json!(1i32)),
)
.property(
"name",
Object::new()
.schema_type(SchemaType::String)
.description("Name of credential"),
)
.property(
"status",
Object::new()
.schema_type(SchemaType::String)
.default_value(json!("Active"))
.description("Credential status")
.enum_values(["Active", "NotActive", "Locked", "Expired"]),
)
.property("history", Array::new(Ref::from_schema_name("UpdateHistory")))
.property("tags", Object::with_type(SchemaType::String).to_array());
#[cfg(not(feature = "preserve-order"))]
assert_eq!(
json_value.properties.keys().collect::<Vec<_>>(),
vec!["history", "id", "name", "status", "tags"]
);
#[cfg(feature = "preserve-order")]
assert_eq!(
json_value.properties.keys().collect::<Vec<_>>(),
vec!["id", "name", "status", "history", "tags"]
);
}
#[test]
fn test_additional_properties() {
let json_value = Object::new().additional_properties(Object::new().schema_type(SchemaType::String));
assert_json_eq!(
json_value,
json!({
"type": "object",
"additionalProperties": {
"type": "string"
}
})
);
let json_value = Object::new().additional_properties(Array::new(Object::new().schema_type(SchemaType::Number)));
assert_json_eq!(
json_value,
json!({
"type": "object",
"additionalProperties": {
"items": {
"type": "number",
},
"type": "array",
}
})
);
let json_value = Object::new().additional_properties(Ref::from_schema_name("ComplexModel"));
assert_json_eq!(
json_value,
json!({
"type": "object",
"additionalProperties": {
"$ref": "#/components/schemas/ComplexModel"
}
})
)
}
#[test]
fn test_object_with_name() {
let json_value = Object::new().name("SomeName");
assert_json_eq!(
json_value,
json!({
"type": "object",
"name": "SomeName"
})
);
}
#[test]
fn derive_object_with_example() {
let expected = r#"{"type":"object","example":{"age":20,"name":"bob the cat"}}"#;
let json_value = Object::new().example(json!({"age": 20, "name": "bob the cat"}));
let value_string = serde_json::to_string(&json_value).unwrap();
assert_eq!(
value_string, expected,
"value string != expected string, {value_string} != {expected}"
);
}
fn get_json_path<'a>(value: &'a Value, path: &str) -> &'a Value {
path.split('.').fold(value, |acc, fragment| {
acc.get(fragment).unwrap_or(&serde_json::value::Value::Null)
})
}
#[test]
fn test_array_new() {
let array = Array::new(
Object::new().property(
"id",
Object::new()
.schema_type(SchemaType::Integer)
.format(SchemaFormat::KnownFormat(KnownFormat::Int32))
.description("Id of credential")
.default_value(json!(1i32)),
),
);
assert!(matches!(array.schema_type, SchemaType::Array));
}
#[test]
fn test_array_builder() {
let array: Array = Array::new(
Object::new().property(
"id",
Object::new()
.schema_type(SchemaType::Integer)
.format(SchemaFormat::KnownFormat(KnownFormat::Int32))
.description("Id of credential")
.default_value(json!(1i32)),
),
);
assert!(matches!(array.schema_type, SchemaType::Array));
}
#[test]
fn reserialize_deserialized_schema_components() {
let components = Components::new()
.extend_schemas(vec![(
"Comp",
Schema::from(
Object::new()
.property("name", Object::new().schema_type(SchemaType::String))
.required("name"),
),
)])
.response("204", Response::new("No Content"))
.extend_responses(vec![("200", Response::new("Okay"))])
.add_security_scheme("TLS", SecurityScheme::MutualTls { description: None })
.extend_security_schemes(vec![("APIKey", SecurityScheme::Http(security::Http::default()))]);
let serialized_components = serde_json::to_string(&components).unwrap();
let deserialized_components: Components = serde_json::from_str(serialized_components.as_str()).unwrap();
assert_eq!(
serialized_components,
serde_json::to_string(&deserialized_components).unwrap()
)
}
#[test]
fn reserialize_deserialized_object_component() {
let prop = Object::new()
.property("name", Object::new().schema_type(SchemaType::String))
.required("name");
let serialized_components = serde_json::to_string(&prop).unwrap();
let deserialized_components: Object = serde_json::from_str(serialized_components.as_str()).unwrap();
assert_eq!(
serialized_components,
serde_json::to_string(&deserialized_components).unwrap()
)
}
#[test]
fn reserialize_deserialized_property() {
let prop = Object::new().schema_type(SchemaType::String);
let serialized_components = serde_json::to_string(&prop).unwrap();
let deserialized_components: Object = serde_json::from_str(serialized_components.as_str()).unwrap();
assert_eq!(
serialized_components,
serde_json::to_string(&deserialized_components).unwrap()
)
}
#[test]
fn serialize_deserialize_array_within_ref_or_t_object_builder() {
let ref_or_schema = RefOr::T(Schema::Object(Object::new().property(
"test",
RefOr::T(Schema::Array(Array::new(RefOr::T(Schema::Object(
Object::new().property("element", RefOr::Ref(Ref::new("#/test"))),
))))),
)));
let json_str = serde_json::to_string(&ref_or_schema).expect("");
println!("----------------------------");
println!("{json_str}");
let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
let json_de_str = serde_json::to_string(&deserialized).expect("");
println!("----------------------------");
println!("{json_de_str}");
assert_eq!(json_str, json_de_str);
}
#[test]
fn serialize_deserialize_one_of_within_ref_or_t_object_builder() {
let ref_or_schema = RefOr::T(Schema::Object(
Object::new().property(
"test",
RefOr::T(Schema::OneOf(
OneOf::new()
.item(Schema::Array(Array::new(RefOr::T(Schema::Object(
Object::new().property("element", RefOr::Ref(Ref::new("#/test"))),
)))))
.item(Schema::Array(Array::new(RefOr::T(Schema::Object(
Object::new().property("foobar", RefOr::Ref(Ref::new("#/foobar"))),
))))),
)),
),
));
let json_str = serde_json::to_string(&ref_or_schema).expect("");
println!("----------------------------");
println!("{json_str}");
let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
let json_de_str = serde_json::to_string(&deserialized).expect("");
println!("----------------------------");
println!("{json_de_str}");
assert_eq!(json_str, json_de_str);
}
#[test]
fn serialize_deserialize_all_of_of_within_ref_or_t_object_builder() {
let ref_or_schema = RefOr::T(Schema::Object(
Object::new().property(
"test",
RefOr::T(Schema::AllOf(
AllOf::new()
.item(Schema::Array(Array::new(RefOr::T(Schema::Object(
Object::new().property("element", RefOr::Ref(Ref::new("#/test"))),
)))))
.item(RefOr::T(Schema::Object(
Object::new().property("foobar", RefOr::Ref(Ref::new("#/foobar"))),
))),
)),
),
));
let json_str = serde_json::to_string(&ref_or_schema).expect("");
println!("----------------------------");
println!("{json_str}");
let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
let json_de_str = serde_json::to_string(&deserialized).expect("");
println!("----------------------------");
println!("{json_de_str}");
assert_eq!(json_str, json_de_str);
}
#[test]
fn serialize_deserialize_any_of_of_within_ref_or_t_object_builder() {
let ref_or_schema = RefOr::T(Schema::Object(
Object::new().property(
"test",
RefOr::T(Schema::AnyOf(
AnyOf::new()
.item(Schema::Array(Array::new(RefOr::T(Schema::Object(
Object::new().property("element", RefOr::Ref(Ref::new("#/test"))),
)))))
.item(RefOr::T(Schema::Object(
Object::new().property("foobar", RefOr::Ref(Ref::new("#/foobar"))),
))),
)),
),
));
let json_str = serde_json::to_string(&ref_or_schema).expect("");
println!("----------------------------");
println!("{json_str}");
let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
let json_de_str = serde_json::to_string(&deserialized).expect("");
println!("----------------------------");
println!("{json_de_str}");
assert!(json_str.contains("\"anyOf\""));
assert_eq!(json_str, json_de_str);
}
#[test]
fn serialize_deserialize_schema_array_ref_or_t() {
let ref_or_schema = RefOr::T(Schema::Array(Array::new(RefOr::T(Schema::Object(
Object::new().property("element", RefOr::Ref(Ref::new("#/test"))),
)))));
let json_str = serde_json::to_string(&ref_or_schema).expect("");
println!("----------------------------");
println!("{json_str}");
let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
let json_de_str = serde_json::to_string(&deserialized).expect("");
println!("----------------------------");
println!("{json_de_str}");
assert_eq!(json_str, json_de_str);
}
#[test]
fn serialize_deserialize_schema_array_builder() {
let ref_or_schema = Array::new(RefOr::T(Schema::Object(
Object::new().property("element", RefOr::Ref(Ref::new("#/test"))),
)));
let json_str = serde_json::to_string(&ref_or_schema).expect("");
println!("----------------------------");
println!("{json_str}");
let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
let json_de_str = serde_json::to_string(&deserialized).expect("");
println!("----------------------------");
println!("{json_de_str}");
assert_eq!(json_str, json_de_str);
}
#[test]
fn serialize_deserialize_schema_with_additional_properties() {
let schema = Schema::Object(Object::new().property(
"map",
Object::new().additional_properties(AdditionalProperties::FreeForm(true)),
));
let json_str = serde_json::to_string(&schema).unwrap();
println!("----------------------------");
println!("{json_str}");
let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).unwrap();
let json_de_str = serde_json::to_string(&deserialized).unwrap();
println!("----------------------------");
println!("{json_de_str}");
assert_eq!(json_str, json_de_str);
}
#[test]
fn serialize_deserialize_schema_with_additional_properties_object() {
let schema = Schema::Object(Object::new().property(
"map",
Object::new().additional_properties(Object::new().property("name", Object::with_type(SchemaType::String))),
));
let json_str = serde_json::to_string(&schema).unwrap();
println!("----------------------------");
println!("{json_str}");
let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).unwrap();
let json_de_str = serde_json::to_string(&deserialized).unwrap();
println!("----------------------------");
println!("{json_de_str}");
assert_eq!(json_str, json_de_str);
}
#[test]
fn serialize_discriminator_with_mapping() {
let mut discriminator = Discriminator::new("type");
discriminator.mapping = [("int".to_string(), "#/components/schemas/MyInt".to_string())]
.into_iter()
.collect::<BTreeMap<_, _>>();
let one_of = OneOf::new()
.item(Ref::from_schema_name("MyInt"))
.discriminator(discriminator);
let json_value = serde_json::to_value(one_of).unwrap();
assert_json_eq!(
json_value,
json!({
"oneOf": [
{
"$ref": "#/components/schemas/MyInt"
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"int": "#/components/schemas/MyInt"
}
}
})
);
}
#[test]
fn test_empty_schema() {
let schema = empty();
assert_json_eq!(
schema,
json!({
"type": "object",
"nullable": true,
"default": null
})
)
}
#[test]
fn test_default_schema() {
let schema = Schema::default();
assert_json_eq!(
schema,
json!({
"type": "object",
})
)
}
#[test]
fn test_ref_from_response_name() {
let _ref = Ref::from_response_name("MyResponse");
assert_json_eq!(
_ref,
json!({
"$ref": "#/components/responses/MyResponse"
})
)
}
#[test]
fn test_additional_properties_from_ref_or() {
let additional_properties = AdditionalProperties::from(RefOr::T(Schema::Object(Object::new())));
assert_json_eq!(
additional_properties,
json!({
"type": "object",
})
)
}
}