Generate schema from any serializable value (#75)
Implement schema_for_value!(...) macro
This commit is contained in:
parent
0957204bc1
commit
f6482fd460
19 changed files with 1179 additions and 5 deletions
62
README.md
62
README.md
|
@ -200,6 +200,68 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap());
|
||||||
|
|
||||||
`#[serde(...)]` attributes can be overriden using `#[schemars(...)]` attributes, which behave identically (e.g. `#[schemars(rename_all = "camelCase")]`). You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde.
|
`#[serde(...)]` attributes can be overriden using `#[schemars(...)]` attributes, which behave identically (e.g. `#[schemars(rename_all = "camelCase")]`). You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde.
|
||||||
|
|
||||||
|
### Schema from Example Values
|
||||||
|
|
||||||
|
If you want a schema for a type that can't/doesn't implement `JsonSchema`, but does implement `serde::Serialize`, then you can generate a JSON schema from a value of that type. However, this schema will generally be less precise than if the type implemented `JsonSchema` - particularly when it involves enums, since schemars will not make any assumptions about the structure of an enum based on a single variant.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use schemars::schema_for_value;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct MyStruct {
|
||||||
|
pub my_int: i32,
|
||||||
|
pub my_bool: bool,
|
||||||
|
pub my_nullable_enum: Option<MyEnum>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub enum MyEnum {
|
||||||
|
StringNewType(String),
|
||||||
|
StructVariant { floats: Vec<f32> },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let schema = schema_for_value!(MyStruct {
|
||||||
|
my_int: 123,
|
||||||
|
my_bool: true,
|
||||||
|
my_nullable_enum: Some(MyEnum::StringNewType("foo".to_string()))
|
||||||
|
});
|
||||||
|
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click to see the output JSON schema...</summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "MyStruct",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"my_bool": true,
|
||||||
|
"my_int": 123,
|
||||||
|
"my_nullable_enum": {
|
||||||
|
"StringNewType": "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"my_bool": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"my_int": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"my_nullable_enum": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
## Feature Flags
|
## Feature Flags
|
||||||
- `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro
|
- `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro
|
||||||
- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves
|
- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves
|
||||||
|
|
|
@ -22,4 +22,6 @@ See the API documentation for more info on how to use those types for custom sch
|
||||||
|
|
||||||
<!-- TODO:
|
<!-- TODO:
|
||||||
create and link to example
|
create and link to example
|
||||||
|
|
||||||
|
Generating schema from example value
|
||||||
-->
|
-->
|
||||||
|
|
24
docs/_includes/examples/from_value.rs
Normal file
24
docs/_includes/examples/from_value.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use schemars::schema_for_value;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct MyStruct {
|
||||||
|
pub my_int: i32,
|
||||||
|
pub my_bool: bool,
|
||||||
|
pub my_nullable_enum: Option<MyEnum>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub enum MyEnum {
|
||||||
|
StringNewType(String),
|
||||||
|
StructVariant { floats: Vec<f32> },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let schema = schema_for_value!(MyStruct {
|
||||||
|
my_int: 123,
|
||||||
|
my_bool: true,
|
||||||
|
my_nullable_enum: Some(MyEnum::StringNewType("foo".to_string()))
|
||||||
|
});
|
||||||
|
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
|
||||||
|
}
|
24
docs/_includes/examples/from_value.schema.json
Normal file
24
docs/_includes/examples/from_value.schema.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "MyStruct",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"my_bool": true,
|
||||||
|
"my_int": 123,
|
||||||
|
"my_nullable_enum": {
|
||||||
|
"StringNewType": "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"my_bool": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"my_int": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"my_nullable_enum": true
|
||||||
|
}
|
||||||
|
}
|
24
schemars/examples/from_value.rs
Normal file
24
schemars/examples/from_value.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use schemars::schema_for_value;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct MyStruct {
|
||||||
|
pub my_int: i32,
|
||||||
|
pub my_bool: bool,
|
||||||
|
pub my_nullable_enum: Option<MyEnum>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub enum MyEnum {
|
||||||
|
StringNewType(String),
|
||||||
|
StructVariant { floats: Vec<f32> },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let schema = schema_for_value!(MyStruct {
|
||||||
|
my_int: 123,
|
||||||
|
my_bool: true,
|
||||||
|
my_nullable_enum: Some(MyEnum::StringNewType("foo".to_string()))
|
||||||
|
});
|
||||||
|
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
|
||||||
|
}
|
24
schemars/examples/from_value.schema.json
Normal file
24
schemars/examples/from_value.schema.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "MyStruct",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"my_bool": true,
|
||||||
|
"my_int": 123,
|
||||||
|
"my_nullable_enum": {
|
||||||
|
"StringNewType": "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"my_bool": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"my_int": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"my_nullable_enum": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,9 @@ use crate::schema::*;
|
||||||
use crate::{Map, Set};
|
use crate::{Map, Set};
|
||||||
|
|
||||||
impl Schema {
|
impl Schema {
|
||||||
|
/// This function is only public for use by schemars_derive.
|
||||||
|
///
|
||||||
|
/// It should not be considered part of the public API.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn flatten(self, other: Self) -> Schema {
|
pub fn flatten(self, other: Self) -> Schema {
|
||||||
if is_null_type(&self) {
|
if is_null_type(&self) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*!
|
/*!
|
||||||
JSON Schema generator and settings.
|
JSON Schema generator and settings.
|
||||||
|
|
||||||
This module is useful if you want more control over how the schema generated then the [`schema_for!`] macro gives you.
|
This module is useful if you want more control over how the schema generated than the [`schema_for!`] macro gives you.
|
||||||
There are two main types in this module:two main types in this module:
|
There are two main types in this module:two main types in this module:
|
||||||
* [`SchemaSettings`], which defines what JSON Schema features should be used when generating schemas (for example, how `Option`s should be represented).
|
* [`SchemaSettings`], which defines what JSON Schema features should be used when generating schemas (for example, how `Option`s should be represented).
|
||||||
* [`SchemaGenerator`], which manages the generation of a schema document.
|
* [`SchemaGenerator`], which manages the generation of a schema document.
|
||||||
|
@ -11,6 +11,7 @@ use crate::flatten::Merge;
|
||||||
use crate::schema::*;
|
use crate::schema::*;
|
||||||
use crate::{visit::*, JsonSchema, Map};
|
use crate::{visit::*, JsonSchema, Map};
|
||||||
use dyn_clone::DynClone;
|
use dyn_clone::DynClone;
|
||||||
|
use serde::Serialize;
|
||||||
use std::{any::Any, collections::HashSet, fmt::Debug};
|
use std::{any::Any, collections::HashSet, fmt::Debug};
|
||||||
|
|
||||||
/// Settings to customize how Schemas are generated.
|
/// Settings to customize how Schemas are generated.
|
||||||
|
@ -314,6 +315,70 @@ impl SchemaGenerator {
|
||||||
root
|
root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates a root JSON Schema for the given example value.
|
||||||
|
///
|
||||||
|
/// If the value implements [`JsonSchema`](crate::JsonSchema), then prefer using the [`root_schema_for()`](Self::root_schema_for())
|
||||||
|
/// function which will generally produce a more precise schema, particularly when the value contains any enums.
|
||||||
|
pub fn root_schema_for_value<T: ?Sized + Serialize>(
|
||||||
|
&mut self,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<RootSchema, serde_json::Error> {
|
||||||
|
let mut schema = value
|
||||||
|
.serialize(crate::ser::Serializer {
|
||||||
|
gen: self,
|
||||||
|
include_title: true,
|
||||||
|
})?
|
||||||
|
.into_object();
|
||||||
|
|
||||||
|
if let Ok(example) = serde_json::to_value(value) {
|
||||||
|
schema.metadata().examples.push(example);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut root = RootSchema {
|
||||||
|
meta_schema: self.settings.meta_schema.clone(),
|
||||||
|
definitions: self.definitions.clone(),
|
||||||
|
schema,
|
||||||
|
};
|
||||||
|
|
||||||
|
for visitor in &mut self.settings.visitors {
|
||||||
|
visitor.visit_root_schema(&mut root)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes `self` and generates a root JSON Schema for the given example value.
|
||||||
|
///
|
||||||
|
/// If the value implements [`JsonSchema`](crate::JsonSchema), then prefer using the [`into_root_schema_for()!`](Self::into_root_schema_for())
|
||||||
|
/// function which will generally produce a more precise schema, particularly when the value contains any enums.
|
||||||
|
pub fn into_root_schema_for_value<T: ?Sized + Serialize>(
|
||||||
|
mut self,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<RootSchema, serde_json::Error> {
|
||||||
|
let mut schema = value
|
||||||
|
.serialize(crate::ser::Serializer {
|
||||||
|
gen: &mut self,
|
||||||
|
include_title: true,
|
||||||
|
})?
|
||||||
|
.into_object();
|
||||||
|
|
||||||
|
if let Ok(example) = serde_json::to_value(value) {
|
||||||
|
schema.metadata().examples.push(example);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut root = RootSchema {
|
||||||
|
meta_schema: self.settings.meta_schema,
|
||||||
|
definitions: self.definitions,
|
||||||
|
schema,
|
||||||
|
};
|
||||||
|
|
||||||
|
for visitor in &mut self.settings.visitors {
|
||||||
|
visitor.visit_root_schema(&mut root)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(root)
|
||||||
|
}
|
||||||
|
|
||||||
/// Attemps to find the schema that the given `schema` is referencing.
|
/// Attemps to find the schema that the given `schema` is referencing.
|
||||||
///
|
///
|
||||||
/// If the given `schema` has a [`$ref`](../schema/struct.SchemaObject.html#structfield.reference) property which refers
|
/// If the given `schema` has a [`$ref`](../schema/struct.SchemaObject.html#structfield.reference) property which refers
|
||||||
|
|
|
@ -25,6 +25,7 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
|
||||||
schema
|
schema
|
||||||
}
|
}
|
||||||
schema => SchemaObject {
|
schema => SchemaObject {
|
||||||
|
// TODO technically the schema already accepts null, so this may be unnecessary
|
||||||
subschemas: Some(Box::new(SubschemaValidation {
|
subschemas: Some(Box::new(SubschemaValidation {
|
||||||
any_of: Some(vec![schema, <()>::json_schema(gen)]),
|
any_of: Some(vec![schema, <()>::json_schema(gen)]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -195,9 +195,71 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap());
|
||||||
|
|
||||||
`#[serde(...)]` attributes can be overriden using `#[schemars(...)]` attributes, which behave identically (e.g. `#[schemars(rename_all = "camelCase")]`). You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde.
|
`#[serde(...)]` attributes can be overriden using `#[schemars(...)]` attributes, which behave identically (e.g. `#[schemars(rename_all = "camelCase")]`). You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde.
|
||||||
|
|
||||||
|
### Schema from Example Values
|
||||||
|
|
||||||
|
If you want a schema for a type that can't/doesn't implement `JsonSchema`, but does implement `serde::Serialize`, then you can generate a JSON schema from a value of that type. However, this schema will generally be less precise than if the type implemented `JsonSchema` - particularly when it involves enums, since schemars will not make any assumptions about the structure of an enum based on a single variant.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use schemars::schema_for_value;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct MyStruct {
|
||||||
|
pub my_int: i32,
|
||||||
|
pub my_bool: bool,
|
||||||
|
pub my_nullable_enum: Option<MyEnum>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub enum MyEnum {
|
||||||
|
StringNewType(String),
|
||||||
|
StructVariant { floats: Vec<f32> },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let schema = schema_for_value!(MyStruct {
|
||||||
|
my_int: 123,
|
||||||
|
my_bool: true,
|
||||||
|
my_nullable_enum: Some(MyEnum::StringNewType("foo".to_string()))
|
||||||
|
});
|
||||||
|
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click to see the output JSON schema...</summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "MyStruct",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"my_bool": true,
|
||||||
|
"my_int": 123,
|
||||||
|
"my_nullable_enum": {
|
||||||
|
"StringNewType": "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"my_bool": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"my_int": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"my_nullable_enum": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
## Feature Flags
|
## Feature Flags
|
||||||
- `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro.
|
- `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro
|
||||||
- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves.
|
- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves
|
||||||
- `preserve_order` - keep the order of struct fields in `Schema` and `SchemaObject`
|
- `preserve_order` - keep the order of struct fields in `Schema` and `SchemaObject`
|
||||||
|
|
||||||
## Optional Dependencies
|
## Optional Dependencies
|
||||||
|
@ -236,6 +298,7 @@ pub type MapEntry<'a, K, V> = indexmap::map::Entry<'a, K, V>;
|
||||||
|
|
||||||
mod flatten;
|
mod flatten;
|
||||||
mod json_schema_impls;
|
mod json_schema_impls;
|
||||||
|
mod ser;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,66 @@
|
||||||
///
|
///
|
||||||
/// let my_schema = schema_for!(MyStruct);
|
/// let my_schema = schema_for!(MyStruct);
|
||||||
/// ```
|
/// ```
|
||||||
|
#[cfg(doc)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! schema_for {
|
macro_rules! schema_for {
|
||||||
($type:ty) => {
|
($type:ty) => {
|
||||||
$crate::gen::SchemaGenerator::default().into_root_schema_for::<$type>()
|
$crate::gen::SchemaGenerator::default().into_root_schema_for::<$type>()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates a [`RootSchema`](crate::schema::RootSchema) for the given type using default settings.
|
||||||
|
///
|
||||||
|
/// The type must implement [`JsonSchema`](crate::JsonSchema).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use schemars::{schema_for, JsonSchema};
|
||||||
|
///
|
||||||
|
/// #[derive(JsonSchema)]
|
||||||
|
/// struct MyStruct {
|
||||||
|
/// foo: i32,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let my_schema = schema_for!(MyStruct);
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(doc))]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! schema_for {
|
||||||
|
($type:ty) => {
|
||||||
|
$crate::gen::SchemaGenerator::default().into_root_schema_for::<$type>()
|
||||||
|
};
|
||||||
|
($_:expr) => {
|
||||||
|
compile_error!("This argument to `schema_for!` is not a type - did you mean to use `schema_for_value!` instead?")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a [`RootSchema`](crate::schema::RootSchema) for the given example value using default settings.
|
||||||
|
///
|
||||||
|
/// The value must implement [`Serialize`](serde::Serialize). If the value also implements [`JsonSchema`](crate::JsonSchema),
|
||||||
|
/// then prefer using the [`schema_for!`](schema_for) macro which will generally produce a more precise schema,
|
||||||
|
/// particularly when the value contains any enums.
|
||||||
|
///
|
||||||
|
/// If the `Serialize` implementation of the value decides to fail, this macro will panic.
|
||||||
|
/// For a non-panicking alternative, create a [`SchemaGenerator`](crate::gen::SchemaGenerator) and use
|
||||||
|
/// its [`into_root_schema_for_value`](crate::gen::SchemaGenerator::into_root_schema_for_value) method.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use schemars::schema_for_value;
|
||||||
|
///
|
||||||
|
/// #[derive(serde::Serialize)]
|
||||||
|
/// struct MyStruct {
|
||||||
|
/// foo: i32,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let my_schema = schema_for_value!(MyStruct { foo: 123 });
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! schema_for_value {
|
||||||
|
($value:expr) => {
|
||||||
|
$crate::gen::SchemaGenerator::default()
|
||||||
|
.into_root_schema_for_value(&$value)
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
496
schemars/src/ser.rs
Normal file
496
schemars/src/ser.rs
Normal file
|
@ -0,0 +1,496 @@
|
||||||
|
use crate::schema::*;
|
||||||
|
use crate::JsonSchema;
|
||||||
|
use crate::{gen::SchemaGenerator, Map};
|
||||||
|
use serde_json::{Error, Value};
|
||||||
|
use std::{convert::TryInto, fmt::Display};
|
||||||
|
|
||||||
|
pub(crate) struct Serializer<'a> {
|
||||||
|
pub(crate) gen: &'a mut SchemaGenerator,
|
||||||
|
pub(crate) include_title: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct SerializeSeq<'a> {
|
||||||
|
gen: &'a mut SchemaGenerator,
|
||||||
|
items: Option<Schema>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct SerializeTuple<'a> {
|
||||||
|
gen: &'a mut SchemaGenerator,
|
||||||
|
items: Vec<Schema>,
|
||||||
|
title: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct SerializeMap<'a> {
|
||||||
|
gen: &'a mut SchemaGenerator,
|
||||||
|
properties: Map<String, Schema>,
|
||||||
|
current_key: Option<String>,
|
||||||
|
title: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! forward_to_subschema_for {
|
||||||
|
($fn:ident, $ty:ty) => {
|
||||||
|
fn $fn(self, _value: $ty) -> Result<Self::Ok, Self::Error> {
|
||||||
|
Ok(self.gen.subschema_for::<$ty>())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> serde::Serializer for Serializer<'a> {
|
||||||
|
type Ok = Schema;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = SerializeSeq<'a>;
|
||||||
|
type SerializeTuple = SerializeTuple<'a>;
|
||||||
|
type SerializeTupleStruct = SerializeTuple<'a>;
|
||||||
|
type SerializeTupleVariant = Self;
|
||||||
|
type SerializeMap = SerializeMap<'a>;
|
||||||
|
type SerializeStruct = SerializeMap<'a>;
|
||||||
|
type SerializeStructVariant = Self;
|
||||||
|
|
||||||
|
forward_to_subschema_for!(serialize_bool, bool);
|
||||||
|
forward_to_subschema_for!(serialize_i8, i8);
|
||||||
|
forward_to_subschema_for!(serialize_i16, i16);
|
||||||
|
forward_to_subschema_for!(serialize_i32, i32);
|
||||||
|
forward_to_subschema_for!(serialize_i64, i64);
|
||||||
|
forward_to_subschema_for!(serialize_i128, i128);
|
||||||
|
forward_to_subschema_for!(serialize_u8, u8);
|
||||||
|
forward_to_subschema_for!(serialize_u16, u16);
|
||||||
|
forward_to_subschema_for!(serialize_u32, u32);
|
||||||
|
forward_to_subschema_for!(serialize_u64, u64);
|
||||||
|
forward_to_subschema_for!(serialize_u128, u128);
|
||||||
|
forward_to_subschema_for!(serialize_f32, f32);
|
||||||
|
forward_to_subschema_for!(serialize_f64, f64);
|
||||||
|
forward_to_subschema_for!(serialize_char, char);
|
||||||
|
forward_to_subschema_for!(serialize_str, &str);
|
||||||
|
forward_to_subschema_for!(serialize_bytes, &[u8]);
|
||||||
|
|
||||||
|
fn collect_str<T: ?Sized>(self, _value: &T) -> Result<Self::Ok, Self::Error>
|
||||||
|
where
|
||||||
|
T: Display,
|
||||||
|
{
|
||||||
|
Ok(self.gen.subschema_for::<String>())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_map<K, V, I>(self, iter: I) -> Result<Self::Ok, Self::Error>
|
||||||
|
where
|
||||||
|
K: serde::Serialize,
|
||||||
|
V: serde::Serialize,
|
||||||
|
I: IntoIterator<Item = (K, V)>,
|
||||||
|
{
|
||||||
|
let value_schema = iter
|
||||||
|
.into_iter()
|
||||||
|
.try_fold(None, |acc, (_, v)| {
|
||||||
|
if acc == Some(Schema::Bool(true)) {
|
||||||
|
return Ok(acc);
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = v.serialize(Serializer {
|
||||||
|
gen: self.gen,
|
||||||
|
include_title: false,
|
||||||
|
})?;
|
||||||
|
Ok(match &acc {
|
||||||
|
None => Some(schema),
|
||||||
|
Some(items) if items != &schema => Some(Schema::Bool(true)),
|
||||||
|
_ => acc,
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.unwrap_or(Schema::Bool(true));
|
||||||
|
|
||||||
|
Ok(SchemaObject {
|
||||||
|
instance_type: Some(InstanceType::Object.into()),
|
||||||
|
object: Some(Box::new(ObjectValidation {
|
||||||
|
additional_properties: Some(Box::new(value_schema)),
|
||||||
|
..ObjectValidation::default()
|
||||||
|
})),
|
||||||
|
..SchemaObject::default()
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
Ok(self.gen.subschema_for::<Option<Value>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_some<T: ?Sized>(mut self, value: &T) -> Result<Self::Ok, Self::Error>
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
// FIXME nasty duplication of `impl JsonSchema for Option<T>`
|
||||||
|
fn add_null_type(instance_type: &mut SingleOrVec<InstanceType>) {
|
||||||
|
match instance_type {
|
||||||
|
SingleOrVec::Single(ty) if **ty != InstanceType::Null => {
|
||||||
|
*instance_type = vec![**ty, InstanceType::Null].into()
|
||||||
|
}
|
||||||
|
SingleOrVec::Vec(ty) if !ty.contains(&InstanceType::Null) => {
|
||||||
|
ty.push(InstanceType::Null)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut schema = value.serialize(Serializer {
|
||||||
|
gen: self.gen,
|
||||||
|
include_title: false,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if self.gen.settings().option_add_null_type {
|
||||||
|
schema = match schema {
|
||||||
|
Schema::Bool(true) => Schema::Bool(true),
|
||||||
|
Schema::Bool(false) => <()>::json_schema(&mut self.gen),
|
||||||
|
Schema::Object(SchemaObject {
|
||||||
|
instance_type: Some(ref mut instance_type),
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
add_null_type(instance_type);
|
||||||
|
schema
|
||||||
|
}
|
||||||
|
schema => SchemaObject {
|
||||||
|
subschemas: Some(Box::new(SubschemaValidation {
|
||||||
|
any_of: Some(vec![schema, <()>::json_schema(&mut self.gen)]),
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.gen.settings().option_nullable {
|
||||||
|
let mut schema_obj = schema.into_object();
|
||||||
|
schema_obj
|
||||||
|
.extensions
|
||||||
|
.insert("nullable".to_owned(), serde_json::json!(true));
|
||||||
|
schema = Schema::Object(schema_obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
Ok(self.gen.subschema_for::<()>())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> {
|
||||||
|
self.serialize_unit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_unit_variant(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_variant_index: u32,
|
||||||
|
_variant: &'static str,
|
||||||
|
) -> Result<Self::Ok, Self::Error> {
|
||||||
|
Ok(Schema::Bool(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_newtype_struct<T: ?Sized>(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<Self::Ok, Self::Error>
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
let include_title = self.include_title;
|
||||||
|
let mut result = value.serialize(self);
|
||||||
|
|
||||||
|
if include_title {
|
||||||
|
if let Ok(Schema::Object(ref mut object)) = result {
|
||||||
|
object.metadata().title = Some(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_newtype_variant<T: ?Sized>(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_variant_index: u32,
|
||||||
|
_variant: &'static str,
|
||||||
|
_value: &T,
|
||||||
|
) -> Result<Self::Ok, Self::Error>
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
Ok(Schema::Bool(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
|
||||||
|
Ok(SerializeSeq {
|
||||||
|
gen: self.gen,
|
||||||
|
items: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
|
||||||
|
Ok(SerializeTuple {
|
||||||
|
gen: self.gen,
|
||||||
|
items: Vec::with_capacity(len),
|
||||||
|
title: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tuple_struct(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<Self::SerializeTupleStruct, Self::Error> {
|
||||||
|
let title = if self.include_title { name } else { "" };
|
||||||
|
Ok(SerializeTuple {
|
||||||
|
gen: self.gen,
|
||||||
|
items: Vec::with_capacity(len),
|
||||||
|
title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tuple_variant(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_variant_index: u32,
|
||||||
|
_variant: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<Self::SerializeTupleVariant, Self::Error> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
|
||||||
|
Ok(SerializeMap {
|
||||||
|
gen: self.gen,
|
||||||
|
properties: Map::new(),
|
||||||
|
current_key: None,
|
||||||
|
title: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_struct(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<Self::SerializeStruct, Self::Error> {
|
||||||
|
let title = if self.include_title { name } else { "" };
|
||||||
|
Ok(SerializeMap {
|
||||||
|
gen: self.gen,
|
||||||
|
properties: Map::new(),
|
||||||
|
current_key: None,
|
||||||
|
title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_struct_variant(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_variant_index: u32,
|
||||||
|
_variant: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<Self::SerializeStructVariant, Self::Error> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeTupleVariant for Serializer<'_> {
|
||||||
|
type Ok = Schema;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<T: ?Sized>(&mut self, _value: &T) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
Ok(Schema::Bool(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeStructVariant for Serializer<'_> {
|
||||||
|
type Ok = Schema;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<T: ?Sized>(
|
||||||
|
&mut self,
|
||||||
|
_key: &'static str,
|
||||||
|
_value: &T,
|
||||||
|
) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
Ok(Schema::Bool(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeSeq for SerializeSeq<'_> {
|
||||||
|
type Ok = Schema;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
if self.items != Some(Schema::Bool(true)) {
|
||||||
|
let schema = value.serialize(Serializer {
|
||||||
|
gen: self.gen,
|
||||||
|
include_title: false,
|
||||||
|
})?;
|
||||||
|
match &self.items {
|
||||||
|
None => self.items = Some(schema),
|
||||||
|
Some(items) => {
|
||||||
|
if items != &schema {
|
||||||
|
self.items = Some(Schema::Bool(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
let items = self.items.unwrap_or(Schema::Bool(true));
|
||||||
|
Ok(SchemaObject {
|
||||||
|
instance_type: Some(InstanceType::Array.into()),
|
||||||
|
array: Some(Box::new(ArrayValidation {
|
||||||
|
items: Some(items.into()),
|
||||||
|
..ArrayValidation::default()
|
||||||
|
})),
|
||||||
|
..SchemaObject::default()
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeTuple for SerializeTuple<'_> {
|
||||||
|
type Ok = Schema;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
let schema = value.serialize(Serializer {
|
||||||
|
gen: self.gen,
|
||||||
|
include_title: false,
|
||||||
|
})?;
|
||||||
|
self.items.push(schema);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
let len = self.items.len().try_into().ok();
|
||||||
|
let mut schema = SchemaObject {
|
||||||
|
instance_type: Some(InstanceType::Array.into()),
|
||||||
|
array: Some(Box::new(ArrayValidation {
|
||||||
|
items: Some(SingleOrVec::Vec(self.items)),
|
||||||
|
max_items: len,
|
||||||
|
min_items: len,
|
||||||
|
..ArrayValidation::default()
|
||||||
|
})),
|
||||||
|
..SchemaObject::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
if !self.title.is_empty() {
|
||||||
|
schema.metadata().title = Some(self.title.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(schema.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeTupleStruct for SerializeTuple<'_> {
|
||||||
|
type Ok = Schema;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
serde::ser::SerializeTuple::serialize_element(self, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
serde::ser::SerializeTuple::end(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeMap for SerializeMap<'_> {
|
||||||
|
type Ok = Schema;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
// FIXME this is too lenient - we should return an error if serde_json
|
||||||
|
// doesn't allow T to be a key of a map.
|
||||||
|
let json = serde_json::to_string(key)?;
|
||||||
|
self.current_key = Some(
|
||||||
|
json.trim_start_matches('"')
|
||||||
|
.trim_end_matches('"')
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
let key = self.current_key.take().unwrap_or_default();
|
||||||
|
let schema = value.serialize(Serializer {
|
||||||
|
gen: self.gen,
|
||||||
|
include_title: false,
|
||||||
|
})?;
|
||||||
|
self.properties.insert(key, schema);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
let mut schema = SchemaObject {
|
||||||
|
instance_type: Some(InstanceType::Object.into()),
|
||||||
|
object: Some(Box::new(ObjectValidation {
|
||||||
|
properties: self.properties,
|
||||||
|
..ObjectValidation::default()
|
||||||
|
})),
|
||||||
|
..SchemaObject::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
if !self.title.is_empty() {
|
||||||
|
schema.metadata().title = Some(self.title.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(schema.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeStruct for SerializeMap<'_> {
|
||||||
|
type Ok = Schema;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<T: ?Sized>(
|
||||||
|
&mut self,
|
||||||
|
key: &'static str,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
let prop_schema = value.serialize(Serializer {
|
||||||
|
gen: self.gen,
|
||||||
|
include_title: false,
|
||||||
|
})?;
|
||||||
|
self.properties.insert(key.to_string(), prop_schema);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
serde::ser::SerializeMap::end(self)
|
||||||
|
}
|
||||||
|
}
|
80
schemars/tests/expected/from_value_2019_09.json
Normal file
80
schemars/tests/expected/from_value_2019_09.json
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2019-09/schema",
|
||||||
|
"title": "MyStruct",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"myBool": true,
|
||||||
|
"myInnerStruct": {
|
||||||
|
"my_empty_map": {},
|
||||||
|
"my_empty_vec": [],
|
||||||
|
"my_map": {
|
||||||
|
"": 0.0
|
||||||
|
},
|
||||||
|
"my_tuple": [
|
||||||
|
"💩",
|
||||||
|
42
|
||||||
|
],
|
||||||
|
"my_vec": [
|
||||||
|
"hello",
|
||||||
|
"world"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"myInt": 123,
|
||||||
|
"myNullableEnum": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"myInt": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"myBool": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"myNullableEnum": true,
|
||||||
|
"myInnerStruct": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"my_map": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"my_vec": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"my_empty_map": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"my_empty_vec": {
|
||||||
|
"type": "array",
|
||||||
|
"items": true
|
||||||
|
},
|
||||||
|
"my_tuple": {
|
||||||
|
"type": "array",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 1,
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint8",
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
schemars/tests/expected/from_value_draft07.json
Normal file
80
schemars/tests/expected/from_value_draft07.json
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "MyStruct",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"myBool": true,
|
||||||
|
"myInnerStruct": {
|
||||||
|
"my_empty_map": {},
|
||||||
|
"my_empty_vec": [],
|
||||||
|
"my_map": {
|
||||||
|
"": 0.0
|
||||||
|
},
|
||||||
|
"my_tuple": [
|
||||||
|
"💩",
|
||||||
|
42
|
||||||
|
],
|
||||||
|
"my_vec": [
|
||||||
|
"hello",
|
||||||
|
"world"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"myInt": 123,
|
||||||
|
"myNullableEnum": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"myInt": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"myBool": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"myNullableEnum": true,
|
||||||
|
"myInnerStruct": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"my_map": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"my_vec": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"my_empty_map": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"my_empty_vec": {
|
||||||
|
"type": "array",
|
||||||
|
"items": true
|
||||||
|
},
|
||||||
|
"my_tuple": {
|
||||||
|
"type": "array",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 1,
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint8",
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
schemars/tests/expected/from_value_openapi3.json
Normal file
80
schemars/tests/expected/from_value_openapi3.json
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||||
|
"title": "MyStruct",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"myInt": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"myBool": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"myNullableEnum": {
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"myInnerStruct": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"my_map": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"my_vec": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"my_empty_map": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"my_empty_vec": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {}
|
||||||
|
},
|
||||||
|
"my_tuple": {
|
||||||
|
"type": "array",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 1,
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint8",
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"myBool": true,
|
||||||
|
"myInnerStruct": {
|
||||||
|
"my_empty_map": {},
|
||||||
|
"my_empty_vec": [],
|
||||||
|
"my_map": {
|
||||||
|
"": 0.0
|
||||||
|
},
|
||||||
|
"my_tuple": [
|
||||||
|
"💩",
|
||||||
|
42
|
||||||
|
],
|
||||||
|
"my_vec": [
|
||||||
|
"hello",
|
||||||
|
"world"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"myInt": 123,
|
||||||
|
"myNullableEnum": null
|
||||||
|
}
|
||||||
|
}
|
77
schemars/tests/from_value.rs
Normal file
77
schemars/tests/from_value.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
mod util;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use schemars::gen::SchemaSettings;
|
||||||
|
use serde::Serialize;
|
||||||
|
use util::*;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct MyStruct {
|
||||||
|
pub my_int: i32,
|
||||||
|
pub my_bool: bool,
|
||||||
|
pub my_nullable_enum: Option<MyEnum>,
|
||||||
|
pub my_inner_struct: MyInnerStruct,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub skip: i32,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub skip_if_none: Option<MyEnum>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct MyInnerStruct {
|
||||||
|
pub my_map: HashMap<String, f64>,
|
||||||
|
pub my_vec: Vec<&'static str>,
|
||||||
|
pub my_empty_map: HashMap<String, f64>,
|
||||||
|
pub my_empty_vec: Vec<&'static str>,
|
||||||
|
pub my_tuple: (char, u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub enum MyEnum {
|
||||||
|
StringNewType(String),
|
||||||
|
StructVariant { floats: Vec<f32> },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_value() -> MyStruct {
|
||||||
|
let mut value = MyStruct {
|
||||||
|
my_int: 123,
|
||||||
|
my_bool: true,
|
||||||
|
my_nullable_enum: None,
|
||||||
|
my_inner_struct: MyInnerStruct {
|
||||||
|
my_map: HashMap::new(),
|
||||||
|
my_vec: vec!["hello", "world"],
|
||||||
|
my_empty_map: HashMap::new(),
|
||||||
|
my_empty_vec: vec![],
|
||||||
|
my_tuple: ('💩', 42),
|
||||||
|
},
|
||||||
|
skip: 123,
|
||||||
|
skip_if_none: None,
|
||||||
|
};
|
||||||
|
value.my_inner_struct.my_map.insert(String::new(), 0.0);
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_from_value_matches_draft07() -> TestResult {
|
||||||
|
let gen = SchemaSettings::draft07().into_generator();
|
||||||
|
let actual = gen.into_root_schema_for_value(&make_value())?;
|
||||||
|
|
||||||
|
test_schema(&actual, "from_value_draft07")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_from_value_matches_2019_09() -> TestResult {
|
||||||
|
let gen = SchemaSettings::draft2019_09().into_generator();
|
||||||
|
let actual = gen.into_root_schema_for_value(&make_value())?;
|
||||||
|
|
||||||
|
test_schema(&actual, "from_value_2019_09")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_from_value_matches_openapi3() -> TestResult {
|
||||||
|
let gen = SchemaSettings::openapi3().into_generator();
|
||||||
|
let actual = gen.into_root_schema_for_value(&make_value())?;
|
||||||
|
|
||||||
|
test_schema(&actual, "from_value_openapi3")
|
||||||
|
}
|
5
schemars/tests/ui/schema_for_arg_value.rs
Normal file
5
schemars/tests/ui/schema_for_arg_value.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
use schemars::schema_for;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let _schema = schema_for!(123);
|
||||||
|
}
|
7
schemars/tests/ui/schema_for_arg_value.stderr
Normal file
7
schemars/tests/ui/schema_for_arg_value.stderr
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
error: This argument to `schema_for!` is not a type - did you mean to use `schema_for_value!` instead?
|
||||||
|
--> $DIR/schema_for_arg_value.rs:4:19
|
||||||
|
|
|
||||||
|
4 | let _schema = schema_for!(123);
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
@ -18,7 +18,7 @@ pub fn test_default_generated_schema<T: JsonSchema>(file: &str) -> TestResult {
|
||||||
test_schema(&actual, file)
|
test_schema(&actual, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_schema(actual: &RootSchema, file: &str) -> TestResult {
|
pub fn test_schema(actual: &RootSchema, file: &str) -> TestResult {
|
||||||
let expected_json = match fs::read_to_string(format!("tests/expected/{}.json", file)) {
|
let expected_json = match fs::read_to_string(format!("tests/expected/{}.json", file)) {
|
||||||
Ok(j) => j,
|
Ok(j) => j,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue