use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Schema {
Boolean(bool),
Ref(RefSchema),
Object(ObjectSchema),
Array(ArraySchema),
Primitive(PrimitiveSchema),
Enum(EnumSchema),
OneOf(OneOfSchema),
}
impl Schema {
pub fn string() -> Self {
Schema::Primitive(PrimitiveSchema::string())
}
pub fn integer(format: Option<&str>) -> Self {
Schema::Primitive(PrimitiveSchema::integer(format))
}
pub fn number(format: Option<&str>) -> Self {
Schema::Primitive(PrimitiveSchema::number(format))
}
pub fn boolean() -> Self {
Schema::Primitive(PrimitiveSchema::boolean())
}
pub fn reference(name: &str) -> Self {
Schema::Ref(RefSchema {
reference: format!("#/components/schemas/{name}"),
})
}
pub fn array(items: Schema) -> Self {
Schema::Array(ArraySchema {
items: Box::new(items),
min_items: None,
max_items: None,
})
}
pub fn object(properties: HashMap<String, Schema>, required: Vec<String>) -> Self {
Schema::Object(ObjectSchema {
title: None,
description: None,
properties,
required,
additional_properties: None,
})
}
#[must_use]
pub fn nullable(mut self) -> Self {
if let Schema::Primitive(ref mut p) = self {
p.nullable = true;
}
self
}
#[must_use]
pub fn with_title(mut self, title: impl Into<String>) -> Self {
if let Schema::Object(ref mut o) = self {
o.title = Some(title.into());
}
self
}
#[must_use]
pub fn with_description(mut self, description: impl Into<String>) -> Self {
if let Schema::Object(ref mut o) = self {
o.description = Some(description.into());
}
self
}
pub fn string_enum(values: Vec<String>) -> Self {
Schema::Enum(EnumSchema {
schema_type: SchemaType::String,
enum_values: values,
})
}
pub fn one_of(schemas: Vec<Schema>) -> Self {
Schema::OneOf(OneOfSchema { one_of: schemas })
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefSchema {
#[serde(rename = "$ref")]
pub reference: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ObjectSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub properties: HashMap<String, Schema>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub required: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub additional_properties: Option<Box<Schema>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArraySchema {
pub items: Box<Schema>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_items: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_items: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnumSchema {
#[serde(rename = "type")]
pub schema_type: SchemaType,
#[serde(rename = "enum")]
pub enum_values: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OneOfSchema {
#[serde(rename = "oneOf")]
pub one_of: Vec<Schema>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrimitiveSchema {
#[serde(rename = "type")]
pub schema_type: SchemaType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub nullable: bool,
}
impl PrimitiveSchema {
pub fn string() -> Self {
Self {
schema_type: SchemaType::String,
format: None,
nullable: false,
}
}
pub fn integer(format: Option<&str>) -> Self {
Self {
schema_type: SchemaType::Integer,
format: format.map(String::from),
nullable: false,
}
}
pub fn number(format: Option<&str>) -> Self {
Self {
schema_type: SchemaType::Number,
format: format.map(String::from),
nullable: false,
}
}
pub fn boolean() -> Self {
Self {
schema_type: SchemaType::Boolean,
format: None,
nullable: false,
}
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_false(b: &bool) -> bool {
!*b
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SchemaType {
String,
Number,
Integer,
Boolean,
Null,
}
pub trait JsonSchema {
fn schema() -> Schema;
#[must_use]
fn schema_name() -> Option<&'static str> {
None
}
}
impl JsonSchema for String {
fn schema() -> Schema {
Schema::Primitive(PrimitiveSchema {
schema_type: SchemaType::String,
format: None,
nullable: false,
})
}
}
impl JsonSchema for i64 {
fn schema() -> Schema {
Schema::Primitive(PrimitiveSchema {
schema_type: SchemaType::Integer,
format: Some("int64".to_string()),
nullable: false,
})
}
}
impl JsonSchema for i32 {
fn schema() -> Schema {
Schema::Primitive(PrimitiveSchema {
schema_type: SchemaType::Integer,
format: Some("int32".to_string()),
nullable: false,
})
}
}
impl JsonSchema for f64 {
fn schema() -> Schema {
Schema::Primitive(PrimitiveSchema {
schema_type: SchemaType::Number,
format: Some("double".to_string()),
nullable: false,
})
}
}
impl JsonSchema for bool {
fn schema() -> Schema {
Schema::Primitive(PrimitiveSchema {
schema_type: SchemaType::Boolean,
format: None,
nullable: false,
})
}
}
impl<T: JsonSchema> JsonSchema for Option<T> {
fn schema() -> Schema {
match T::schema() {
Schema::Primitive(mut p) => {
p.nullable = true;
Schema::Primitive(p)
}
other => other,
}
}
}
impl<T: JsonSchema> JsonSchema for Vec<T> {
fn schema() -> Schema {
Schema::Array(ArraySchema {
items: Box::new(T::schema()),
min_items: None,
max_items: None,
})
}
}