Add SchemaGenerator
This commit is contained in:
parent
b3924feb87
commit
f4ca23ddeb
6 changed files with 146 additions and 85 deletions
1
schemars/rust-toolchain
Normal file
1
schemars/rust-toolchain
Normal file
|
@ -0,0 +1 @@
|
||||||
|
nightly
|
69
schemars/src/generator.rs
Normal file
69
schemars/src/generator.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
use crate::make_schema::MakeSchema;
|
||||||
|
use crate::schema::*;
|
||||||
|
use core::any::{type_name, TypeId};
|
||||||
|
use std::collections::HashMap as Map;
|
||||||
|
use std::collections::HashSet as Set;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct SchemaGenerator {
|
||||||
|
names: Set<String>,
|
||||||
|
definitions: Map<TypeId, (String, Schema)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SchemaGenerator {
|
||||||
|
pub fn new() -> SchemaGenerator {
|
||||||
|
SchemaGenerator {
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subschema_for<T: MakeSchema + 'static>(&mut self) -> Schema {
|
||||||
|
let type_id = TypeId::of::<T>();
|
||||||
|
// TODO is there a nicer way to do this?
|
||||||
|
if !self.definitions.contains_key(&type_id) {
|
||||||
|
let name = self.make_unique_name::<T>();
|
||||||
|
self.names.insert(name.clone());
|
||||||
|
// insert into definitions BEFORE calling make_schema to avoid infinite recursion
|
||||||
|
let dummy = Schema::Bool(false);
|
||||||
|
self.definitions.insert(type_id, (name.clone(), dummy));
|
||||||
|
|
||||||
|
let schema = T::make_schema(self);
|
||||||
|
self.definitions
|
||||||
|
.entry(type_id)
|
||||||
|
.and_modify(|(_, s)| *s = schema);
|
||||||
|
}
|
||||||
|
let ref name = self.definitions.get(&type_id).unwrap().0;
|
||||||
|
SchemaRef {
|
||||||
|
reference: format!("#/definitions/{}", name),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root_schema_for<T: MakeSchema>(&mut self) -> Schema {
|
||||||
|
let schema = T::make_schema(self);
|
||||||
|
if let Schema::Object(mut o) = schema {
|
||||||
|
o.schema = Some("http://json-schema.org/draft-07/schema#".to_owned());
|
||||||
|
o.title = Some(Self::schema_name::<T>());
|
||||||
|
for (_, (name, schema)) in self.definitions.iter() {
|
||||||
|
o.definitions.insert(name.clone(), schema.clone());
|
||||||
|
}
|
||||||
|
return Schema::Object(o);
|
||||||
|
}
|
||||||
|
schema
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schema_name<T: MakeSchema>() -> String {
|
||||||
|
let override_name = T::override_schema_name();
|
||||||
|
if override_name.is_empty() {
|
||||||
|
type_name::<T>().to_owned()
|
||||||
|
} else {
|
||||||
|
override_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_unique_name<T: MakeSchema>(&mut self) -> String {
|
||||||
|
Self::schema_name::<T>()
|
||||||
|
// TODO remove namespace, remove special chars
|
||||||
|
// TODO enforce uniqueness
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod make_schema;
|
pub mod make_schema;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
|
pub mod generator;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
mod make_schema;
|
pub mod generator;
|
||||||
mod schema;
|
pub mod make_schema;
|
||||||
|
pub mod schema;
|
||||||
|
|
||||||
use make_schema::MakeSchema;
|
use make_schema::MakeSchema;
|
||||||
|
use schema::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Result;
|
use serde_json::Result;
|
||||||
|
use std::collections::BTreeMap as Map;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
|
use crate::generator::SchemaGenerator;
|
||||||
use crate::schema::*;
|
use crate::schema::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::collections::BTreeMap as Map;
|
use std::collections::BTreeMap as Map;
|
||||||
|
|
||||||
pub trait MakeSchema {
|
pub trait MakeSchema {
|
||||||
fn make_schema() -> Schema;
|
fn override_schema_name() -> String {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn makes_ref_schema() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_schema(generator: &mut SchemaGenerator) -> Schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO structs, enums, tuples
|
// TODO structs, enums, tuples
|
||||||
|
@ -15,16 +24,15 @@ pub trait MakeSchema {
|
||||||
// Path, PathBuf, OsStr, OsString, Wrapping<T>, Reverse<T>, AtomicBool, AtomixI8 etc.,
|
// Path, PathBuf, OsStr, OsString, Wrapping<T>, Reverse<T>, AtomicBool, AtomixI8 etc.,
|
||||||
// NonZeroU8 etc., ArcWeak, RcWeak, BTreeMap, HashMap, (!)?, Bound?, Range?, RangeInclusive?,
|
// NonZeroU8 etc., ArcWeak, RcWeak, BTreeMap, HashMap, (!)?, Bound?, Range?, RangeInclusive?,
|
||||||
// PhantomData?, CString?, CStr?, fmt::Arguments?
|
// PhantomData?, CString?, CStr?, fmt::Arguments?
|
||||||
// !map keys must be Into<String>!
|
|
||||||
|
|
||||||
////////// PRIMITIVES (except ints) //////////
|
////////// PRIMITIVES //////////
|
||||||
|
|
||||||
macro_rules! simple_impl {
|
macro_rules! simple_impl {
|
||||||
($type:tt => $instance_type:expr) => {
|
($type:tt => $instance_type:tt) => {
|
||||||
impl MakeSchema for $type {
|
impl MakeSchema for $type {
|
||||||
fn make_schema() -> Schema {
|
fn make_schema(_: &mut SchemaGenerator) -> Schema {
|
||||||
SchemaObject {
|
SchemaObject {
|
||||||
instance_type: Some($instance_type.into()),
|
instance_type: Some(InstanceType::$instance_type.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
|
@ -33,15 +41,27 @@ macro_rules! simple_impl {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
simple_impl!(str => InstanceType::String);
|
simple_impl!(str => String);
|
||||||
simple_impl!(String => InstanceType::String);
|
simple_impl!(String => String);
|
||||||
simple_impl!(bool => InstanceType::Boolean);
|
simple_impl!(bool => Boolean);
|
||||||
simple_impl!(f32 => InstanceType::Number);
|
simple_impl!(f32 => Number);
|
||||||
simple_impl!(f64 => InstanceType::Number);
|
simple_impl!(f64 => Number);
|
||||||
simple_impl!(() => InstanceType::Null);
|
simple_impl!(i8 => Integer);
|
||||||
|
simple_impl!(i16 => Integer);
|
||||||
|
simple_impl!(i32 => Integer);
|
||||||
|
simple_impl!(i64 => Integer);
|
||||||
|
simple_impl!(i128 => Integer);
|
||||||
|
simple_impl!(isize => Integer);
|
||||||
|
simple_impl!(u8 => Integer);
|
||||||
|
simple_impl!(u16 => Integer);
|
||||||
|
simple_impl!(u32 => Integer);
|
||||||
|
simple_impl!(u64 => Integer);
|
||||||
|
simple_impl!(u128 => Integer);
|
||||||
|
simple_impl!(usize => Integer);
|
||||||
|
simple_impl!(() => Null);
|
||||||
|
|
||||||
impl MakeSchema for char {
|
impl MakeSchema for char {
|
||||||
fn make_schema() -> Schema {
|
fn make_schema(_: &mut SchemaGenerator) -> Schema {
|
||||||
let mut extra_properties = Map::new();
|
let mut extra_properties = Map::new();
|
||||||
extra_properties.insert("minLength".to_owned(), json!(1));
|
extra_properties.insert("minLength".to_owned(), json!(1));
|
||||||
extra_properties.insert("maxLength".to_owned(), json!(1));
|
extra_properties.insert("maxLength".to_owned(), json!(1));
|
||||||
|
@ -54,45 +74,11 @@ impl MakeSchema for char {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
////////// INTS //////////
|
|
||||||
|
|
||||||
macro_rules! int_impl {
|
|
||||||
($type:ident) => {
|
|
||||||
impl MakeSchema for $type {
|
|
||||||
fn make_schema() -> Schema {
|
|
||||||
let mut extra_properties = Map::new();
|
|
||||||
// this may be overkill...
|
|
||||||
extra_properties.insert("minimum".to_owned(), json!($type::min_value()));
|
|
||||||
extra_properties.insert("maximum".to_owned(), json!($type::max_value()));
|
|
||||||
SchemaObject {
|
|
||||||
instance_type: Some(InstanceType::Integer.into()),
|
|
||||||
extra_properties,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
int_impl!(i8);
|
|
||||||
int_impl!(i16);
|
|
||||||
int_impl!(i32);
|
|
||||||
int_impl!(i64);
|
|
||||||
int_impl!(i128);
|
|
||||||
int_impl!(isize);
|
|
||||||
int_impl!(u8);
|
|
||||||
int_impl!(u16);
|
|
||||||
int_impl!(u32);
|
|
||||||
int_impl!(u64);
|
|
||||||
int_impl!(u128);
|
|
||||||
int_impl!(usize);
|
|
||||||
|
|
||||||
////////// ARRAYS //////////
|
////////// ARRAYS //////////
|
||||||
|
|
||||||
// Does not require T: MakeSchema.
|
// Does not require T: MakeSchema.
|
||||||
impl<T> MakeSchema for [T; 0] {
|
impl<T> MakeSchema for [T; 0] {
|
||||||
fn make_schema() -> Schema {
|
fn make_schema(_: &mut SchemaGenerator) -> Schema {
|
||||||
let mut extra_properties = Map::new();
|
let mut extra_properties = Map::new();
|
||||||
extra_properties.insert("maxItems".to_owned(), json!(0));
|
extra_properties.insert("maxItems".to_owned(), json!(0));
|
||||||
SchemaObject {
|
SchemaObject {
|
||||||
|
@ -107,15 +93,15 @@ impl<T> MakeSchema for [T; 0] {
|
||||||
macro_rules! array_impls {
|
macro_rules! array_impls {
|
||||||
($($len:tt)+) => {
|
($($len:tt)+) => {
|
||||||
$(
|
$(
|
||||||
impl<T: MakeSchema> MakeSchema for [T; $len]
|
impl<T: MakeSchema + 'static> MakeSchema for [T; $len]
|
||||||
{
|
{
|
||||||
fn make_schema() -> Schema {
|
fn make_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||||
let mut extra_properties = Map::new();
|
let mut extra_properties = Map::new();
|
||||||
extra_properties.insert("minItems".to_owned(), json!($len));
|
extra_properties.insert("minItems".to_owned(), json!($len));
|
||||||
extra_properties.insert("maxItems".to_owned(), json!($len));
|
extra_properties.insert("maxItems".to_owned(), json!($len));
|
||||||
SchemaObject {
|
SchemaObject {
|
||||||
instance_type: Some(InstanceType::Array.into()),
|
instance_type: Some(InstanceType::Array.into()),
|
||||||
items: Some(Box::from(T::make_schema())),
|
items: Some(Box::from(gen.subschema_for::<T>())),
|
||||||
extra_properties,
|
extra_properties,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}.into()
|
}.into()
|
||||||
|
@ -138,13 +124,13 @@ macro_rules! seq_impl {
|
||||||
($($desc:tt)+) => {
|
($($desc:tt)+) => {
|
||||||
impl $($desc)+
|
impl $($desc)+
|
||||||
where
|
where
|
||||||
T: MakeSchema,
|
T: MakeSchema + 'static,
|
||||||
{
|
{
|
||||||
fn make_schema() -> Schema
|
fn make_schema(gen: &mut SchemaGenerator) -> Schema
|
||||||
{
|
{
|
||||||
SchemaObject {
|
SchemaObject {
|
||||||
instance_type: Some(InstanceType::Array.into()),
|
instance_type: Some(InstanceType::Array.into()),
|
||||||
items: Some(Box::from(T::make_schema())),
|
items: Some(Box::from(gen.subschema_for::<T>())),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}.into()
|
}.into()
|
||||||
}
|
}
|
||||||
|
@ -166,14 +152,14 @@ macro_rules! map_impl {
|
||||||
impl $($desc)+
|
impl $($desc)+
|
||||||
where
|
where
|
||||||
K: Into<String>,
|
K: Into<String>,
|
||||||
T: MakeSchema,
|
T: MakeSchema + 'static,
|
||||||
{
|
{
|
||||||
fn make_schema() -> Schema
|
fn make_schema(gen: &mut SchemaGenerator) -> Schema
|
||||||
{
|
{
|
||||||
let mut extra_properties = Map::new();
|
let mut extra_properties = Map::new();
|
||||||
extra_properties.insert(
|
extra_properties.insert(
|
||||||
"additionalProperties".to_owned(),
|
"additionalProperties".to_owned(),
|
||||||
json!(T::make_schema())
|
json!(gen.subschema_for::<T>())
|
||||||
);
|
);
|
||||||
SchemaObject {
|
SchemaObject {
|
||||||
instance_type: Some(InstanceType::Object.into()),
|
instance_type: Some(InstanceType::Object.into()),
|
||||||
|
@ -190,13 +176,13 @@ map_impl!(<K, T: Eq + core::hash::Hash, H: core::hash::BuildHasher> MakeSchema f
|
||||||
|
|
||||||
////////// OPTION //////////
|
////////// OPTION //////////
|
||||||
|
|
||||||
impl<T: MakeSchema> MakeSchema for Option<T> {
|
impl<T: MakeSchema + 'static> MakeSchema for Option<T> {
|
||||||
fn make_schema() -> Schema {
|
fn make_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||||
match T::make_schema() {
|
match gen.subschema_for::<T>() {
|
||||||
Schema::Bool(true) => true.into(),
|
Schema::Bool(true) => true.into(),
|
||||||
Schema::Bool(false) => <()>::make_schema(),
|
Schema::Bool(false) => <()>::make_schema(gen),
|
||||||
Schema::Object(schema) => SchemaObject {
|
schema => SchemaObject {
|
||||||
any_of: Some(vec![schema.into(), <()>::make_schema()]),
|
any_of: Some(vec![schema, <()>::make_schema(gen)]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
|
@ -210,10 +196,10 @@ macro_rules! deref_impl {
|
||||||
($($desc:tt)+) => {
|
($($desc:tt)+) => {
|
||||||
impl $($desc)+
|
impl $($desc)+
|
||||||
where
|
where
|
||||||
T: ?Sized + MakeSchema,
|
T: MakeSchema + 'static,
|
||||||
{
|
{
|
||||||
fn make_schema() -> Schema {
|
fn make_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||||
T::make_schema()
|
gen.subschema_for::<T>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -229,7 +215,7 @@ deref_impl!(<'a, T: ToOwned> MakeSchema for std::borrow::Cow<'a, T>);
|
||||||
////////// SERDE_JSON //////////
|
////////// SERDE_JSON //////////
|
||||||
|
|
||||||
impl MakeSchema for serde_json::Value {
|
impl MakeSchema for serde_json::Value {
|
||||||
fn make_schema() -> Schema {
|
fn make_schema(_: &mut SchemaGenerator) -> Schema {
|
||||||
true.into()
|
true.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::BTreeMap as Map;
|
use std::collections::BTreeMap as Map;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Schema {
|
pub enum Schema {
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
|
Ref(SchemaRef),
|
||||||
Object(SchemaObject),
|
Object(SchemaObject),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +22,19 @@ impl From<bool> for Schema {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
impl From<SchemaRef> for Schema {
|
||||||
|
fn from(r: SchemaRef) -> Self {
|
||||||
|
Schema::Ref(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||||
|
pub struct SchemaRef {
|
||||||
|
#[serde(rename = "$ref")]
|
||||||
|
pub reference: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SchemaObject {
|
pub struct SchemaObject {
|
||||||
#[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
|
#[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
|
||||||
|
@ -56,7 +69,7 @@ pub struct SchemaObject {
|
||||||
pub extra_properties: Map<String, Value>,
|
pub extra_properties: Map<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum InstanceType {
|
pub enum InstanceType {
|
||||||
Null,
|
Null,
|
||||||
|
@ -68,7 +81,7 @@ pub enum InstanceType {
|
||||||
Integer,
|
Integer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum SingleOrVec<T> {
|
pub enum SingleOrVec<T> {
|
||||||
Single(T),
|
Single(T),
|
||||||
|
@ -98,15 +111,3 @@ impl<T> Into<Vec<T>> for SingleOrVec<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*pub struct SchemaObject {
|
|
||||||
pub ref_path: Option<String>,
|
|
||||||
pub description: Option<String>,
|
|
||||||
pub schema_type: Option<String>,
|
|
||||||
pub format: Option<String>,
|
|
||||||
pub enum_values: Option<Vec<String>>,
|
|
||||||
pub required: Option<Vec<String>>,
|
|
||||||
pub items: Option<Box<SchemaObject>>,
|
|
||||||
pub properties: Option<std::collections::BTreeMap<String, SchemaObject>>,
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue