Initial commit
This commit is contained in:
commit
3a321a901b
6 changed files with 359 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "schemars"
|
||||
version = "0.1.0"
|
||||
authors = ["Graham Esau <graham.esau@newvoicemedia.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
10
src/lib.rs
Normal file
10
src/lib.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
pub mod make_schema;
|
||||
pub mod schema;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
54
src/main.rs
Normal file
54
src/main.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
mod make_schema;
|
||||
mod schema;
|
||||
|
||||
use make_schema::MakeSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Result;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
enum TodoStatus {
|
||||
Backlog,
|
||||
InProgress,
|
||||
Done,
|
||||
Archived,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Todo {
|
||||
id: u64,
|
||||
title: String,
|
||||
description: Option<String>,
|
||||
status: TodoStatus,
|
||||
assigned_to: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct User {
|
||||
id: u64,
|
||||
username: String,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let schema = <&str>::make_schema();
|
||||
let json = serde_json::to_string(&schema)?;
|
||||
println!("{}", json);
|
||||
|
||||
/*let todo = Todo {
|
||||
id: 42,
|
||||
title: "Learn Rust".to_owned(),
|
||||
description: Option::None,
|
||||
status: TodoStatus::InProgress,
|
||||
assigned_to: vec![User {
|
||||
id: 1248,
|
||||
username: "testuser".to_owned(),
|
||||
}],
|
||||
};
|
||||
|
||||
let t = serde_json::to_string(&todo)?;
|
||||
println!("{}", t);*/
|
||||
|
||||
Ok(())
|
||||
}
|
193
src/make_schema.rs
Normal file
193
src/make_schema.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
use crate::schema::*;
|
||||
use serde_json::json;
|
||||
use std::collections::BTreeMap as Map;
|
||||
|
||||
pub trait MakeSchema {
|
||||
fn make_schema() -> Schema;
|
||||
}
|
||||
|
||||
// TODO structs, enums, tuples
|
||||
|
||||
// TODO serde json value, any other serde values?
|
||||
// https://github.com/serde-rs/serde/blob/ce75418e40a593fc5c0902cbf4a45305a4178dd7/serde/src/ser/impls.rs
|
||||
// Cell<T>, RefCell<T>, Mutex<T>, RwLock<T>, Result<R,E>?, Duration, SystemTime,
|
||||
// IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6, SocketAddrV6,
|
||||
// Path, PathBuf, OsStr, OsString, Wrapping<T>, Reverse<T>, AtomicBool, AtomixI8 etc.,
|
||||
// NonZeroU8 etc., ArcWeak, RcWeak, BTreeMap, HashMap, unit?, (!)?, Bound?, Range?, RangeInclusive?,
|
||||
// PhantomData?, CString?, CStr?, fmt::Arguments?
|
||||
// !map keys must be Into<String>!
|
||||
|
||||
////////// PRIMITIVES (except ints) //////////
|
||||
|
||||
macro_rules! simple_impl {
|
||||
($type:ident => $instance_type:expr) => {
|
||||
impl MakeSchema for $type {
|
||||
fn make_schema() -> Schema {
|
||||
Schema {
|
||||
instance_type: Some($instance_type.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
simple_impl!(str => InstanceType::String);
|
||||
simple_impl!(String => InstanceType::String);
|
||||
simple_impl!(bool => InstanceType::Boolean);
|
||||
simple_impl!(f32 => InstanceType::Number);
|
||||
simple_impl!(f64 => InstanceType::Number);
|
||||
|
||||
impl MakeSchema for char {
|
||||
fn make_schema() -> Schema {
|
||||
let mut extra_properties = Map::new();
|
||||
extra_properties.insert("minLength".to_owned(), json!(1));
|
||||
extra_properties.insert("maxLength".to_owned(), json!(1));
|
||||
Schema {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
extra_properties,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////// 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()));
|
||||
Schema {
|
||||
instance_type: Some(InstanceType::Integer.into()),
|
||||
extra_properties,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 //////////
|
||||
|
||||
// Does not require T: MakeSchema.
|
||||
impl<T> MakeSchema for [T; 0] {
|
||||
fn make_schema() -> Schema {
|
||||
let mut extra_properties = Map::new();
|
||||
extra_properties.insert("maxItems".to_owned(), json!(0));
|
||||
Schema {
|
||||
instance_type: Some(InstanceType::Array.into()),
|
||||
extra_properties,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! array_impls {
|
||||
($($len:tt)+) => {
|
||||
$(
|
||||
impl<T: MakeSchema> MakeSchema for [T; $len]
|
||||
{
|
||||
fn make_schema() -> Schema {
|
||||
let mut extra_properties = Map::new();
|
||||
extra_properties.insert("minItems".to_owned(), json!($len));
|
||||
extra_properties.insert("maxItems".to_owned(), json!($len));
|
||||
Schema {
|
||||
instance_type: Some(InstanceType::Array.into()),
|
||||
items: Some(Box::from(T::make_schema())),
|
||||
extra_properties,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
array_impls! {
|
||||
01 02 03 04 05 06 07 08 09 10
|
||||
11 12 13 14 15 16 17 18 19 20
|
||||
21 22 23 24 25 26 27 28 29 30
|
||||
31 32
|
||||
}
|
||||
|
||||
////////// SEQUENCES /////////
|
||||
|
||||
macro_rules! seq_impl {
|
||||
($($desc:tt)+) => {
|
||||
impl $($desc)+
|
||||
where
|
||||
T: MakeSchema,
|
||||
{
|
||||
fn make_schema() -> Schema
|
||||
{
|
||||
Schema {
|
||||
instance_type: Some(InstanceType::Array.into()),
|
||||
items: Some(Box::from(T::make_schema())),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
seq_impl!(<T: Ord> MakeSchema for std::collections::BinaryHeap<T>);
|
||||
seq_impl!(<T: Ord> MakeSchema for std::collections::BTreeSet<T>);
|
||||
seq_impl!(<T: Eq + core::hash::Hash, H: core::hash::BuildHasher> MakeSchema for std::collections::HashSet<T, H>);
|
||||
seq_impl!(<T> MakeSchema for std::collections::LinkedList<T>);
|
||||
seq_impl!(<T> MakeSchema for Vec<T>);
|
||||
seq_impl!(<T> MakeSchema for std::collections::VecDeque<T>);
|
||||
|
||||
////////// OPTION //////////
|
||||
|
||||
impl<T: MakeSchema> MakeSchema for Option<T> {
|
||||
fn make_schema() -> Schema {
|
||||
let mut schema = T::make_schema();
|
||||
if let Some(instance_type) = schema.instance_type {
|
||||
let mut vec: Vec<_> = instance_type.into();
|
||||
if !vec.contains(&InstanceType::Null) {
|
||||
vec.push(InstanceType::Null);
|
||||
}
|
||||
schema.instance_type = Some(vec.into());
|
||||
}
|
||||
schema
|
||||
}
|
||||
}
|
||||
|
||||
////////// DEREF //////////
|
||||
|
||||
macro_rules! deref_impl {
|
||||
($($desc:tt)+) => {
|
||||
impl $($desc)+
|
||||
where
|
||||
T: ?Sized + MakeSchema,
|
||||
{
|
||||
fn make_schema() -> Schema {
|
||||
T::make_schema()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
deref_impl!(<'a, T> MakeSchema for &'a T);
|
||||
deref_impl!(<'a, T> MakeSchema for &'a mut T);
|
||||
deref_impl!(<T> MakeSchema for Box<T>);
|
||||
deref_impl!(<T> MakeSchema for std::rc::Rc<T>);
|
||||
deref_impl!(<T> MakeSchema for std::sync::Arc<T>);
|
||||
deref_impl!(<'a, T: ToOwned> MakeSchema for std::borrow::Cow<'a, T>);
|
88
src/schema.rs
Normal file
88
src/schema.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap as Map;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RootSchema {
|
||||
#[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
|
||||
pub schema: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub root: Schema,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Schema {
|
||||
#[serde(rename = "$id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
|
||||
pub instance_type: Option<SingleOrVec<InstanceType>>,
|
||||
#[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
|
||||
pub instance_enum: Option<Vec<Value>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub items: Option<Box<Schema>>,
|
||||
#[serde(skip_serializing_if = "Map::is_empty")]
|
||||
pub definitions: Map<String, Schema>,
|
||||
#[serde(flatten)]
|
||||
pub extra_properties: Map<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum InstanceType {
|
||||
Null,
|
||||
Boolean,
|
||||
Object,
|
||||
Array,
|
||||
Number,
|
||||
String,
|
||||
Integer,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum SingleOrVec<T> {
|
||||
Single(T),
|
||||
Vec(Vec<T>),
|
||||
}
|
||||
|
||||
impl <T> From<T> for SingleOrVec<T> {
|
||||
fn from(single: T) -> Self {
|
||||
SingleOrVec::Single(single)
|
||||
}
|
||||
}
|
||||
|
||||
impl <T> From<Vec<T>> for SingleOrVec<T> {
|
||||
fn from(mut vec: Vec<T>) -> Self {
|
||||
match vec.len() {
|
||||
1 => SingleOrVec::Single(vec.remove(0)),
|
||||
_ => SingleOrVec::Vec(vec),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl <T> Into<Vec<T>> for SingleOrVec<T> {
|
||||
fn into(self) -> Vec<T> {
|
||||
match self {
|
||||
SingleOrVec::Single(s) => vec![s],
|
||||
SingleOrVec::Vec(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*pub struct Schema {
|
||||
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<Schema>>,
|
||||
pub properties: Option<std::collections::BTreeMap<String, Schema>>,
|
||||
}
|
||||
*/
|
Loading…
Add table
Add a link
Reference in a new issue