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" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
chrono = { version = "0.4", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
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;
|
||||
#[cfg(feature = "chrono")]
|
||||
mod chrono;
|
||||
mod core;
|
||||
mod deref;
|
||||
mod maps;
|
||||
|
|
|
@ -50,6 +50,7 @@ impl Schema {
|
|||
extensions: extend(s1.extensions, s2.extensions),
|
||||
// TODO do the following make sense?
|
||||
instance_type: s1.instance_type.or(s2.instance_type),
|
||||
format: s1.format.or(s2.format),
|
||||
enum_values: s1.enum_values.or(s2.enum_values),
|
||||
all_of: s1.all_of.or(s2.all_of),
|
||||
any_of: s1.any_of.or(s2.any_of),
|
||||
|
@ -105,6 +106,8 @@ pub struct SchemaObject {
|
|||
pub description: Option<String>,
|
||||
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
|
||||
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")]
|
||||
pub enum_values: Option<Vec<Value>>,
|
||||
#[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": {},
|
||||
"nullable": true
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
|
|
@ -24,6 +24,17 @@
|
|||
"integer"
|
||||
]
|
||||
},
|
||||
"Ref": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$ref": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"$ref"
|
||||
]
|
||||
},
|
||||
"Schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
@ -113,6 +124,16 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"format": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
@ -181,17 +202,6 @@
|
|||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"Ref": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$ref": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"$ref"
|
||||
]
|
||||
},
|
||||
"SingleOrVec_For_InstanceType": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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::fs;
|
||||
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
|
||||
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>()?;
|
||||
|
||||
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(())
|
||||
test_schema(&actual, file)
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/46379
|
||||
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)?;
|
||||
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 {
|
||||
let actual_json = serde_json::to_string_pretty(&actual)?;
|
||||
fs::write(format!("tests/actual/{}.json", file), actual_json)?;
|
||||
write_actual_to_file(actual, file)?;
|
||||
}
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
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