Implement JsonSchema for chrono types
Requires chrono feature.
This commit is contained in:
parent
7285dde99a
commit
a236d7aee0
9 changed files with 178 additions and 28 deletions
|
@ -12,6 +12,14 @@ keywords = ["rust", "json-schema", "serde"]
|
||||||
schemars_derive = { version = "0.1.7", path = "../schemars_derive" }
|
schemars_derive = { version = "0.1.7", path = "../schemars_derive" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
chrono = { version = "0.4", default-features = false, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "0.6.1"
|
pretty_assertions = "0.6.1"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "chrono"
|
||||||
|
required-features = ["chrono"]
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
59
schemars/src/json_schema_impls/chrono.rs
Normal file
59
schemars/src/json_schema_impls/chrono.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
use crate::gen::SchemaGenerator;
|
||||||
|
use crate::schema::*;
|
||||||
|
use crate::{JsonSchema, Result};
|
||||||
|
use chrono::prelude::*;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
impl JsonSchema for Weekday {
|
||||||
|
no_ref_schema!();
|
||||||
|
|
||||||
|
fn schema_name() -> String {
|
||||||
|
"Weekday".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_schema(_: &mut SchemaGenerator) -> Result {
|
||||||
|
Ok(SchemaObject {
|
||||||
|
instance_type: Some(InstanceType::String.into()),
|
||||||
|
enum_values: Some(vec![
|
||||||
|
json!("Mon"),
|
||||||
|
json!("Tue"),
|
||||||
|
json!("Wed"),
|
||||||
|
json!("Thu"),
|
||||||
|
json!("Fri"),
|
||||||
|
json!("Sat"),
|
||||||
|
json!("Sun"),
|
||||||
|
]),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! formatted_string_impl {
|
||||||
|
($ty:ident, $format:literal) => {
|
||||||
|
formatted_string_impl!($ty, $format, JsonSchema for $ty);
|
||||||
|
};
|
||||||
|
($ty:ident, $format:literal, $($desc:tt)+) => {
|
||||||
|
impl $($desc)+ {
|
||||||
|
no_ref_schema!();
|
||||||
|
|
||||||
|
fn schema_name() -> String {
|
||||||
|
stringify!($ty).to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_schema(_: &mut SchemaGenerator) -> Result {
|
||||||
|
Ok(SchemaObject {
|
||||||
|
instance_type: Some(InstanceType::String.into()),
|
||||||
|
format: Some($format.to_owned()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted_string_impl!(NaiveDate, "date");
|
||||||
|
formatted_string_impl!(NaiveDateTime, "partial-date-time");
|
||||||
|
formatted_string_impl!(NaiveTime, "partial-date-time");
|
||||||
|
formatted_string_impl!(DateTime, "date-time", <Tz: TimeZone> JsonSchema for DateTime<Tz>);
|
|
@ -7,6 +7,8 @@ macro_rules! no_ref_schema {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod array;
|
mod array;
|
||||||
|
#[cfg(feature = "chrono")]
|
||||||
|
mod chrono;
|
||||||
mod core;
|
mod core;
|
||||||
mod deref;
|
mod deref;
|
||||||
mod maps;
|
mod maps;
|
||||||
|
|
|
@ -50,6 +50,7 @@ impl Schema {
|
||||||
extensions: extend(s1.extensions, s2.extensions),
|
extensions: extend(s1.extensions, s2.extensions),
|
||||||
// TODO do the following make sense?
|
// TODO do the following make sense?
|
||||||
instance_type: s1.instance_type.or(s2.instance_type),
|
instance_type: s1.instance_type.or(s2.instance_type),
|
||||||
|
format: s1.format.or(s2.format),
|
||||||
enum_values: s1.enum_values.or(s2.enum_values),
|
enum_values: s1.enum_values.or(s2.enum_values),
|
||||||
all_of: s1.all_of.or(s2.all_of),
|
all_of: s1.all_of.or(s2.all_of),
|
||||||
any_of: s1.any_of.or(s2.any_of),
|
any_of: s1.any_of.or(s2.any_of),
|
||||||
|
@ -105,6 +106,8 @@ pub struct SchemaObject {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
|
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
|
||||||
pub instance_type: Option<SingleOrVec<InstanceType>>,
|
pub instance_type: Option<SingleOrVec<InstanceType>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub format: Option<String>,
|
||||||
#[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
|
#[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
|
||||||
pub enum_values: Option<Vec<Value>>,
|
pub enum_values: Option<Vec<Value>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
18
schemars/tests/chrono.rs
Normal file
18
schemars/tests/chrono.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
mod util;
|
||||||
|
use chrono::prelude::*;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use util::*;
|
||||||
|
|
||||||
|
#[derive(Debug, JsonSchema)]
|
||||||
|
struct ChronoTypes {
|
||||||
|
weekday: Weekday,
|
||||||
|
date_time: DateTime<Utc>,
|
||||||
|
naive_date: NaiveDate,
|
||||||
|
naive_date_time: NaiveDateTime,
|
||||||
|
naive_time: NaiveTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chrono_types() -> TestResult {
|
||||||
|
test_default_generated_schema::<ChronoTypes>("chrono-types")
|
||||||
|
}
|
42
schemars/tests/expected/chrono-types.json
Normal file
42
schemars/tests/expected/chrono-types.json
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "ChronoTypes",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date_time": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"naive_date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date"
|
||||||
|
},
|
||||||
|
"naive_date_time": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "partial-date-time"
|
||||||
|
},
|
||||||
|
"naive_time": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "partial-date-time"
|
||||||
|
},
|
||||||
|
"weekday": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Mon",
|
||||||
|
"Tue",
|
||||||
|
"Wed",
|
||||||
|
"Thu",
|
||||||
|
"Fri",
|
||||||
|
"Sat",
|
||||||
|
"Sun"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"date_time",
|
||||||
|
"naive_date",
|
||||||
|
"naive_date_time",
|
||||||
|
"naive_time",
|
||||||
|
"weekday"
|
||||||
|
]
|
||||||
|
}
|
|
@ -88,6 +88,10 @@
|
||||||
"items": {},
|
"items": {},
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
|
"format": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,6 +24,17 @@
|
||||||
"integer"
|
"integer"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"Ref": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"$ref": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"$ref"
|
||||||
|
]
|
||||||
|
},
|
||||||
"Schema": {
|
"Schema": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
|
@ -113,6 +124,16 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"format": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
|
@ -181,17 +202,6 @@
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
},
|
},
|
||||||
"Ref": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"$ref": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"$ref"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"SingleOrVec_For_InstanceType": {
|
"SingleOrVec_For_InstanceType": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use schemars::{gen::SchemaSettings, schema_for, JsonSchema};
|
use schemars::{gen::SchemaSettings, schema::Schema, schema_for, JsonSchema};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::panic;
|
use std::panic;
|
||||||
|
@ -8,32 +8,36 @@ pub type TestResult = Result<(), Box<dyn Error>>;
|
||||||
|
|
||||||
#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/46379
|
#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/46379
|
||||||
pub fn test_generated_schema<T: JsonSchema>(file: &str, settings: SchemaSettings) -> TestResult {
|
pub fn test_generated_schema<T: JsonSchema>(file: &str, settings: SchemaSettings) -> TestResult {
|
||||||
let expected_json = fs::read_to_string(format!("tests/expected/{}.json", file))?;
|
|
||||||
let expected = serde_json::from_str(&expected_json)?;
|
|
||||||
|
|
||||||
let actual = settings.into_generator().into_root_schema_for::<T>()?;
|
let actual = settings.into_generator().into_root_schema_for::<T>()?;
|
||||||
|
test_schema(&actual, file)
|
||||||
if actual != expected {
|
|
||||||
let actual_json = serde_json::to_string_pretty(&actual)?;
|
|
||||||
fs::write(format!("tests/actual/{}.json", file), actual_json)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/46379
|
#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/46379
|
||||||
pub fn test_default_generated_schema<T: JsonSchema>(file: &str) -> TestResult {
|
pub fn test_default_generated_schema<T: JsonSchema>(file: &str) -> TestResult {
|
||||||
let expected_json = fs::read_to_string(format!("tests/expected/{}.json", file))?;
|
|
||||||
let expected = serde_json::from_str(&expected_json)?;
|
|
||||||
|
|
||||||
let actual = schema_for!(T)?;
|
let actual = schema_for!(T)?;
|
||||||
|
test_schema(&actual, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_schema(actual: &Schema, file: &str) -> TestResult {
|
||||||
|
let expected_json = match fs::read_to_string(format!("tests/expected/{}.json", file)) {
|
||||||
|
Ok(j) => j,
|
||||||
|
Err(e) => {
|
||||||
|
write_actual_to_file(&actual, file)?;
|
||||||
|
return Err(Box::from(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let expected = &serde_json::from_str(&expected_json)?;
|
||||||
|
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
let actual_json = serde_json::to_string_pretty(&actual)?;
|
write_actual_to_file(actual, file)?;
|
||||||
fs::write(format!("tests/actual/{}.json", file), actual_json)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_actual_to_file(schema: &Schema, file: &str) -> TestResult {
|
||||||
|
let actual_json = serde_json::to_string_pretty(&schema)?;
|
||||||
|
fs::write(format!("tests/actual/{}.json", file), actual_json)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue