pub mod attributes;
pub mod error;
pub mod formats;
pub mod registry;
pub mod types;
pub use attributes::{
EnumAttribute, FieldAttribute, SchemaAttributes, TypeAttribute, VariantAttribute,
};
pub use error::{SchemaError, SchemaResult};
pub use registry::SchemaRegistry;
pub use types::{
EnumRepresentation, EnumVariant, FieldDefinition, PrimitiveType, SchemaDefinition, SchemaType,
StructField, TypeReference,
};
#[doc(hidden)]
pub mod __private {
pub use indexmap::IndexMap;
pub use serde_json::json;
}
pub trait Schema: Sized {
fn schema_definition() -> SchemaDefinition;
fn schema_name() -> &'static str;
fn type_reference() -> TypeReference {
TypeReference::Named(Self::schema_name().to_string())
}
#[cfg(feature = "json-schema")]
fn json_schema() -> String {
formats::json_schema::generate(&Self::schema_definition())
}
#[cfg(feature = "json-schema")]
fn json_schema_value() -> serde_json::Value {
formats::json_schema::generate_value(&Self::schema_definition())
}
#[cfg(feature = "openapi")]
fn openapi_schema() -> String {
formats::openapi::generate(&Self::schema_definition())
}
#[cfg(feature = "graphql")]
fn graphql_sdl() -> String {
formats::graphql::generate(&Self::schema_definition())
}
#[cfg(feature = "protobuf")]
fn proto_definition() -> String {
formats::protobuf::generate(&Self::schema_definition())
}
#[cfg(feature = "typescript")]
fn typescript_type() -> String {
formats::typescript::generate(&Self::schema_definition())
}
#[cfg(feature = "avro")]
fn avro_schema() -> String {
formats::avro::generate(&Self::schema_definition())
}
}
pub trait FlattenableSchema: Schema {
fn flattened_fields() -> Vec<FieldDefinition>;
}
pub trait ExternalSchema {
type Target;
fn schema_definition() -> SchemaDefinition;
fn schema_name() -> &'static str {
std::any::type_name::<Self::Target>()
.rsplit("::")
.next()
.unwrap_or("Unknown")
}
}
macro_rules! impl_schema_for_primitive {
($ty:ty, $prim:expr, $name:expr) => {
impl Schema for $ty {
fn schema_definition() -> SchemaDefinition {
SchemaDefinition::new($name, SchemaType::Primitive($prim))
}
fn schema_name() -> &'static str {
$name
}
}
};
}
impl_schema_for_primitive!(bool, PrimitiveType::Bool, "bool");
impl_schema_for_primitive!(i8, PrimitiveType::I8, "i8");
impl_schema_for_primitive!(i16, PrimitiveType::I16, "i16");
impl_schema_for_primitive!(i32, PrimitiveType::I32, "i32");
impl_schema_for_primitive!(i64, PrimitiveType::I64, "i64");
impl_schema_for_primitive!(i128, PrimitiveType::I128, "i128");
impl_schema_for_primitive!(isize, PrimitiveType::Isize, "isize");
impl_schema_for_primitive!(u8, PrimitiveType::U8, "u8");
impl_schema_for_primitive!(u16, PrimitiveType::U16, "u16");
impl_schema_for_primitive!(u32, PrimitiveType::U32, "u32");
impl_schema_for_primitive!(u64, PrimitiveType::U64, "u64");
impl_schema_for_primitive!(u128, PrimitiveType::U128, "u128");
impl_schema_for_primitive!(usize, PrimitiveType::Usize, "usize");
impl_schema_for_primitive!(f32, PrimitiveType::F32, "f32");
impl_schema_for_primitive!(f64, PrimitiveType::F64, "f64");
impl_schema_for_primitive!(char, PrimitiveType::Char, "char");
impl_schema_for_primitive!(String, PrimitiveType::String, "String");
impl_schema_for_primitive!(&str, PrimitiveType::String, "str");
impl<T: Schema> Schema for Option<T> {
fn schema_definition() -> SchemaDefinition {
let inner = T::schema_definition();
SchemaDefinition::new(
"Option".to_string(),
SchemaType::Option(Box::new(inner.schema_type)),
)
}
fn schema_name() -> &'static str {
"Option"
}
fn type_reference() -> TypeReference {
TypeReference::Option(Box::new(T::type_reference()))
}
}
impl<T: Schema> Schema for Vec<T> {
fn schema_definition() -> SchemaDefinition {
let inner = T::schema_definition();
SchemaDefinition::new(
"Vec".to_string(),
SchemaType::Array(Box::new(inner.schema_type)),
)
}
fn schema_name() -> &'static str {
"Vec"
}
fn type_reference() -> TypeReference {
TypeReference::Array(Box::new(T::type_reference()))
}
}
impl<T: Schema> Schema for std::collections::HashSet<T> {
fn schema_definition() -> SchemaDefinition {
let inner = T::schema_definition();
SchemaDefinition::new(
"HashSet".to_string(),
SchemaType::Set(Box::new(inner.schema_type)),
)
}
fn schema_name() -> &'static str {
"HashSet"
}
fn type_reference() -> TypeReference {
TypeReference::Set(Box::new(T::type_reference()))
}
}
impl<T: Schema> Schema for std::collections::BTreeSet<T> {
fn schema_definition() -> SchemaDefinition {
let inner = T::schema_definition();
SchemaDefinition::new(
"BTreeSet".to_string(),
SchemaType::Set(Box::new(inner.schema_type)),
)
}
fn schema_name() -> &'static str {
"BTreeSet"
}
fn type_reference() -> TypeReference {
TypeReference::Set(Box::new(T::type_reference()))
}
}
impl<K: Schema, V: Schema> Schema for std::collections::HashMap<K, V> {
fn schema_definition() -> SchemaDefinition {
let value = V::schema_definition();
SchemaDefinition::new(
"HashMap".to_string(),
SchemaType::Map(Box::new(value.schema_type)),
)
}
fn schema_name() -> &'static str {
"HashMap"
}
fn type_reference() -> TypeReference {
TypeReference::Map(Box::new(V::type_reference()))
}
}
impl<K: Schema, V: Schema> Schema for std::collections::BTreeMap<K, V> {
fn schema_definition() -> SchemaDefinition {
let value = V::schema_definition();
SchemaDefinition::new(
"BTreeMap".to_string(),
SchemaType::Map(Box::new(value.schema_type)),
)
}
fn schema_name() -> &'static str {
"BTreeMap"
}
fn type_reference() -> TypeReference {
TypeReference::Map(Box::new(V::type_reference()))
}
}
impl<T: Schema> Schema for Box<T> {
fn schema_definition() -> SchemaDefinition {
T::schema_definition()
}
fn schema_name() -> &'static str {
T::schema_name()
}
fn type_reference() -> TypeReference {
T::type_reference()
}
}
macro_rules! impl_schema_for_tuple {
($($T:ident),+) => {
impl<$($T: Schema),+> Schema for ($($T,)+) {
fn schema_definition() -> SchemaDefinition {
let types = vec![
$(Box::new($T::schema_definition().schema_type)),+
];
SchemaDefinition::new(
"Tuple",
SchemaType::Tuple(types),
)
}
fn schema_name() -> &'static str {
"Tuple"
}
fn type_reference() -> TypeReference {
TypeReference::Tuple(vec![$($T::type_reference()),+])
}
}
};
}
impl_schema_for_tuple!(T0);
impl_schema_for_tuple!(T0, T1);
impl_schema_for_tuple!(T0, T1, T2);
impl_schema_for_tuple!(T0, T1, T2, T3);
impl_schema_for_tuple!(T0, T1, T2, T3, T4);
impl_schema_for_tuple!(T0, T1, T2, T3, T4, T5);
impl_schema_for_tuple!(T0, T1, T2, T3, T4, T5, T6);
impl_schema_for_tuple!(T0, T1, T2, T3, T4, T5, T6, T7);
impl_schema_for_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
impl_schema_for_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
impl_schema_for_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
impl_schema_for_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
impl Schema for () {
fn schema_definition() -> SchemaDefinition {
SchemaDefinition::new("Unit", SchemaType::Unit)
}
fn schema_name() -> &'static str {
"()"
}
fn type_reference() -> TypeReference {
TypeReference::Unit
}
}
#[cfg(feature = "uuid-support")]
impl Schema for uuid::Uuid {
fn schema_definition() -> SchemaDefinition {
SchemaDefinition::new("Uuid", SchemaType::Primitive(PrimitiveType::String))
.with_format("uuid")
.with_description("A universally unique identifier (UUID)")
}
fn schema_name() -> &'static str {
"Uuid"
}
}
#[cfg(feature = "chrono-support")]
mod chrono_impls {
use super::*;
impl<Tz: chrono::TimeZone> Schema for chrono::DateTime<Tz> {
fn schema_definition() -> SchemaDefinition {
SchemaDefinition::new("DateTime", SchemaType::Primitive(PrimitiveType::String))
.with_format("date-time")
.with_description("An RFC 3339 date-time string")
}
fn schema_name() -> &'static str {
"DateTime"
}
}
impl Schema for chrono::NaiveDate {
fn schema_definition() -> SchemaDefinition {
SchemaDefinition::new("Date", SchemaType::Primitive(PrimitiveType::String))
.with_format("date")
.with_description("A date in YYYY-MM-DD format")
}
fn schema_name() -> &'static str {
"Date"
}
}
impl Schema for chrono::NaiveTime {
fn schema_definition() -> SchemaDefinition {
SchemaDefinition::new("Time", SchemaType::Primitive(PrimitiveType::String))
.with_format("time")
.with_description("A time in HH:MM:SS format")
}
fn schema_name() -> &'static str {
"Time"
}
}
impl Schema for chrono::NaiveDateTime {
fn schema_definition() -> SchemaDefinition {
SchemaDefinition::new("NaiveDateTime", SchemaType::Primitive(PrimitiveType::String))
.with_format("date-time")
.with_description("A date-time without timezone information")
}
fn schema_name() -> &'static str {
"NaiveDateTime"
}
}
impl Schema for chrono::Duration {
fn schema_definition() -> SchemaDefinition {
SchemaDefinition::new("Duration", SchemaType::Primitive(PrimitiveType::String))
.with_format("duration")
.with_description("An ISO 8601 duration string")
}
fn schema_name() -> &'static str {
"Duration"
}
}
}
#[cfg(feature = "url-support")]
impl Schema for url::Url {
fn schema_definition() -> SchemaDefinition {
SchemaDefinition::new("Url", SchemaType::Primitive(PrimitiveType::String))
.with_format("uri")
.with_description("A valid URL")
}
fn schema_name() -> &'static str {
"Url"
}
}
impl Schema for serde_json::Value {
fn schema_definition() -> SchemaDefinition {
SchemaDefinition::new("JsonValue", SchemaType::Any)
.with_description("Any valid JSON value")
}
fn schema_name() -> &'static str {
"JsonValue"
}
fn type_reference() -> TypeReference {
TypeReference::Any
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_primitive_schema() {
let def = String::schema_definition();
assert_eq!(def.name, "String");
assert!(matches!(
def.schema_type,
SchemaType::Primitive(PrimitiveType::String)
));
}
#[test]
fn test_option_schema() {
let def = Option::<String>::schema_definition();
assert!(matches!(def.schema_type, SchemaType::Option(_)));
}
#[test]
fn test_vec_schema() {
let def = Vec::<i32>::schema_definition();
assert!(matches!(def.schema_type, SchemaType::Array(_)));
}
#[test]
fn test_hashmap_schema() {
let def = std::collections::HashMap::<String, i32>::schema_definition();
assert!(matches!(def.schema_type, SchemaType::Map(_)));
}
}