Generate schema from any serializable value (#75)

Implement schema_for_value!(...) macro
This commit is contained in:
Graham Esau 2021-03-25 18:32:28 +00:00 committed by GitHub
parent 0957204bc1
commit f6482fd460
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1179 additions and 5 deletions

View file

@ -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.
### 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
- `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro
- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves

View file

@ -22,4 +22,6 @@ See the API documentation for more info on how to use those types for custom sch
<!-- TODO:
create and link to example
Generating schema from example value
-->

View 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());
}

View 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
}
}

View 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());
}

View 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
}
}

View file

@ -2,6 +2,9 @@ use crate::schema::*;
use crate::{Map, Set};
impl Schema {
/// This function is only public for use by schemars_derive.
///
/// It should not be considered part of the public API.
#[doc(hidden)]
pub fn flatten(self, other: Self) -> Schema {
if is_null_type(&self) {

View file

@ -1,7 +1,7 @@
/*!
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:
* [`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.
@ -11,6 +11,7 @@ use crate::flatten::Merge;
use crate::schema::*;
use crate::{visit::*, JsonSchema, Map};
use dyn_clone::DynClone;
use serde::Serialize;
use std::{any::Any, collections::HashSet, fmt::Debug};
/// Settings to customize how Schemas are generated.
@ -314,6 +315,70 @@ impl SchemaGenerator {
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.
///
/// If the given `schema` has a [`$ref`](../schema/struct.SchemaObject.html#structfield.reference) property which refers

View file

@ -25,6 +25,7 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
schema
}
schema => SchemaObject {
// TODO technically the schema already accepts null, so this may be unnecessary
subschemas: Some(Box::new(SubschemaValidation {
any_of: Some(vec![schema, <()>::json_schema(gen)]),
..Default::default()

View file

@ -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.
### 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
- `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro.
- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves.
- `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro
- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves
- `preserve_order` - keep the order of struct fields in `Schema` and `SchemaObject`
## Optional Dependencies
@ -236,6 +298,7 @@ pub type MapEntry<'a, K, V> = indexmap::map::Entry<'a, K, V>;
mod flatten;
mod json_schema_impls;
mod ser;
#[macro_use]
mod macros;

View file

@ -13,9 +13,66 @@
///
/// let my_schema = schema_for!(MyStruct);
/// ```
#[cfg(doc)]
#[macro_export]
macro_rules! schema_for {
($type:ty) => {
$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
View 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)
}
}

View 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
}
}
}
}
}

View 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
}
}
}
}
}

View 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
}
}

View 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")
}

View file

@ -0,0 +1,5 @@
use schemars::schema_for;
fn main() {
let _schema = schema_for!(123);
}

View 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)

View file

@ -18,7 +18,7 @@ pub fn test_default_generated_schema<T: JsonSchema>(file: &str) -> TestResult {
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)) {
Ok(j) => j,
Err(e) => {