Improvements to test coverage (#340)

This increases msrv to 1.70
This commit is contained in:
Graham Esau 2024-09-16 10:06:22 +01:00 committed by GitHub
parent 092dc17ae4
commit a479e6cc0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
239 changed files with 5780 additions and 4883 deletions

View file

@ -8,12 +8,12 @@ jobs:
strategy: strategy:
matrix: matrix:
rust: rust:
- 1.65.0 - 1.70.0
- stable - stable
- beta - beta
- nightly - nightly
include: include:
- rust: 1.65.0 - rust: 1.70.0
allow_failure: false allow_failure: false
- rust: stable - rust: stable
allow_failure: false allow_failure: false

View file

@ -6,6 +6,15 @@
- the `enumset1`/`enumset` optional dependency has been removed, as its `JsonSchema` impl did not actually match the default serialization format of `EnumSet` (https://github.com/GREsau/schemars/pull/339) - the `enumset1`/`enumset` optional dependency has been removed, as its `JsonSchema` impl did not actually match the default serialization format of `EnumSet` (https://github.com/GREsau/schemars/pull/339)
### Changed
- ⚠️ MSRV is now 1.70 ⚠️
### Fixed
- The "deserialize" schema for `bytes::Bytes`/`BytesMut` now allows strings, matching the actual deserialize behaviour of the types.
- The schema for `either::Either` now matches the actual serialize/deserialize behaviour of that type.
## [1.0.0-alpha.15] - 2024-09-05 ## [1.0.0-alpha.15] - 2024-09-05
### Added ### Added

1146
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,7 @@
[![CI Build](https://img.shields.io/github/actions/workflow/status/GREsau/schemars/ci.yml?branch=master&logo=GitHub)](https://github.com/GREsau/schemars/actions) [![CI Build](https://img.shields.io/github/actions/workflow/status/GREsau/schemars/ci.yml?branch=master&logo=GitHub)](https://github.com/GREsau/schemars/actions)
[![Crates.io](https://img.shields.io/crates/v/schemars)](https://crates.io/crates/schemars) [![Crates.io](https://img.shields.io/crates/v/schemars)](https://crates.io/crates/schemars)
[![Docs](https://img.shields.io/docsrs/schemars/1.0.0--latest?label=docs)](https://docs.rs/schemars/1.0.0--latest) [![Docs](https://img.shields.io/docsrs/schemars/1.0.0--latest?label=docs)](https://docs.rs/schemars/1.0.0--latest)
[![MSRV 1.65+](https://img.shields.io/badge/msrv-1.65-blue)](https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html) [![MSRV 1.70+](https://img.shields.io/badge/msrv-1.70-blue)](https://blog.rust-lang.org/2023/06/01/Rust-1.70.0.html)
Generate JSON Schema documents from Rust code Generate JSON Schema documents from Rust code

View file

@ -10,7 +10,7 @@ license = "MIT"
readme = "README.md" readme = "README.md"
keywords = ["rust", "json-schema", "serde"] keywords = ["rust", "json-schema", "serde"]
categories = ["encoding", "no-std"] categories = ["encoding", "no-std"]
rust-version = "1.65" rust-version = "1.70"
[dependencies] [dependencies]
schemars_derive = { version = "=1.0.0-alpha.15", optional = true, path = "../schemars_derive" } schemars_derive = { version = "=1.0.0-alpha.15", optional = true, path = "../schemars_derive" }
@ -26,7 +26,7 @@ bytes1 = { version = "1.0", default-features = false, optional = true, package =
chrono04 = { version = "0.4", default-features = false, optional = true, package = "chrono" } chrono04 = { version = "0.4", default-features = false, optional = true, package = "chrono" }
either1 = { version = "1.3", default-features = false, optional = true, package = "either" } either1 = { version = "1.3", default-features = false, optional = true, package = "either" }
indexmap2 = { version = "2.0", default-features = false, optional = true, package = "indexmap" } indexmap2 = { version = "2.0", default-features = false, optional = true, package = "indexmap" }
rust_decimal1 = { version = "1", default-features = false, optional = true, package = "rust_decimal"} rust_decimal1 = { version = "1", default-features = false, optional = true, package = "rust_decimal" }
semver1 = { version = "1.0.9", default-features = false, optional = true, package = "semver" } semver1 = { version = "1.0.9", default-features = false, optional = true, package = "semver" }
smallvec1 = { version = "1.0", default-features = false, optional = true, package = "smallvec" } smallvec1 = { version = "1.0", default-features = false, optional = true, package = "smallvec" }
smol_str02 = { version = "0.2.1", default-features = false, optional = true, package = "smol_str" } smol_str02 = { version = "0.2.1", default-features = false, optional = true, package = "smol_str" }
@ -37,6 +37,26 @@ uuid1 = { version = "1.0", default-features = false, optional = true, package =
pretty_assertions = "1.2.1" pretty_assertions = "1.2.1"
trybuild = "1.0" trybuild = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
jsonschema = { version = "0.19.0", default-features = false }
snapbox = { version = "0.6.17", features = ["json"] }
serde_repr = "0.1.19"
# Use github source until published garde version supports `length(equal = ...)` attr
garde = { git = "https://github.com/jprochazk/garde.git", rev = "be00ddddf8de14530ee890ccfdbaf0b13fb32852", features = ["derive", "email", "regex", "url"] }
validator = { version = "0.18.1", features = ["derive"] }
regex = { version = "1.10.6", default-features = false }
arrayvec07 = { version = "0.7", default-features = false, features = ["serde"], package = "arrayvec"}
bigdecimal04 = { version = "0.4", default-features = false, features = ["serde"], package = "bigdecimal" }
bytes1 = { version = "1.0", default-features = false, features = ["serde"], package = "bytes" }
chrono04 = { version = "0.4", default-features = false, features = ["serde"], package = "chrono" }
either1 = { version = "1.3", default-features = false, features = ["serde"], package = "either" }
indexmap2 = { version = "2.0", default-features = false, features = ["serde"], package = "indexmap" }
rust_decimal1 = { version = "1", default-features = false, features = ["serde"], package = "rust_decimal" }
semver1 = { version = "1.0.9", default-features = false, features = ["serde"], package = "semver" }
smallvec1 = { version = "1.0", default-features = false, features = ["serde"], package = "smallvec" }
smol_str02 = { version = "0.2.1", default-features = false, features = ["serde"], package = "smol_str" }
url2 = { version = "2.0", default-features = false, features = ["serde"], package = "url" }
uuid1 = { version = "1.0", default-features = false, features = ["serde"], package = "uuid" }
[features] [features]
default = ["derive", "std"] default = ["derive", "std"]
@ -59,62 +79,10 @@ raw_value = ["serde_json/raw_value"]
# For internal/CI use only # For internal/CI use only
_ui_test = [] _ui_test = []
[[test]]
name = "std_time"
required-features = ["std"]
[[test]]
name = "ffi"
required-features = ["std"]
[[test]] [[test]]
name = "ui" name = "ui"
required-features = ["_ui_test"] required-features = ["_ui_test"]
[[test]]
name = "chrono"
required-features = ["chrono04"]
[[test]]
name = "indexmap"
required-features = ["indexmap2"]
[[test]]
name = "either"
required-features = ["either1"]
[[test]]
name = "uuid"
required-features = ["uuid1"]
[[test]]
name = "smallvec"
required-features = ["smallvec1"]
[[test]]
name = "bytes"
required-features = ["bytes1"]
[[test]]
name = "arrayvec"
required-features = ["arrayvec07"]
[[test]]
name = "url"
required-features = ["url2"]
[[test]]
name = "smol_str"
required-features = ["smol_str02"]
[[test]]
name = "semver"
required-features = ["semver1"]
[[test]]
name = "decimal"
required-features = ["rust_decimal1", "bigdecimal04"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--extend-css", "docs-rs-custom.css"] rustdoc-args = ["--extend-css", "docs-rs-custom.css"]

View file

@ -0,0 +1,29 @@
use crate::_alloc_prelude::*;
use crate::generate::Contract;
use crate::{JsonSchema, Schema};
use alloc::borrow::Cow;
use serde_json::Value;
impl JsonSchema for bytes1::Bytes {
fn schema_name() -> Cow<'static, str> {
"Bytes".into()
}
fn schema_id() -> Cow<'static, str> {
"bytes::Bytes".into()
}
fn json_schema(generator: &mut crate::SchemaGenerator) -> crate::Schema {
let ty = match generator.contract() {
Contract::Deserialize => Value::Array(vec!["array".into(), "string".into()]),
Contract::Serialize => "array".into(),
};
let mut result = Schema::default();
result.insert("type".to_owned(), ty);
result.insert("items".to_owned(), generator.subschema_for::<u8>().into());
result
}
}
forward_impl!(bytes1::BytesMut => bytes1::Bytes);

View file

@ -58,5 +58,5 @@ macro_rules! formatted_string_impl {
formatted_string_impl!(NaiveDate, "date"); formatted_string_impl!(NaiveDate, "date");
formatted_string_impl!(NaiveDateTime, "partial-date-time"); formatted_string_impl!(NaiveDateTime, "partial-date-time");
formatted_string_impl!(NaiveTime, "partial-date-time"); formatted_string_impl!(NaiveTime, "partial-time");
formatted_string_impl!(DateTime, "date-time", <Tz: TimeZone> JsonSchema for DateTime<Tz>); formatted_string_impl!(DateTime, "date-time", <Tz: TimeZone> JsonSchema for DateTime<Tz>);

View file

@ -17,7 +17,28 @@ impl<L: JsonSchema, R: JsonSchema> JsonSchema for Either<L, R> {
fn json_schema(generator: &mut SchemaGenerator) -> Schema { fn json_schema(generator: &mut SchemaGenerator) -> Schema {
json_schema!({ json_schema!({
"anyOf": [generator.subschema_for::<L>(), generator.subschema_for::<R>()], "oneOf": [
{
"type": "object",
"properties": {
"Left": generator.subschema_for::<L>()
},
"additionalProperties": false,
"required": [
"Left"
]
},
{
"type": "object",
"properties": {
"Right": generator.subschema_for::<R>()
},
"additionalProperties": false,
"required": [
"Right"
]
}
]
}) })
} }
} }

View file

@ -2,6 +2,7 @@ use crate::SchemaGenerator;
use crate::_alloc_prelude::*; use crate::_alloc_prelude::*;
use crate::{json_schema, JsonSchema, Schema}; use crate::{json_schema, JsonSchema, Schema};
use alloc::borrow::Cow; use alloc::borrow::Cow;
use serde_json::json;
use std::ffi::{CStr, CString, OsStr, OsString}; use std::ffi::{CStr, CString, OsStr, OsString};
impl JsonSchema for OsString { impl JsonSchema for OsString {
@ -37,5 +38,31 @@ impl JsonSchema for OsString {
forward_impl!(OsStr => OsString); forward_impl!(OsStr => OsString);
forward_impl!(CString => Vec<u8>); impl JsonSchema for CString {
forward_impl!(CStr => Vec<u8>); fn schema_name() -> Cow<'static, str> {
"CString".into()
}
fn schema_id() -> Cow<'static, str> {
"std::ffi::CString".into()
}
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
let ty = if generator.contract().is_deserialize() {
json!(["array", "string"])
} else {
json!("array")
};
json_schema!({
"type": ty,
"items": {
"type": "integer",
"minimum": 1,
"maximum": 255
},
})
}
}
forward_impl!(CStr => CString);

View file

@ -42,8 +42,7 @@ macro_rules! forward_impl {
mod array; mod array;
mod core; mod core;
mod maps; mod maps;
mod nonzero_signed; mod nonzero;
mod nonzero_unsigned;
mod primitives; mod primitives;
mod sequences; mod sequences;
mod serdejson; mod serdejson;
@ -61,10 +60,7 @@ mod ffi;
mod arrayvec07; mod arrayvec07;
#[cfg(feature = "bytes1")] #[cfg(feature = "bytes1")]
mod bytes1 { mod bytes1;
forward_impl!(bytes1::Bytes => alloc::vec::Vec<u8>);
forward_impl!(bytes1::BytesMut => alloc::vec::Vec<u8>);
}
#[cfg(feature = "chrono04")] #[cfg(feature = "chrono04")]
mod chrono04; mod chrono04;

View file

@ -0,0 +1,64 @@
use crate::_alloc_prelude::*;
use crate::{JsonSchema, Schema, SchemaGenerator};
use alloc::borrow::Cow;
use core::num::*;
macro_rules! nonzero_signed_impl {
($type:ty => $primitive:ty) => {
impl JsonSchema for $type {
always_inline!();
fn schema_name() -> Cow<'static, str> {
stringify!($type).into()
}
fn schema_id() -> Cow<'static, str> {
stringify!(std::num::$type).into()
}
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
let mut schema = <$primitive>::json_schema(generator);
schema.insert("not".to_owned(), serde_json::json!({
"const": 0
}));
schema
}
}
};
}
nonzero_signed_impl!(NonZeroI8 => i8);
nonzero_signed_impl!(NonZeroI16 => i16);
nonzero_signed_impl!(NonZeroI32 => i32);
nonzero_signed_impl!(NonZeroI64 => i64);
nonzero_signed_impl!(NonZeroI128 => i128);
nonzero_signed_impl!(NonZeroIsize => isize);
macro_rules! nonzero_unsigned_impl {
($type:ty => $primitive:ty) => {
impl JsonSchema for $type {
always_inline!();
fn schema_name() -> Cow<'static, str> {
stringify!($type).into()
}
fn schema_id() -> Cow<'static, str> {
stringify!(std::num::$type).into()
}
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
let mut schema = <$primitive>::json_schema(generator);
schema.insert("minimum".to_owned(), 1.into());
schema
}
}
};
}
nonzero_unsigned_impl!(NonZeroU8 => u8);
nonzero_unsigned_impl!(NonZeroU16 => u16);
nonzero_unsigned_impl!(NonZeroU32 => u32);
nonzero_unsigned_impl!(NonZeroU64 => u64);
nonzero_unsigned_impl!(NonZeroU128 => u128);
nonzero_unsigned_impl!(NonZeroUsize => usize);

View file

@ -1,37 +0,0 @@
use crate::SchemaGenerator;
use crate::_alloc_prelude::*;
use crate::{JsonSchema, Schema};
use alloc::borrow::Cow;
use core::num::*;
macro_rules! nonzero_unsigned_impl {
($type:ty => $primitive:ty) => {
impl JsonSchema for $type {
always_inline!();
fn schema_name() -> Cow<'static, str> {
stringify!($type).into()
}
fn schema_id() -> Cow<'static, str> {
stringify!(std::num::$type).into()
}
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
let mut schema = <$primitive>::json_schema(generator);
let object = schema.ensure_object();
object.insert("not".to_owned(), serde_json::json!({
"const": 0
}));
schema
}
}
};
}
nonzero_unsigned_impl!(NonZeroI8 => i8);
nonzero_unsigned_impl!(NonZeroI16 => i16);
nonzero_unsigned_impl!(NonZeroI32 => i32);
nonzero_unsigned_impl!(NonZeroI64 => i64);
nonzero_unsigned_impl!(NonZeroI128 => i128);
nonzero_unsigned_impl!(NonZeroIsize => isize);

View file

@ -1,36 +0,0 @@
use crate::JsonSchema;
use crate::Schema;
use crate::SchemaGenerator;
use crate::_alloc_prelude::*;
use alloc::borrow::Cow;
use core::num::*;
macro_rules! nonzero_unsigned_impl {
($type:ty => $primitive:ty) => {
impl JsonSchema for $type {
always_inline!();
fn schema_name() -> Cow<'static, str> {
stringify!($type).into()
}
fn schema_id() -> Cow<'static, str> {
stringify!(std::num::$type).into()
}
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
let mut schema = <$primitive>::json_schema(generator);
let object = schema.ensure_object();
object.insert("minimum".to_owned(), 1.into());
schema
}
}
};
}
nonzero_unsigned_impl!(NonZeroU8 => u8);
nonzero_unsigned_impl!(NonZeroU16 => u16);
nonzero_unsigned_impl!(NonZeroU32 => u32);
nonzero_unsigned_impl!(NonZeroU64 => u64);
nonzero_unsigned_impl!(NonZeroU128 => u128);
nonzero_unsigned_impl!(NonZeroUsize => usize);

View file

@ -4,10 +4,8 @@ use alloc::borrow::Cow;
use semver1::Version; use semver1::Version;
impl JsonSchema for Version { impl JsonSchema for Version {
always_inline!();
fn schema_name() -> Cow<'static, str> { fn schema_name() -> Cow<'static, str> {
"Version".into() "SemVer".into()
} }
fn schema_id() -> Cow<'static, str> { fn schema_id() -> Cow<'static, str> {

View file

@ -1,3 +0,0 @@
# Actual Generated Schemas
If a test fails because a generated schema did not match the expected JSON, then the actual schema will be written to a JSON file in this directory.

View file

@ -1,12 +0,0 @@
mod util;
use util::*;
#[test]
fn arrayvec07() -> TestResult {
test_default_generated_schema::<arrayvec07::ArrayVec<i32, 16>>("arrayvec")
}
#[test]
fn arrayvec07_string() -> TestResult {
test_default_generated_schema::<arrayvec07::ArrayString<16>>("arrayvec_string")
}

View file

@ -1,8 +0,0 @@
mod util;
use bytes1::{Bytes, BytesMut};
use util::*;
#[test]
fn bytes() -> TestResult {
test_default_generated_schema::<(Bytes, BytesMut)>("bytes")
}

View file

@ -1,19 +0,0 @@
mod util;
use chrono04::prelude::*;
use schemars::JsonSchema;
use util::*;
#[allow(dead_code)]
#[derive(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")
}

View file

@ -1,214 +0,0 @@
mod util;
use schemars::{generate::SchemaSettings, JsonSchema};
use util::*;
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(rename_all(serialize = "SCREAMING-KEBAB-CASE"))]
struct MyStruct {
#[schemars(skip_deserializing)]
read_only: bool,
#[schemars(skip_serializing)]
write_only: bool,
#[schemars(default)]
default: bool,
#[schemars(skip_serializing_if = "anything")]
skip_serializing_if: bool,
#[schemars(rename(serialize = "ser_renamed", deserialize = "de_renamed"))]
renamed: bool,
option: Option<bool>,
}
#[test]
fn contract_deserialize() -> TestResult {
test_generated_schema::<MyStruct>(
"contract_deserialize",
SchemaSettings::default().for_deserialize(),
)
}
#[test]
fn contract_serialize() -> TestResult {
test_generated_schema::<MyStruct>(
"contract_serialize",
SchemaSettings::default().for_serialize(),
)
}
#[allow(dead_code)]
#[derive(JsonSchema)]
struct TupleStruct(
String,
#[schemars(skip_serializing)] bool,
String,
#[schemars(skip_deserializing)] bool,
String,
);
#[test]
fn contract_deserialize_tuple_struct() -> TestResult {
test_generated_schema::<TupleStruct>(
"contract_deserialize_tuple_struct",
SchemaSettings::default().for_deserialize(),
)
}
#[test]
fn contract_serialize_tuple_struct() -> TestResult {
test_generated_schema::<TupleStruct>(
"contract_serialize_tuple_struct",
SchemaSettings::default().for_serialize(),
)
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(
rename_all(serialize = "SCREAMING-KEBAB-CASE"),
rename_all_fields(serialize = "PascalCase")
)]
enum ExternalEnum {
#[schemars(skip_deserializing)]
ReadOnlyUnit,
#[schemars(skip_serializing)]
WriteOnlyUnit,
#[schemars(skip_deserializing)]
ReadOnlyStruct { s: String },
#[schemars(skip_serializing)]
WriteOnlyStruct { i: isize },
#[schemars(rename(serialize = "ser_renamed_unit", deserialize = "de_renamed_unit"))]
RenamedUnit,
#[schemars(rename(serialize = "ser_renamed_struct", deserialize = "de_renamed_struct"))]
RenamedStruct { b: bool },
}
#[test]
fn contract_deserialize_external_tag_enum() -> TestResult {
test_generated_schema::<ExternalEnum>(
"contract_deserialize_external_tag_enum",
SchemaSettings::default().for_deserialize(),
)
}
#[test]
fn contract_serialize_external_tag_enum() -> TestResult {
test_generated_schema::<ExternalEnum>(
"contract_serialize_external_tag_enum",
SchemaSettings::default().for_serialize(),
)
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(
tag = "tag",
rename_all(serialize = "SCREAMING-KEBAB-CASE"),
rename_all_fields(serialize = "PascalCase")
)]
enum InternalEnum {
#[schemars(skip_deserializing)]
ReadOnlyUnit,
#[schemars(skip_serializing)]
WriteOnlyUnit,
#[schemars(skip_deserializing)]
ReadOnlyStruct { s: String },
#[schemars(skip_serializing)]
WriteOnlyStruct { i: isize },
#[schemars(rename(serialize = "ser_renamed_unit", deserialize = "de_renamed_unit"))]
RenamedUnit,
#[schemars(rename(serialize = "ser_renamed_struct", deserialize = "de_renamed_struct"))]
RenamedStruct { b: bool },
}
#[test]
fn contract_deserialize_internal_tag_enum() -> TestResult {
test_generated_schema::<InternalEnum>(
"contract_deserialize_internal_tag_enum",
SchemaSettings::default().for_deserialize(),
)
}
#[test]
fn contract_serialize_internal_tag_enum() -> TestResult {
test_generated_schema::<InternalEnum>(
"contract_serialize_internal_tag_enum",
SchemaSettings::default().for_serialize(),
)
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(
tag = "tag",
content = "content",
rename_all(serialize = "SCREAMING-KEBAB-CASE"),
rename_all_fields(serialize = "PascalCase")
)]
enum AdjacentEnum {
#[schemars(skip_deserializing)]
ReadOnlyUnit,
#[schemars(skip_serializing)]
WriteOnlyUnit,
#[schemars(skip_deserializing)]
ReadOnlyStruct { s: String },
#[schemars(skip_serializing)]
WriteOnlyStruct { i: isize },
#[schemars(rename(serialize = "ser_renamed_unit", deserialize = "de_renamed_unit"))]
RenamedUnit,
#[schemars(rename(serialize = "ser_renamed_struct", deserialize = "de_renamed_struct"))]
RenamedStruct { b: bool },
}
#[test]
fn contract_deserialize_adjacent_tag_enum() -> TestResult {
test_generated_schema::<AdjacentEnum>(
"contract_deserialize_adjacent_tag_enum",
SchemaSettings::default().for_deserialize(),
)
}
#[test]
fn contract_serialize_adjacent_tag_enum() -> TestResult {
test_generated_schema::<AdjacentEnum>(
"contract_serialize_adjacent_tag_enum",
SchemaSettings::default().for_serialize(),
)
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(
untagged,
rename_all(serialize = "SCREAMING-KEBAB-CASE"),
rename_all_fields(serialize = "PascalCase")
)]
enum UntaggedEnum {
#[schemars(skip_deserializing)]
ReadOnlyUnit,
#[schemars(skip_serializing)]
WriteOnlyUnit,
#[schemars(skip_deserializing)]
ReadOnlyStruct { s: String },
#[schemars(skip_serializing)]
WriteOnlyStruct { i: isize },
#[schemars(rename(serialize = "ser_renamed_unit", deserialize = "de_renamed_unit"))]
RenamedUnit,
#[schemars(rename(serialize = "ser_renamed_struct", deserialize = "de_renamed_struct"))]
RenamedStruct { b: bool },
}
#[test]
fn contract_deserialize_untagged_enum() -> TestResult {
test_generated_schema::<UntaggedEnum>(
"contract_deserialize_untagged_enum",
SchemaSettings::default().for_deserialize(),
)
}
#[test]
fn contract_serialize_untagged_enum() -> TestResult {
test_generated_schema::<UntaggedEnum>(
"contract_serialize_untagged_enum",
SchemaSettings::default().for_serialize(),
)
}

View file

@ -1,20 +0,0 @@
mod util;
use ::schemars as not_schemars;
use util::*;
#[allow(unused_imports)]
use std as schemars;
#[allow(dead_code)]
#[derive(not_schemars::JsonSchema)]
#[schemars(crate = "not_schemars")]
struct Struct {
/// This is a document
foo: i32,
bar: bool,
}
#[test]
fn test_crate_alias() -> TestResult {
test_default_generated_schema::<Struct>("crate_alias")
}

View file

@ -1,12 +0,0 @@
mod util;
use util::*;
#[test]
fn rust_decimal() -> TestResult {
test_default_generated_schema::<rust_decimal1::Decimal>("rust_decimal")
}
#[test]
fn bigdecimal04() -> TestResult {
test_default_generated_schema::<bigdecimal04::BigDecimal>("bigdecimal04")
}

View file

@ -1,59 +0,0 @@
mod util;
use schemars::JsonSchema;
use util::*;
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
value == &T::default()
}
fn ten_and_true() -> MyStruct2 {
MyStruct2 {
my_int: 10,
my_bool: true,
}
}
fn six() -> i32 {
6
}
fn custom_serialize<S>(value: &MyStruct2, ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
ser.collect_str(&format_args!("i:{} b:{}", value.my_int, value.my_bool))
}
#[allow(dead_code)]
#[derive(Default, JsonSchema)]
#[serde(default)]
struct MyStruct {
my_int: i32,
my_bool: bool,
my_optional_string: Option<String>,
#[serde(serialize_with = "custom_serialize")]
my_struct2: MyStruct2,
#[serde(
serialize_with = "custom_serialize",
skip_serializing_if = "is_default"
)]
my_struct2_default_skipped: MyStruct2,
not_serialize: NotSerialize,
}
#[allow(dead_code)]
#[derive(Default, JsonSchema, PartialEq)]
#[serde(default = "ten_and_true")]
struct MyStruct2 {
#[serde(default = "six")]
my_int: i32,
my_bool: bool,
}
#[derive(Default, JsonSchema)]
struct NotSerialize;
#[test]
fn schema_default_values() -> TestResult {
test_default_generated_schema::<MyStruct>("default")
}

View file

@ -1,39 +0,0 @@
#![allow(deprecated)]
mod util;
use schemars::JsonSchema;
use util::*;
#[allow(dead_code)]
#[derive(JsonSchema)]
#[deprecated]
struct DeprecatedStruct {
foo: i32,
#[deprecated]
deprecated_field: bool,
}
#[test]
fn deprecated_struct() -> TestResult {
test_default_generated_schema::<DeprecatedStruct>("deprecated-struct")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[deprecated]
enum DeprecatedEnum {
Unit,
#[deprecated]
DeprecatedUnitVariant,
#[deprecated]
DeprecatedStructVariant {
foo: i32,
#[deprecated]
deprecated_field: bool,
},
}
#[test]
fn deprecated_enum() -> TestResult {
test_default_generated_schema::<DeprecatedEnum>("deprecated-enum")
}

View file

@ -1,8 +0,0 @@
mod util;
use either1::Either;
use util::*;
#[test]
fn either() -> TestResult {
test_default_generated_schema::<Either<i32, Either<bool, ()>>>("either")
}

View file

@ -1,185 +0,0 @@
mod util;
use std::collections::BTreeMap;
use schemars::JsonSchema;
use util::*;
// Ensure that schemars_derive uses the full path to std::string::String
pub struct String;
#[derive(JsonSchema)]
struct UnitStruct;
#[allow(dead_code)]
#[derive(JsonSchema)]
struct Struct {
foo: i32,
bar: bool,
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(rename_all = "camelCase")]
enum External {
UnitOne,
StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
UnitTwo,
Tuple(i32, bool),
#[schemars(with = "i32")]
WithInt,
}
#[test]
fn enum_external_tag() -> TestResult {
test_default_generated_schema::<External>("enum-external")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(tag = "typeProperty")]
enum Internal {
UnitOne,
StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
UnitTwo,
#[schemars(with = "i32")]
WithInt,
}
#[test]
fn enum_internal_tag() -> TestResult {
test_default_generated_schema::<Internal>("enum-internal")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(untagged)]
enum Untagged {
UnitOne,
StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
Tuple(i32, bool),
#[schemars(with = "i32")]
WithInt,
}
#[test]
fn enum_untagged() -> TestResult {
test_default_generated_schema::<Untagged>("enum-untagged")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(tag = "t", content = "c")]
enum Adjacent {
UnitOne,
StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
Tuple(i32, bool),
UnitTwo,
#[schemars(with = "i32")]
WithInt,
}
#[test]
fn enum_adjacent_tagged() -> TestResult {
test_default_generated_schema::<Adjacent>("enum-adjacent-tagged")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(tag = "typeProperty")]
enum SimpleInternal {
A,
B,
C,
}
#[test]
fn enum_simple_internal_tag() -> TestResult {
test_default_generated_schema::<SimpleInternal>("enum-simple-internal")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
enum SoundOfMusic {
/// # A deer
///
/// A female deer
Do,
/// A drop of golden sun
Re,
/// A name I call myself
Mi,
}
#[test]
fn enum_unit_with_doc_comments() -> TestResult {
test_default_generated_schema::<SoundOfMusic>("enum-unit-doc")
}
#[derive(JsonSchema)]
enum NoVariants {}
#[test]
fn enum_no_variants() -> TestResult {
test_default_generated_schema::<NoVariants>("no-variants")
}
#[derive(JsonSchema)]
#[serde(rename_all_fields = "PascalCase")]
pub enum RenameAllFields {
First {
nested_attribute: std::string::String,
},
}
#[derive(JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum RenameAll {
First { nested_attribute: bool },
}
#[derive(JsonSchema)]
pub enum RenameAttribute {
First {
#[serde(rename = "RenamedAttribute")]
nested_attribute: std::string::String,
},
}
#[test]
fn enum_unit_rename_attribute() -> TestResult {
test_default_generated_schema::<RenameAttribute>("enum-rename-attr")
}
#[test]
fn enum_unit_rename_all_fields() -> TestResult {
test_default_generated_schema::<RenameAllFields>("enum-rename-all-fields")
}
#[test]
fn enum_unit_rename_all() -> TestResult {
test_default_generated_schema::<RenameAll>("enum-rename-all")
}

View file

@ -1,127 +0,0 @@
mod util;
use std::collections::BTreeMap;
use schemars::JsonSchema;
use util::*;
// Ensure that schemars_derive uses the full path to std::string::String
pub struct String;
#[derive(JsonSchema)]
struct UnitStruct;
#[allow(dead_code)]
#[derive(JsonSchema)]
struct Struct {
foo: i32,
bar: bool,
}
// Outer container should always have additionalProperties: false
// `Struct` variant should have additionalProperties: false
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(rename_all = "camelCase", deny_unknown_fields)]
enum External {
UnitOne,
StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
UnitTwo,
Tuple(i32, bool),
#[schemars(with = "i32")]
WithInt,
}
#[test]
fn enum_external_tag() -> TestResult {
test_default_generated_schema::<External>("enum-external-duf")
}
// Only `Struct` variant should have additionalProperties: false
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(tag = "typeProperty", deny_unknown_fields)]
enum Internal {
UnitOne,
StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
UnitTwo,
#[schemars(with = "i32")]
WithInt,
}
#[test]
fn enum_internal_tag() -> TestResult {
test_default_generated_schema::<Internal>("enum-internal-duf")
}
// Only `Struct` variant should have additionalProperties: false
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(untagged, deny_unknown_fields)]
enum Untagged {
UnitOne,
StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
Tuple(i32, bool),
#[schemars(with = "i32")]
WithInt,
}
#[test]
fn enum_untagged() -> TestResult {
test_default_generated_schema::<Untagged>("enum-untagged-duf")
}
// Outer container and `Struct` variant should have additionalProperties: false
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(tag = "t", content = "c", deny_unknown_fields)]
enum Adjacent {
UnitOne,
StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
Tuple(i32, bool),
UnitTwo,
#[schemars(with = "i32")]
WithInt,
}
#[test]
fn enum_adjacent_tagged() -> TestResult {
test_default_generated_schema::<Adjacent>("enum-adjacent-tagged-duf")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(tag = "typeProperty", deny_unknown_fields)]
enum SimpleInternal {
A,
B,
C,
}
#[test]
fn enum_simple_internal_tag() -> TestResult {
test_default_generated_schema::<SimpleInternal>("enum-simple-internal-duf")
}

View file

@ -1,89 +0,0 @@
mod util;
use schemars::{generate::SchemaSettings, JsonSchema};
use util::*;
#[allow(dead_code)]
#[derive(JsonSchema)]
struct Flat {
f: f32,
#[schemars(flatten)]
e1: Enum1,
#[schemars(flatten)]
e2: Enum2,
#[schemars(flatten)]
e3: Enum3,
#[schemars(flatten)]
e4: Enum4,
#[schemars(flatten)]
e5: Enum5,
}
#[allow(dead_code)]
#[derive(JsonSchema)]
enum Enum1 {
B(bool),
S(String),
}
#[allow(dead_code)]
#[derive(JsonSchema)]
enum Enum2 {
U(u32),
F(f64),
}
#[allow(dead_code)]
#[derive(JsonSchema)]
enum Enum3 {
B2(bool),
S2(String),
}
#[allow(dead_code)]
#[derive(JsonSchema)]
enum Enum4 {
U2(u32),
F2(f64),
}
#[allow(dead_code)]
#[derive(JsonSchema)]
enum Enum5 {
B3(bool),
S3(String),
}
#[test]
fn test_flat_schema() -> TestResult {
test_default_generated_schema::<Flat>("enum_flatten")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(deny_unknown_fields)]
struct FlatDenyUnknownFields {
f: f32,
#[schemars(flatten)]
e1: Enum1,
#[schemars(flatten)]
e2: Enum2,
#[schemars(flatten)]
e3: Enum3,
#[schemars(flatten)]
e4: Enum4,
#[schemars(flatten)]
e5: Enum5,
}
#[test]
fn test_flat_schema_duf() -> TestResult {
test_default_generated_schema::<FlatDenyUnknownFields>("enum_flatten_duf")
}
#[test]
fn test_flat_schema_duf_draft07() -> TestResult {
test_generated_schema::<FlatDenyUnknownFields>(
"enum_flatten_duf_draft07",
SchemaSettings::draft07(),
)
}

View file

@ -1,35 +0,0 @@
mod util;
use schemars::JsonSchema_repr;
use util::*;
#[derive(JsonSchema_repr)]
#[repr(u8)]
pub enum Enum {
Zero,
One,
Five = 5,
Six,
Three = 3,
}
#[test]
fn enum_repr() -> TestResult {
test_default_generated_schema::<Enum>("enum-repr")
}
#[derive(JsonSchema_repr)]
#[repr(i64)]
#[serde(rename = "Renamed")]
/// Description from comment
pub enum EnumWithAttrs {
Zero,
One,
Five = 5,
Six,
Three = 3,
}
#[test]
fn enum_repr_with_attrs() -> TestResult {
test_default_generated_schema::<EnumWithAttrs>("enum-repr-with-attrs")
}

View file

@ -1,5 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "string",
"type": "string"
}

View file

@ -1,25 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Tuple_of_Array_of_uint8_and_Array_of_uint8",
"type": "array",
"prefixItems": [
{
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0
}
},
{
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0
}
}
],
"minItems": 2,
"maxItems": 2
}

View file

@ -1,52 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MyStruct",
"type": "object",
"properties": {
"my_int": {
"type": "integer",
"format": "int32",
"default": 0
},
"my_bool": {
"type": "boolean",
"default": false
},
"my_optional_string": {
"type": [
"string",
"null"
],
"default": null
},
"my_struct2": {
"$ref": "#/$defs/MyStruct2",
"default": "i:0 b:false"
},
"my_struct2_default_skipped": {
"$ref": "#/$defs/MyStruct2"
},
"not_serialize": {
"$ref": "#/$defs/NotSerialize"
}
},
"$defs": {
"MyStruct2": {
"type": "object",
"properties": {
"my_int": {
"type": "integer",
"format": "int32",
"default": 6
},
"my_bool": {
"type": "boolean",
"default": true
}
}
},
"NotSerialize": {
"type": "null"
}
}
}

View file

@ -1,57 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MyStruct",
"type": "object",
"properties": {
"duration": {
"$ref": "#/$defs/Duration"
},
"time": {
"$ref": "#/$defs/SystemTime"
}
},
"required": [
"duration",
"time"
],
"$defs": {
"Duration": {
"type": "object",
"properties": {
"secs": {
"type": "integer",
"format": "uint64",
"minimum": 0
},
"nanos": {
"type": "integer",
"format": "uint32",
"minimum": 0
}
},
"required": [
"secs",
"nanos"
]
},
"SystemTime": {
"type": "object",
"properties": {
"secs_since_epoch": {
"type": "integer",
"format": "uint64",
"minimum": 0
},
"nanos_since_epoch": {
"type": "integer",
"format": "uint32",
"minimum": 0
}
},
"required": [
"secs_since_epoch",
"nanos_since_epoch"
]
}
}
}

View file

@ -1,20 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Either_int32_or_Either_boolean_or_null",
"anyOf": [
{
"type": "integer",
"format": "int32"
},
{
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
]
}
]
}

View file

@ -1,184 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Adjacent",
"oneOf": [
{
"type": "object",
"properties": {
"t": {
"type": "string",
"const": "UnitOne"
}
},
"required": [
"t"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"t": {
"type": "string",
"const": "StringMap"
},
"c": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"t",
"c"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"t": {
"type": "string",
"const": "UnitStructNewType"
},
"c": {
"$ref": "#/$defs/UnitStruct"
}
},
"required": [
"t",
"c"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"t": {
"type": "string",
"const": "StructNewType"
},
"c": {
"$ref": "#/$defs/Struct"
}
},
"required": [
"t",
"c"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"t": {
"type": "string",
"const": "Struct"
},
"c": {
"type": "object",
"properties": {
"foo": {
"type": "integer",
"format": "int32"
},
"bar": {
"type": "boolean"
}
},
"additionalProperties": false,
"required": [
"foo",
"bar"
]
}
},
"required": [
"t",
"c"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"t": {
"type": "string",
"const": "Tuple"
},
"c": {
"type": "array",
"prefixItems": [
{
"type": "integer",
"format": "int32"
},
{
"type": "boolean"
}
],
"minItems": 2,
"maxItems": 2
}
},
"required": [
"t",
"c"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"t": {
"type": "string",
"const": "UnitTwo"
}
},
"required": [
"t"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"t": {
"type": "string",
"const": "WithInt"
},
"c": {
"type": "integer",
"format": "int32"
}
},
"required": [
"t",
"c"
],
"additionalProperties": false
}
],
"$defs": {
"UnitStruct": {
"type": "null"
},
"Struct": {
"type": "object",
"properties": {
"foo": {
"type": "integer",
"format": "int32"
},
"bar": {
"type": "boolean"
}
},
"required": [
"foo",
"bar"
]
}
}
}

View file

@ -1,26 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "RenameAllFields",
"oneOf": [
{
"type": "object",
"properties": {
"First": {
"type": "object",
"properties": {
"NestedAttribute": {
"type": "string"
}
},
"required": [
"NestedAttribute"
]
}
},
"required": [
"First"
],
"additionalProperties": false
}
]
}

View file

@ -1,26 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "RenameAll",
"oneOf": [
{
"type": "object",
"properties": {
"first": {
"type": "object",
"properties": {
"nested_attribute": {
"type": "boolean"
}
},
"required": [
"nested_attribute"
]
}
},
"required": [
"first"
],
"additionalProperties": false
}
]
}

View file

@ -1,26 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "RenameAttribute",
"oneOf": [
{
"type": "object",
"properties": {
"First": {
"type": "object",
"properties": {
"RenamedAttribute": {
"type": "string"
}
},
"required": [
"RenamedAttribute"
]
}
},
"required": [
"First"
],
"additionalProperties": false
}
]
}

View file

@ -1,45 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "SimpleInternal",
"oneOf": [
{
"type": "object",
"properties": {
"typeProperty": {
"type": "string",
"const": "A"
}
},
"required": [
"typeProperty"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"typeProperty": {
"type": "string",
"const": "B"
}
},
"required": [
"typeProperty"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"typeProperty": {
"type": "string",
"const": "C"
}
},
"required": [
"typeProperty"
],
"additionalProperties": false
}
]
}

View file

@ -1,42 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "SimpleInternal",
"oneOf": [
{
"type": "object",
"properties": {
"typeProperty": {
"type": "string",
"const": "A"
}
},
"required": [
"typeProperty"
]
},
{
"type": "object",
"properties": {
"typeProperty": {
"type": "string",
"const": "B"
}
},
"required": [
"typeProperty"
]
},
{
"type": "object",
"properties": {
"typeProperty": {
"type": "string",
"const": "C"
}
},
"required": [
"typeProperty"
]
}
]
}

View file

@ -1,76 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"title": "MyStruct",
"type": "object",
"properties": {
"myInt": {
"type": "integer"
},
"myBool": {
"type": "boolean"
},
"myNullableEnum": true,
"myInnerStruct": {
"type": "object",
"properties": {
"my_map": {
"type": "object",
"additionalProperties": {
"type": "number"
}
},
"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",
"minItems": 2,
"maxItems": 2,
"items": [
{
"type": "string",
"minLength": 1,
"maxLength": 1
},
{
"type": "integer"
}
]
}
}
}
},
"examples": [
{
"myInt": 123,
"myBool": true,
"myNullableEnum": null,
"myInnerStruct": {
"my_map": {
"": 0.0
},
"my_vec": [
"hello",
"world"
],
"my_empty_map": {},
"my_empty_vec": [],
"my_tuple": [
"💩",
42
]
}
}
]
}

View file

@ -1,25 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "IndexMapTypes",
"type": "object",
"properties": {
"map": {
"type": "object",
"additionalProperties": {
"type": "boolean"
}
},
"set": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "integer",
"format": "int"
}
}
},
"required": [
"map",
"set"
]
}

View file

@ -1,32 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "OuterEnum",
"oneOf": [
{
"type": "object",
"properties": {
"InnerStruct": {
"$ref": "#/$defs/InnerStruct"
}
},
"required": [
"InnerStruct"
],
"additionalProperties": false
}
],
"$defs": {
"InnerStruct": {
"type": "object",
"properties": {
"x": {
"type": "integer",
"format": "int32"
}
},
"required": [
"x"
]
}
}
}

View file

@ -1,20 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "A",
"type": "object",
"properties": {
"x": {
"type": "integer",
"format": "uint8",
"minimum": 0
},
"v": {
"type": "integer",
"format": "int32"
}
},
"required": [
"x",
"v"
]
}

View file

@ -1,70 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MyStruct",
"type": "object",
"properties": {
"my_int": {
"type": "integer",
"format": "int32"
},
"my_bool": {
"type": "boolean"
},
"my_nullable_enum": {
"anyOf": [
{
"$ref": "#/$defs/MyEnum"
},
{
"type": "null"
}
]
}
},
"required": [
"my_int",
"my_bool"
],
"$defs": {
"MyEnum": {
"oneOf": [
{
"type": "object",
"properties": {
"StringNewType": {
"type": "string"
}
},
"required": [
"StringNewType"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"StructVariant": {
"type": "object",
"properties": {
"floats": {
"type": "array",
"items": {
"type": "number",
"format": "float"
}
}
},
"required": [
"floats"
]
}
},
"required": [
"StructVariant"
],
"additionalProperties": false
}
]
}
}
}

View file

@ -1,34 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MyStruct",
"type": "object",
"properties": {
"unsigned": {
"type": "integer",
"format": "uint32",
"minimum": 0
},
"nonzero_unsigned": {
"type": "integer",
"format": "uint32",
"minimum": 1
},
"signed": {
"type": "integer",
"format": "int32"
},
"nonzero_signed": {
"type": "integer",
"format": "int32",
"not": {
"const": 0
}
}
},
"required": [
"unsigned",
"nonzero_unsigned",
"signed",
"nonzero_signed"
]
}

View file

@ -1,55 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "OsStrings",
"type": "object",
"properties": {
"owned": {
"$ref": "#/$defs/OsString"
},
"borrowed": {
"$ref": "#/$defs/OsString"
}
},
"required": [
"owned",
"borrowed"
],
"$defs": {
"OsString": {
"oneOf": [
{
"type": "object",
"properties": {
"Unix": {
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0
}
}
},
"required": [
"Unix"
]
},
{
"type": "object",
"properties": {
"Windows": {
"type": "array",
"items": {
"type": "integer",
"format": "uint16",
"minimum": 0
}
}
},
"required": [
"Windows"
]
}
]
}
}
}

View file

@ -1,89 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MyStruct",
"type": "object",
"properties": {
"range": {
"$ref": "#/$defs/Range_of_uint"
},
"inclusive": {
"$ref": "#/$defs/Range_of_double"
},
"bound": {
"$ref": "#/$defs/Bound_of_string"
}
},
"required": [
"range",
"inclusive",
"bound"
],
"$defs": {
"Range_of_uint": {
"type": "object",
"properties": {
"start": {
"type": "integer",
"format": "uint",
"minimum": 0
},
"end": {
"type": "integer",
"format": "uint",
"minimum": 0
}
},
"required": [
"start",
"end"
]
},
"Range_of_double": {
"type": "object",
"properties": {
"start": {
"type": "number",
"format": "double"
},
"end": {
"type": "number",
"format": "double"
}
},
"required": [
"start",
"end"
]
},
"Bound_of_string": {
"oneOf": [
{
"type": "object",
"properties": {
"Included": {
"type": "string"
}
},
"required": [
"Included"
]
},
{
"type": "object",
"properties": {
"Excluded": {
"type": "string"
}
},
"required": [
"Excluded"
]
},
{
"type": "string",
"const": "Unbounded"
}
]
}
}
}

View file

@ -1,86 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Container",
"type": "object",
"properties": {
"result1": {
"$ref": "#/$defs/Result_of_MyStruct_or_Array_of_string"
},
"result2": {
"$ref": "#/$defs/Result_of_boolean_or_null"
}
},
"required": [
"result1",
"result2"
],
"$defs": {
"Result_of_MyStruct_or_Array_of_string": {
"oneOf": [
{
"type": "object",
"properties": {
"Ok": {
"$ref": "#/$defs/MyStruct"
}
},
"required": [
"Ok"
]
},
{
"type": "object",
"properties": {
"Err": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"Err"
]
}
]
},
"MyStruct": {
"type": "object",
"properties": {
"foo": {
"type": "integer",
"format": "int32"
}
},
"required": [
"foo"
]
},
"Result_of_boolean_or_null": {
"oneOf": [
{
"type": "object",
"properties": {
"Ok": {
"type": "boolean"
}
},
"required": [
"Ok"
]
},
{
"type": "object",
"properties": {
"Err": {
"type": "null"
}
},
"required": [
"Err"
]
}
]
}
}
}

View file

@ -1,9 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Decimal",
"type": [
"string",
"number"
],
"pattern": "^-?[0-9]+(\\.[0-9]+)?([eE][0-9]+)?$"
}

View file

@ -1,47 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "a-new-name-Array_of_string-int32-int32",
"type": "object",
"properties": {
"t": {
"type": "integer",
"format": "int32"
},
"u": {
"type": "null"
},
"v": {
"type": "boolean"
},
"w": {
"type": "array",
"items": {
"type": "string"
}
},
"inner": {
"$ref": "#/$defs/another-new-name"
}
},
"required": [
"t",
"u",
"v",
"w",
"inner"
],
"$defs": {
"another-new-name": {
"type": "object",
"properties": {
"foo": {
"type": "integer",
"format": "int32"
}
},
"required": [
"foo"
]
}
}
}

View file

@ -1,47 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MyStruct_for_int32_and_null_and_boolean_and_Array_of_string",
"type": "object",
"properties": {
"t": {
"type": "integer",
"format": "int32"
},
"u": {
"type": "null"
},
"v": {
"type": "boolean"
},
"w": {
"type": "array",
"items": {
"type": "string"
}
},
"inner": {
"$ref": "#/$defs/MySimpleStruct"
}
},
"required": [
"t",
"u",
"v",
"w",
"inner"
],
"$defs": {
"MySimpleStruct": {
"type": "object",
"properties": {
"foo": {
"type": "integer",
"format": "int32"
}
},
"required": [
"foo"
]
}
}
}

View file

@ -1,63 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MixedGenericStruct_for_MyStruct_for_int32_and_null_and_boolean_and_Array_of_string_and_42_and_z",
"type": "object",
"properties": {
"generic": {
"$ref": "#/$defs/MyStruct_for_int32_and_null_and_boolean_and_Array_of_string"
},
"foo": {
"type": "integer",
"format": "int32"
}
},
"required": [
"generic",
"foo"
],
"$defs": {
"MyStruct_for_int32_and_null_and_boolean_and_Array_of_string": {
"type": "object",
"properties": {
"t": {
"type": "integer",
"format": "int32"
},
"u": {
"type": "null"
},
"v": {
"type": "boolean"
},
"w": {
"type": "array",
"items": {
"type": "string"
}
},
"inner": {
"$ref": "#/$defs/MySimpleStruct"
}
},
"required": [
"t",
"u",
"v",
"w",
"inner"
]
},
"MySimpleStruct": {
"type": "object",
"properties": {
"foo": {
"type": "integer",
"format": "int32"
}
},
"required": [
"foo"
]
}
}
}

View file

@ -1,89 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Adjacent",
"oneOf": [
{
"type": "object",
"properties": {
"t": {
"type": "string",
"const": "Struct"
},
"c": {
"type": "object",
"properties": {
"foo": {
"type": "boolean"
}
},
"required": [
"foo"
]
}
},
"required": [
"t",
"c"
]
},
{
"type": "object",
"properties": {
"t": {
"type": "string",
"const": "NewType"
},
"c": {
"type": "boolean"
}
},
"required": [
"t",
"c"
]
},
{
"type": "object",
"properties": {
"t": {
"type": "string",
"const": "Tuple"
},
"c": {
"type": "array",
"prefixItems": [
{
"type": "boolean"
},
{
"type": "integer",
"format": "int32"
}
],
"minItems": 2,
"maxItems": 2
}
},
"required": [
"t",
"c"
]
},
{
"type": "object",
"properties": {
"t": {
"type": "string",
"const": "Unit"
},
"c": {
"type": "boolean"
}
},
"required": [
"t",
"c"
]
}
]
}

View file

@ -1,46 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Internal",
"oneOf": [
{
"type": "object",
"properties": {
"foo": {
"type": "boolean"
},
"typeProperty": {
"type": "string",
"const": "Struct"
}
},
"required": [
"typeProperty",
"foo"
]
},
{
"type": "object",
"properties": {
"typeProperty": {
"type": "string",
"const": "NewType"
}
},
"required": [
"typeProperty"
]
},
{
"type": "object",
"properties": {
"typeProperty": {
"type": "string",
"const": "Unit"
}
},
"required": [
"typeProperty"
]
}
]
}

View file

@ -1,37 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Untagged",
"anyOf": [
{
"type": "object",
"properties": {
"foo": {
"type": "boolean"
}
},
"required": [
"foo"
]
},
{
"type": "boolean"
},
{
"type": "array",
"prefixItems": [
{
"type": "boolean"
},
{
"type": "integer",
"format": "int32"
}
],
"minItems": 2,
"maxItems": 2
},
{
"type": "boolean"
}
]
}

View file

@ -1,5 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Newtype",
"type": "boolean"
}

View file

@ -1,5 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "schema_fn",
"type": "boolean"
}

View file

@ -1,19 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Tuple",
"type": "array",
"prefixItems": [
{
"type": "boolean"
},
{
"type": "integer",
"format": "int32"
},
{
"type": "boolean"
}
],
"minItems": 3,
"maxItems": 3
}

View file

@ -1,14 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "SemverTypes",
"type": "object",
"properties": {
"version": {
"type": "string",
"pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"
}
},
"required": [
"version"
]
}

View file

@ -1,25 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MyEnum",
"oneOf": [
{
"type": "string",
"enum": [
"Included2"
]
},
{
"type": "object",
"properties": {
"Included1": {
"type": "number",
"format": "float"
}
},
"required": [
"Included1"
],
"additionalProperties": false
}
]
}

View file

@ -1,19 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MyStruct",
"type": "object",
"properties": {
"writable": {
"type": "number",
"format": "float",
"writeOnly": true
},
"included": {
"type": "null"
}
},
"required": [
"writable",
"included"
]
}

View file

@ -1,6 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Newtype",
"type": "integer",
"format": "int32"
}

View file

@ -1,25 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Struct",
"type": "object",
"properties": {
"foo": {
"type": "integer",
"format": "int32"
},
"bar": {
"type": "boolean"
},
"baz": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false,
"required": [
"foo",
"bar"
]
}

View file

@ -1,22 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Tuple",
"type": "array",
"prefixItems": [
{
"type": "integer",
"format": "int32"
},
{
"type": "boolean"
},
{
"type": [
"string",
"null"
]
}
],
"minItems": 3,
"maxItems": 3
}

View file

@ -1,52 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Tuple_of_OuterAllowUnknownFields_and_MiddleDenyUnknownFields",
"type": "array",
"prefixItems": [
{
"$ref": "#/$defs/OuterAllowUnknownFields"
},
{
"$ref": "#/$defs/MiddleDenyUnknownFields"
}
],
"minItems": 2,
"maxItems": 2,
"$defs": {
"OuterAllowUnknownFields": {
"type": "object",
"properties": {
"outer_field": {
"type": "boolean"
},
"middle_field": {
"type": "boolean"
},
"inner_field": {
"type": "boolean"
}
},
"required": [
"outer_field",
"middle_field",
"inner_field"
]
},
"MiddleDenyUnknownFields": {
"type": "object",
"properties": {
"middle_field": {
"type": "boolean"
},
"inner_field": {
"type": "boolean"
}
},
"additionalProperties": false,
"required": [
"middle_field",
"inner_field"
]
}
}
}

View file

@ -1,33 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "OuterStruct",
"type": "object",
"properties": {
"inner": {
"anyOf": [
{
"$ref": "#/$defs/InnerStruct"
},
{
"type": "null"
}
]
}
},
"$defs": {
"InnerStruct": {
"type": "array",
"prefixItems": [
{
"type": "string"
},
{
"type": "integer",
"format": "int32"
}
],
"minItems": 2,
"maxItems": 2
}
}
}

View file

@ -1,84 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Struct",
"type": "object",
"properties": {
"min_max": {
"type": "number",
"format": "float",
"minimum": 0.01,
"maximum": 100
},
"min_max2": {
"type": "number",
"format": "float",
"minimum": 1,
"maximum": 1000
},
"regex_str1": {
"type": "string",
"pattern": "^[Hh]ello\\b"
},
"regex_str2": {
"type": "string",
"pattern": "^[Hh]ello\\b"
},
"contains_str1": {
"type": "string",
"pattern": "substring\\.\\.\\."
},
"contains_str2": {
"type": "string",
"pattern": "substring\\.\\.\\."
},
"email_address": {
"type": "string",
"format": "email"
},
"homepage": {
"type": "string",
"format": "uri"
},
"non_empty_str": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"non_empty_str2": {
"type": "string",
"minLength": 1,
"maxLength": 1000
},
"pair": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"minItems": 2,
"maxItems": 2
},
"required_option": {
"type": "boolean"
},
"x": {
"type": "integer",
"format": "int32"
}
},
"required": [
"min_max",
"min_max2",
"regex_str1",
"regex_str2",
"contains_str1",
"contains_str2",
"email_address",
"homepage",
"non_empty_str",
"non_empty_str2",
"pair",
"required_option",
"x"
]
}

View file

@ -1,74 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Struct",
"type": "object",
"properties": {
"array_str_length": {
"type": "array",
"items": {
"type": "string",
"minLength": 5,
"maxLength": 100
},
"minItems": 2,
"maxItems": 2
},
"slice_str_contains": {
"type": "array",
"items": {
"type": "string",
"pattern": "substring\\.\\.\\."
}
},
"vec_str_regex": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[Hh]ello\\b"
}
},
"vec_str_length": {
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 100
}
},
"vec_str_length2": {
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"minItems": 1,
"maxItems": 3
},
"vec_str_url": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
}
},
"vec_i32_range": {
"type": "array",
"items": {
"type": "integer",
"format": "int32",
"minimum": -10,
"maximum": 10
}
}
},
"required": [
"array_str_length",
"slice_str_contains",
"vec_str_regex",
"vec_str_length",
"vec_str_length2",
"vec_str_url",
"vec_i32_range"
]
}

View file

@ -1,8 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "NewType",
"type": "integer",
"format": "uint8",
"minimum": 0,
"maximum": 10
}

View file

@ -1,84 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Struct2",
"type": "object",
"properties": {
"min_max": {
"type": "number",
"format": "float",
"minimum": 0.01,
"maximum": 100
},
"min_max2": {
"type": "number",
"format": "float",
"minimum": 1,
"maximum": 1000
},
"regex_str1": {
"type": "string",
"pattern": "^[Hh]ello\\b"
},
"regex_str2": {
"type": "string",
"pattern": "^\\d+$"
},
"contains_str1": {
"type": "string",
"pattern": "substring\\.\\.\\."
},
"contains_str2": {
"type": "string",
"pattern": "substring\\.\\.\\."
},
"email_address": {
"type": "string",
"format": "email"
},
"homepage": {
"type": "string",
"format": "uri"
},
"non_empty_str": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"non_empty_str2": {
"type": "string",
"minLength": 1,
"maxLength": 1000
},
"pair": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"minItems": 2,
"maxItems": 2
},
"required_option": {
"type": "boolean"
},
"x": {
"type": "integer",
"format": "int32"
}
},
"required": [
"min_max",
"min_max2",
"regex_str1",
"regex_str2",
"contains_str1",
"contains_str2",
"email_address",
"homepage",
"non_empty_str",
"non_empty_str2",
"pair",
"required_option",
"x"
]
}

View file

@ -1,18 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Tuple",
"type": "array",
"prefixItems": [
{
"type": "integer",
"format": "uint8",
"minimum": 0,
"maximum": 10
},
{
"type": "boolean"
}
],
"minItems": 2,
"maxItems": 2
}

View file

@ -1,96 +0,0 @@
mod util;
use schemars::JsonSchema;
use serde_json::Value;
use util::*;
const THREE: f64 = 3.0;
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(extend("msg" = concat!("hello ", "world"), "obj" = {"array": [null, ()]}))]
#[schemars(extend("3" = THREE), extend("pi" = THREE + 0.14))]
struct Struct {
#[schemars(extend("foo" = "bar"))]
value: Value,
#[schemars(extend("type" = "overridden"))]
int: i32,
}
#[test]
fn extend_struct() -> TestResult {
test_default_generated_schema::<Struct>("extend_struct")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(extend("foo" = "bar"))]
enum External {
#[schemars(extend("foo" = "bar"))]
Unit,
#[schemars(extend("foo" = "bar"))]
NewType(Value),
#[schemars(extend("foo" = "bar"))]
Tuple(i32, bool),
#[schemars(extend("foo" = "bar"))]
Struct { i: i32, b: bool },
}
#[test]
fn extend_enum_external() -> TestResult {
test_default_generated_schema::<External>("extend_enum_external")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(tag = "typeProperty", extend("foo" = "bar"))]
enum Internal {
#[schemars(extend("foo" = "bar"))]
Unit,
#[schemars(extend("foo" = "bar"))]
NewType(Value),
#[schemars(extend("foo" = "bar"))]
Struct { i: i32, b: bool },
}
#[test]
fn extend_enum_internal() -> TestResult {
test_default_generated_schema::<Internal>("extend_enum_internal")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(untagged, extend("foo" = "bar"))]
enum Untagged {
#[schemars(extend("foo" = "bar"))]
Unit,
#[schemars(extend("foo" = "bar"))]
NewType(Value),
#[schemars(extend("foo" = "bar"))]
Tuple(i32, bool),
#[schemars(extend("foo" = "bar"))]
Struct { i: i32, b: bool },
}
#[test]
fn extend_enum_untagged() -> TestResult {
test_default_generated_schema::<Untagged>("extend_enum_untagged")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(tag = "t", content = "c", extend("foo" = "bar"))]
enum Adjacent {
#[schemars(extend("foo" = "bar"))]
Unit,
#[schemars(extend("foo" = "bar"))]
NewType(Value),
#[schemars(extend("foo" = "bar"))]
Tuple(i32, bool),
#[schemars(extend("foo" = "bar"))]
Struct { i: i32, b: bool },
}
#[test]
fn extend_enum_adjacent() -> TestResult {
test_default_generated_schema::<Adjacent>("extend_enum_adjacent")
}

View file

@ -1,16 +0,0 @@
mod util;
use schemars::JsonSchema;
use std::ffi::{OsStr, OsString};
use util::*;
#[allow(dead_code)]
#[derive(JsonSchema)]
struct OsStrings {
owned: OsString,
borrowed: &'static OsStr,
}
#[test]
fn os_strings() -> TestResult {
test_default_generated_schema::<OsStrings>("os_strings")
}

View file

@ -1,115 +0,0 @@
mod util;
use schemars::JsonSchema;
use serde_json::Value;
use std::collections::BTreeMap;
use util::*;
#[allow(dead_code)]
#[derive(JsonSchema)]
struct Flat {
f: f32,
b: bool,
s: String,
#[serde(default)]
os: String,
v: Vec<i32>,
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(rename = "Flat")]
struct Deep1 {
f: f32,
#[schemars(flatten)]
deep2: Deep2,
v: Vec<i32>,
}
#[allow(clippy::option_option, dead_code)]
#[derive(JsonSchema)]
struct Deep2 {
b: bool,
#[serde(flatten)]
deep3: Deep3,
#[serde(flatten)]
deep4: Box<Option<Option<Box<Deep4>>>>,
}
#[allow(dead_code)]
#[derive(JsonSchema)]
struct Deep3 {
s: &'static str,
}
#[allow(dead_code)]
#[derive(JsonSchema)]
struct Deep4 {
#[serde(default)]
os: &'static str,
}
#[test]
fn test_flat_schema() -> TestResult {
test_default_generated_schema::<Flat>("flatten")
}
#[test]
fn test_flattened_schema() -> TestResult {
// intentionally using the same file as test_flat_schema, as the schema should be identical
test_default_generated_schema::<Deep1>("flatten")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
struct FlattenValue {
flag: bool,
#[serde(flatten)]
value: Value,
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(rename = "FlattenValue")]
struct FlattenMap {
flag: bool,
#[serde(flatten)]
value: BTreeMap<String, Value>,
}
#[test]
fn test_flattened_value() -> TestResult {
test_default_generated_schema::<FlattenValue>("flattened_value")
}
#[test]
fn test_flattened_map() -> TestResult {
// intentionally using the same file as test_flattened_value, as the schema should be identical
test_default_generated_schema::<FlattenMap>("flattened_value")
}
#[derive(JsonSchema)]
pub struct OuterAllowUnknownFields {
pub outer_field: bool,
#[serde(flatten)]
pub middle: MiddleDenyUnknownFields,
}
#[derive(JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MiddleDenyUnknownFields {
pub middle_field: bool,
#[serde(flatten)]
pub inner: InnerAllowUnknownFields,
}
#[derive(JsonSchema)]
pub struct InnerAllowUnknownFields {
pub inner_field: bool,
}
#[test]
fn test_flattened_struct_deny_unknown_fields() -> TestResult {
test_default_generated_schema::<(OuterAllowUnknownFields, MiddleDenyUnknownFields)>(
"test_flattened_struct_deny_unknown_fields",
)
}

View file

@ -1,94 +0,0 @@
mod util;
use schemars::generate::{SchemaGenerator, SchemaSettings};
use serde::Serialize;
use std::collections::HashMap;
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 generator = SchemaSettings::draft07().into_generator();
let actual = generator.into_root_schema_for_value(&make_value())?;
test_schema(&actual, "from_value_draft07")
}
#[test]
fn schema_from_value_matches_2019_09() -> TestResult {
let generator = SchemaSettings::draft2019_09().into_generator();
let actual = generator.into_root_schema_for_value(&make_value())?;
test_schema(&actual, "from_value_2019_09")
}
#[test]
fn schema_from_value_matches_openapi3() -> TestResult {
let generator = SchemaSettings::openapi3().into_generator();
let actual = generator.into_root_schema_for_value(&make_value())?;
test_schema(&actual, "from_value_openapi3")
}
#[test]
fn schema_from_json_value() -> TestResult {
let generator = SchemaGenerator::default();
let actual = generator.into_root_schema_for_value(&serde_json::json!({
"zero": 0,
"one": 1,
"minusOne": -1,
"zeroPointZero": 0.0,
"bool": true,
"null": null,
"object": {
"array": ["foo", "bar"]
},
}))?;
test_schema(&actual, "from_json_value")
}

View file

@ -1,99 +0,0 @@
mod util;
use schemars::JsonSchema;
use util::*;
const MIN: u32 = 1;
const MAX: u32 = 1000;
#[allow(dead_code)]
#[derive(JsonSchema)]
pub struct Struct {
#[garde(range(min = 0.01, max = 100))]
min_max: f32,
#[garde(range(min = MIN, max = MAX))]
min_max2: f32,
#[garde(pattern(r"^[Hh]ello\b"))]
regex_str1: String,
#[garde(contains(concat!("sub","string...")))]
contains_str1: String,
#[garde(email)]
email_address: String,
#[garde(url)]
homepage: String,
#[garde(length(min = 1, max = 100))]
non_empty_str: String,
#[garde(length(min = MIN, max = MAX))]
non_empty_str2: String,
#[garde(length(equal = 2))]
pair: Vec<i32>,
#[garde(required)]
required_option: Option<bool>,
#[garde(required)]
#[serde(flatten)]
required_flattened: Option<Inner>,
}
#[allow(dead_code)]
#[derive(JsonSchema)]
pub struct Inner {
x: i32,
}
#[test]
fn garde() -> TestResult {
test_default_generated_schema::<Struct>("garde")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
pub struct Struct2 {
#[schemars(range(min = 0.01, max = 100))]
min_max: f32,
#[schemars(range(min = MIN, max = MAX))]
min_max2: f32,
#[schemars(pattern(r"^[Hh]ello\b"))]
regex_str1: String,
#[schemars(contains(concat!("sub","string...")))]
contains_str1: String,
#[schemars(email)]
email_address: String,
#[schemars(url)]
homepage: String,
#[schemars(length(min = 1, max = 100))]
non_empty_str: String,
#[schemars(length(min = MIN, max = MAX))]
non_empty_str2: String,
#[schemars(length(equal = 2))]
pair: Vec<i32>,
#[schemars(required)]
required_option: Option<bool>,
#[schemars(required)]
#[serde(flatten)]
required_flattened: Option<Inner>,
}
#[test]
fn garde_schemars_attrs() -> TestResult {
test_default_generated_schema::<Struct2>("garde_schemars_attrs")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
pub struct Tuple(
#[garde(range(max = 10))] u8,
#[garde(required)] Option<bool>,
);
#[test]
fn garde_tuple() -> TestResult {
test_default_generated_schema::<Tuple>("garde_tuple")
}
#[allow(dead_code)]
#[derive(JsonSchema)]
pub struct NewType(#[garde(range(max = 10))] u8);
#[test]
fn garde_newtype() -> TestResult {
test_default_generated_schema::<NewType>("garde_newtype")
}

View file

@ -1,18 +0,0 @@
mod util;
use std::collections::hash_map::RandomState;
use indexmap2::{IndexMap, IndexSet};
use schemars::JsonSchema;
use util::*;
#[allow(dead_code)]
#[derive(JsonSchema)]
struct IndexMapTypes {
map: IndexMap<i32, bool, RandomState>,
set: IndexSet<isize, RandomState>,
}
#[test]
fn indexmap_types() -> TestResult {
test_default_generated_schema::<IndexMapTypes>("indexmap")
}

View file

@ -1,43 +0,0 @@
mod util;
use schemars::generate::SchemaSettings;
use schemars::JsonSchema;
use util::*;
#[allow(dead_code)]
#[derive(JsonSchema)]
struct MyJob {
spec: MyJobSpec,
}
#[allow(dead_code)]
#[derive(JsonSchema)]
struct MyJobSpec {
replicas: u32,
}
#[test]
fn struct_normal() -> TestResult {
let mut settings = SchemaSettings::default();
settings.inline_subschemas = true;
test_generated_schema::<MyJob>("inline-subschemas", settings)
}
#[allow(dead_code)]
#[derive(JsonSchema)]
struct RecursiveOuter {
direct: Option<Box<RecursiveOuter>>,
indirect: Option<Box<RecursiveInner>>,
}
#[allow(dead_code)]
#[derive(JsonSchema)]
struct RecursiveInner {
recursive: RecursiveOuter,
}
#[test]
fn struct_recursive() -> TestResult {
let mut settings = SchemaSettings::default();
settings.inline_subschemas = true;
test_generated_schema::<RecursiveOuter>("inline-subschemas-recursive", settings)
}

View file

@ -0,0 +1,36 @@
use crate::prelude::*;
use arrayvec07::{ArrayString, ArrayVec};
#[test]
fn arrayvec07() {
test!(ArrayVec<i32, 8>)
.assert_snapshot()
.assert_allows_ser_roundtrip([
ArrayVec::from_iter([]),
ArrayVec::from_iter([1, 2, 3, 4, 5, 6, 7, 8]),
])
.assert_matches_de_roundtrip(
(0..16).map(|len| Value::Array((0..len).map(Value::from).collect())),
)
.assert_matches_de_roundtrip(arbitrary_values_except(
is_array_of_u64,
"FIXME schema allows out-of-range positive integers",
));
}
#[test]
fn arrayvec07_arraystring() {
test!(ArrayString<8>)
.assert_identical::<String>()
.assert_allows_ser_roundtrip(["".try_into().unwrap(), "12345678".try_into().unwrap()])
.assert_matches_de_roundtrip(arbitrary_values_except(
Value::is_string,
"There's not a good way to express UTF-8 byte length in JSON schema, so schema ignores the ArrayString's capacity.",
));
}
fn is_array_of_u64(value: &Value) -> bool {
value
.as_array()
.is_some_and(|a| a.iter().all(Value::is_u64))
}

View file

@ -1,9 +1,6 @@
mod util; use crate::prelude::*;
use std::marker::PhantomData; use std::marker::PhantomData;
use schemars::JsonSchema;
use util::*;
struct MyIterator; struct MyIterator;
impl Iterator for MyIterator { impl Iterator for MyIterator {
@ -16,7 +13,7 @@ impl Iterator for MyIterator {
// The default trait bounds would require T to implement JsonSchema, // The default trait bounds would require T to implement JsonSchema,
// which MyIterator does not. // which MyIterator does not.
#[derive(JsonSchema)] #[derive(JsonSchema, Serialize, Deserialize)]
#[schemars(bound = "T::Item: JsonSchema", rename = "MyContainer")] #[schemars(bound = "T::Item: JsonSchema", rename = "MyContainer")]
pub struct MyContainer<T> pub struct MyContainer<T>
where where
@ -27,6 +24,12 @@ where
} }
#[test] #[test]
fn manual_bound_set() -> TestResult { fn manual_bound_set() {
test_default_generated_schema::<MyContainer<MyIterator>>("bound") test!(MyContainer<MyIterator>)
.assert_snapshot()
.assert_allows_ser_roundtrip([MyContainer {
associated: "test".to_owned(),
generic: PhantomData,
}])
.assert_matches_de_roundtrip(arbitrary_values());
} }

View file

@ -0,0 +1,24 @@
use crate::prelude::*;
use bytes1::{Bytes, BytesMut};
#[test]
fn bytes() {
test!(Bytes)
.assert_snapshot()
.assert_allows_ser_roundtrip([Bytes::new(), Bytes::from_iter([12; 34])])
.assert_matches_de_roundtrip(arbitrary_values_except(
is_array_of_u64,
"FIXME schema allows out-of-range positive integers",
));
}
#[test]
fn bytes_mut() {
test!(BytesMut).assert_identical::<Bytes>();
}
fn is_array_of_u64(value: &Value) -> bool {
value
.as_array()
.is_some_and(|a| a.iter().all(Value::is_u64))
}

View file

@ -0,0 +1,42 @@
use crate::prelude::*;
use chrono04::prelude::*;
#[derive(JsonSchema, Serialize, Deserialize)]
struct ChronoTypes {
weekday: Weekday,
date_time: DateTime<Utc>,
naive_date: NaiveDate,
naive_date_time: NaiveDateTime,
naive_time: NaiveTime,
}
#[test]
fn chrono() {
test!(ChronoTypes).assert_snapshot();
test!(Weekday)
.assert_allows_ser_roundtrip([Weekday::Mon])
.assert_matches_de_roundtrip(arbitrary_values());
test!(DateTime<Utc>)
.assert_allows_ser_roundtrip_default()
.assert_matches_de_roundtrip(arbitrary_values());
test!(NaiveDate)
.assert_allows_ser_roundtrip_default()
.assert_matches_de_roundtrip(arbitrary_values());
test!(NaiveDateTime)
.assert_allows_ser_roundtrip_default()
.assert_matches_de_roundtrip(arbitrary_values_except(
Value::is_string,
"Custom format 'partial-date-time', so arbitrary strings technically allowed by schema",
));
test!(NaiveTime)
.assert_allows_ser_roundtrip_default()
.assert_matches_de_roundtrip(arbitrary_values_except(
Value::is_string,
"Custom format 'date-time', so arbitrary strings technically allowed by schema",
));
}

View file

@ -0,0 +1,254 @@
use crate::prelude::*;
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(rename_all(serialize = "SCREAMING-KEBAB-CASE"), deny_unknown_fields)]
struct StructDenyUnknownFields {
#[serde(skip_deserializing)]
read_only: bool,
#[allow(dead_code)]
#[serde(skip_serializing)]
write_only: bool,
#[serde(default)]
default: bool,
#[serde(skip_serializing_if = "core::ops::Not::not")]
skip_serializing_if: bool,
#[serde(rename(serialize = "ser_renamed", deserialize = "de_renamed"))]
renamed: bool,
option: Option<bool>,
}
#[derive(JsonSchema, Deserialize, Serialize)]
struct StructAllowUnknownFields {
#[serde(flatten)]
inner: StructDenyUnknownFields,
}
#[test]
fn struct_deny_unknown_fields() {
test!(StructDenyUnknownFields)
.assert_snapshot()
.assert_allows_de_roundtrip([
json!({ "write_only": false, "skip_serializing_if": false, "de_renamed": false }),
json!({ "write_only": true, "skip_serializing_if": true, "de_renamed": true, "default": true }),
json!({ "write_only": true, "skip_serializing_if": true, "de_renamed": true, "option": true }),
])
.assert_rejects_de([
json!({ "skip_serializing_if": false, "de_renamed": false }),
json!({ "write_only": false, "de_renamed": false }),
json!({ "write_only": false, "skip_serializing_if": false }),
json!({ "write_only": true, "skip_serializing_if": true, "de_renamed": true, "unknown": true }),
])
.assert_matches_de_roundtrip(arbitrary_values());
}
#[test]
fn struct_allow_unknown_fields() {
test!(StructAllowUnknownFields)
.assert_snapshot()
.assert_allows_de_roundtrip([
json!({ "write_only": false, "skip_serializing_if": false, "de_renamed": false }),
json!({ "write_only": true, "skip_serializing_if": true, "de_renamed": true, "default": true }),
json!({ "write_only": true, "skip_serializing_if": true, "de_renamed": true, "option": true }),
json!({ "write_only": true, "skip_serializing_if": true, "de_renamed": true, "unknown": true }),
])
.assert_rejects_de([
json!({ "skip_serializing_if": false, "de_renamed": false }),
json!({ "write_only": false, "de_renamed": false }),
json!({ "write_only": false, "skip_serializing_if": false }),
])
.assert_matches_de_roundtrip(arbitrary_values());
}
#[derive(JsonSchema, Deserialize, Serialize)]
struct TupleStruct(
String,
#[allow(dead_code)]
#[serde(skip_serializing)]
bool,
String,
#[serde(skip_deserializing)] bool,
String,
);
#[test]
fn tuple_struct() {
test!(TupleStruct)
.assert_snapshot()
.assert_allows_de_roundtrip([json!(["", true, "", ""])])
.assert_matches_de_roundtrip(arbitrary_values());
}
#[allow(dead_code)]
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(
rename_all(serialize = "SCREAMING-KEBAB-CASE"),
rename_all_fields(serialize = "PascalCase")
)]
enum ExternalEnum {
#[serde(skip_deserializing)]
ReadOnlyUnit,
#[serde(skip_serializing)]
WriteOnlyUnit,
#[serde(skip_deserializing)]
ReadOnlyStruct { s: String },
#[serde(skip_serializing)]
WriteOnlyStruct { i: isize },
#[serde(rename(serialize = "ser_renamed_unit", deserialize = "de_renamed_unit"))]
RenamedUnit,
#[serde(rename(serialize = "ser_renamed_struct", deserialize = "de_renamed_struct"))]
RenamedStruct { b: bool },
}
#[test]
fn externally_tagged_enum() {
test!(ExternalEnum)
.assert_snapshot()
.assert_allows_ser_roundtrip([
ExternalEnum::ReadOnlyUnit,
ExternalEnum::ReadOnlyStruct { s: "test".into() },
ExternalEnum::RenamedUnit,
ExternalEnum::RenamedStruct { b: true },
])
.assert_allows_de_roundtrip([
json!("WriteOnlyUnit"),
json!({ "WriteOnlyStruct": { "i": 123 } }),
json!("de_renamed_unit"),
json!({ "de_renamed_struct": { "b": true } }),
])
.assert_rejects_de([
json!("READ-ONLY-UNIT"),
json!("ReadOnlyUnit"),
json!("ser_renamed_unit"),
])
.assert_matches_de_roundtrip(arbitrary_values());
}
#[allow(dead_code)]
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(
tag = "tag",
rename_all(serialize = "SCREAMING-KEBAB-CASE"),
rename_all_fields(serialize = "PascalCase")
)]
enum InternalEnum {
#[serde(skip_deserializing)]
ReadOnlyUnit,
#[serde(skip_serializing)]
WriteOnlyUnit,
#[serde(skip_deserializing)]
ReadOnlyStruct { s: String },
#[serde(skip_serializing)]
WriteOnlyStruct { i: isize },
#[serde(rename(serialize = "ser_renamed_unit", deserialize = "de_renamed_unit"))]
RenamedUnit,
#[serde(rename(serialize = "ser_renamed_struct", deserialize = "de_renamed_struct"))]
RenamedStruct { b: bool },
}
#[test]
fn internally_tagged_enum() {
test!(InternalEnum)
.assert_snapshot()
.assert_allows_ser_roundtrip([
InternalEnum::ReadOnlyUnit,
InternalEnum::ReadOnlyStruct { s: "test".into() },
InternalEnum::RenamedUnit,
InternalEnum::RenamedStruct { b: true },
])
.assert_allows_de_roundtrip([
json!({ "tag": "WriteOnlyUnit" }),
json!({ "tag": "WriteOnlyStruct", "i": 123 }),
json!({ "tag": "de_renamed_unit" }),
json!({ "tag": "de_renamed_struct", "b": true }),
])
.assert_rejects_de([
json!({ "tag": "READ-ONLY-UNIT" }),
json!({ "tag": "ReadOnlyUnit" }),
json!({ "tag": "ser_renamed_unit" }),
])
.assert_matches_de_roundtrip(arbitrary_values());
}
#[allow(dead_code)]
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(
tag = "tag",
content = "content",
rename_all(serialize = "SCREAMING-KEBAB-CASE"),
rename_all_fields(serialize = "PascalCase")
)]
enum AdjacentEnum {
#[serde(skip_deserializing)]
ReadOnlyUnit,
#[serde(skip_serializing)]
WriteOnlyUnit,
#[serde(skip_deserializing)]
ReadOnlyStruct { s: String },
#[serde(skip_serializing)]
WriteOnlyStruct { i: isize },
#[serde(rename(serialize = "ser_renamed_unit", deserialize = "de_renamed_unit"))]
RenamedUnit,
#[serde(rename(serialize = "ser_renamed_struct", deserialize = "de_renamed_struct"))]
RenamedStruct { b: bool },
}
#[test]
fn adjacently_tagged_enum() {
test!(AdjacentEnum)
.assert_snapshot()
.assert_allows_ser_roundtrip([
AdjacentEnum::ReadOnlyUnit,
AdjacentEnum::ReadOnlyStruct { s: "test".into() },
AdjacentEnum::RenamedUnit,
AdjacentEnum::RenamedStruct { b: true },
])
.assert_allows_de_roundtrip([
json!({ "tag": "WriteOnlyUnit" }),
json!({ "tag": "WriteOnlyStruct", "content": { "i": 123 } }),
json!({ "tag": "de_renamed_unit" }),
json!({ "tag": "de_renamed_struct", "content": { "b": true } }),
])
.assert_rejects_de([
json!({ "tag": "READ-ONLY-UNIT" }),
json!({ "tag": "ReadOnlyUnit" }),
json!({ "tag": "ser_renamed_unit" }),
])
.assert_matches_de_roundtrip(arbitrary_values());
}
#[allow(dead_code)]
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(
untagged,
rename_all(serialize = "SCREAMING-KEBAB-CASE"),
rename_all_fields(serialize = "PascalCase")
)]
enum UntaggedEnum {
#[serde(skip_deserializing)]
ReadOnlyUnit,
#[serde(skip_serializing)]
WriteOnlyUnit,
#[serde(skip_deserializing)]
ReadOnlyStruct { s: String },
#[serde(skip_serializing)]
WriteOnlyStruct { i: isize },
#[serde(rename(serialize = "ser_renamed_unit", deserialize = "de_renamed_unit"))]
RenamedUnit,
#[serde(rename(serialize = "ser_renamed_struct", deserialize = "de_renamed_struct"))]
RenamedStruct { b: bool },
}
#[test]
fn untagged_enum() {
test!(UntaggedEnum)
.assert_snapshot()
.assert_allows_ser_roundtrip([
UntaggedEnum::ReadOnlyUnit,
UntaggedEnum::ReadOnlyStruct { s: "test".into() },
UntaggedEnum::RenamedUnit,
UntaggedEnum::RenamedStruct { b: true },
])
.assert_allows_de_roundtrip([json!(null), json!({ "i": 123 }), json!({ "b": true })])
.assert_rejects_de([json!({ "s": "test" })])
.assert_matches_de_roundtrip(arbitrary_values());
}

View file

@ -0,0 +1,19 @@
use crate::prelude::*;
use ::schemars as aliased_schemars;
#[allow(dead_code)]
#[derive(aliased_schemars::JsonSchema, Deserialize, Serialize, Default)]
#[schemars(crate = "aliased_schemars")]
struct MyStruct {
/// Is it ok with doc comments?
foo: i32,
#[schemars(extend("x-test" = "...and extensions?"))]
bar: bool,
}
#[test]
fn crate_alias() {
test!(MyStruct)
.assert_allows_ser_roundtrip_default()
.assert_matches_de_roundtrip(arbitrary_values());
}

View file

@ -0,0 +1,19 @@
use crate::prelude::*;
#[test]
fn decimal_types() {
#[cfg(feature = "rust_decimal1")]
test!(rust_decimal1::Decimal)
.assert_snapshot()
.assert_allows_ser_roundtrip_default()
.assert_matches_de_roundtrip(arbitrary_values());
#[cfg(feature = "bigdecimal04")]
test!(bigdecimal04::BigDecimal)
.assert_snapshot()
.assert_allows_ser_roundtrip_default()
.assert_matches_de_roundtrip(arbitrary_values());
#[cfg(all(feature = "rust_decimal1", feature = "bigdecimal04"))]
test!(bigdecimal04::BigDecimal).assert_identical::<rust_decimal1::Decimal>();
}

View file

@ -0,0 +1,95 @@
use crate::prelude::*;
#[derive(JsonSchema, Deserialize, Serialize, Default)]
#[serde(default)]
struct MyStruct {
integer: u32,
boolean: bool,
option_string: Option<String>,
#[serde(skip_serializing_if = "str::is_empty")]
string_skip_empty: String,
#[serde(with = "struct_2_as_str")]
#[schemars(with = "str", pattern(r"^\d+ (true|false)$"))]
struct2: MyStruct2,
#[serde(skip_serializing)]
not_serialize: NotSerialize,
}
#[derive(JsonSchema, Deserialize, Serialize, Default)]
#[serde(default = "ten_and_true")]
struct MyStruct2 {
#[serde(default = "six")]
integer: u32,
boolean: bool,
}
#[allow(dead_code)]
#[derive(JsonSchema, Deserialize, Default)]
struct NotSerialize(i8);
mod struct_2_as_str {
use super::MyStruct2;
pub(super) fn serialize<S>(value: &MyStruct2, ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
ser.collect_str(&format_args!("{} {}", value.integer, value.boolean))
}
pub(super) fn deserialize<'de, D>(deser: D) -> Result<MyStruct2, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Deserialize, Error};
let error = || Error::custom("invalid string");
let (i, b) = <&str>::deserialize(deser)?
.split_once(' ')
.ok_or_else(error)?;
Ok(MyStruct2 {
integer: i.parse().map_err(|_| error())?,
boolean: b.parse().map_err(|_| error())?,
})
}
}
fn ten_and_true() -> MyStruct2 {
MyStruct2 {
integer: 10,
boolean: true,
}
}
fn six() -> u32 {
6
}
#[test]
fn default_fields() {
test!(MyStruct)
.assert_snapshot()
.assert_allows_ser_roundtrip([
MyStruct::default(),
MyStruct {
integer: 123,
boolean: true,
option_string: Some("test".into()),
string_skip_empty: "test".into(),
struct2: ten_and_true(),
not_serialize: NotSerialize(42),
},
])
.assert_allows_de_roundtrip([
json!({}),
json!({ "not_serialize": 127 })
])
.assert_rejects_de([
json!({ "not_serialize": "a string" })
])
.assert_matches_de_roundtrip(arbitrary_values_except(
Value::is_array,
"structs with `#derive(Deserialize)` can technically be deserialized from sequences, but that's not intended to be used via JSON, so schemars ignores it",
));
}

View file

@ -0,0 +1,69 @@
#![allow(deprecated)]
use crate::prelude::*;
#[derive(JsonSchema, Default, Serialize, Deserialize)]
#[deprecated]
struct DeprecatedStruct {
foo: i32,
#[deprecated]
bar: bool,
}
#[allow(deprecated)]
#[test]
fn deprecated_struct() {
test!(DeprecatedStruct)
.assert_snapshot()
.assert_allows_ser_roundtrip_default()
.custom(|schema, _| {
assert_eq!(
schema.as_value().pointer("/deprecated"),
Some(&Value::Bool(true)),
);
assert_eq!(
schema.as_value().pointer("/properties/bar/deprecated"),
Some(&Value::Bool(true)),
);
});
}
#[derive(JsonSchema, Default, Serialize, Deserialize)]
#[deprecated]
enum DeprecatedEnum {
#[default]
Unit,
#[deprecated]
DeprecatedUnitVariant,
#[deprecated]
DeprecatedStructVariant {
foo: i32,
#[deprecated]
deprecated_field: bool,
},
}
#[test]
fn deprecated_enum() {
test!(DeprecatedEnum)
.assert_snapshot()
.assert_allows_ser_roundtrip_default()
.custom(|schema, _| {
assert_eq!(
schema.as_value().pointer("/deprecated"),
Some(&Value::Bool(true)),
);
assert_eq!(
schema.as_value().pointer("/oneOf/1/deprecated"),
Some(&Value::Bool(true)),
);
assert_eq!(
schema.as_value().pointer("/oneOf/2/deprecated"),
Some(&Value::Bool(true)),
);
assert_eq!(
schema.as_value().pointer("/oneOf/2/properties/DeprecatedStructVariant/properties/deprecated_field/deprecated"),
Some(&Value::Bool(true)),
);
});
}

View file

@ -1,6 +1,4 @@
mod util; use crate::prelude::*;
use schemars::JsonSchema;
use util::*;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(JsonSchema)] #[derive(JsonSchema)]
@ -24,6 +22,11 @@ struct MyStruct {
#[derive(JsonSchema)] #[derive(JsonSchema)]
struct MyUnitStruct; struct MyUnitStruct;
#[test]
fn doc_comments_struct() {
test!(MyStruct).assert_snapshot();
}
#[allow(dead_code)] #[allow(dead_code)]
#[doc = " # This is the enum's title "] #[doc = " # This is the enum's title "]
#[doc = " This is "] #[doc = " This is "]
@ -55,13 +58,8 @@ enum MyEnum {
} }
#[test] #[test]
fn doc_comments_struct() -> TestResult { fn doc_comments_enum() {
test_default_generated_schema::<MyStruct>("doc_comments_struct") test!(MyEnum).assert_snapshot();
}
#[test]
fn doc_comments_enum() -> TestResult {
test_default_generated_schema::<MyEnum>("doc_comments_enum")
} }
/// # OverrideDocs struct /// # OverrideDocs struct
@ -82,6 +80,6 @@ struct OverrideDocs {
} }
#[test] #[test]
fn doc_comments_override() -> TestResult { fn doc_comments_override() {
test_default_generated_schema::<OverrideDocs>("doc_comments_override") test!(OverrideDocs).assert_snapshot();
} }

View file

@ -0,0 +1,14 @@
use crate::prelude::*;
use either1::Either;
#[test]
fn either() {
test!(Either<i32, Either<bool, ()>>)
.assert_snapshot()
.assert_allows_ser_roundtrip([
Either::Left(123),
Either::Right(Either::Left(true)),
Either::Right(Either::Right(())),
])
.assert_matches_de_roundtrip(arbitrary_values());
}

View file

@ -0,0 +1,37 @@
use crate::prelude::*;
use schemars::JsonSchema_repr;
use serde_repr::{Deserialize_repr, Serialize_repr};
#[derive(JsonSchema_repr, Deserialize_repr, Serialize_repr)]
#[repr(u8)]
#[serde(rename = "EnumWithReprAttr")]
/// Description from comment
pub enum Enum {
Zero,
One,
Five = 5,
Six,
Three = 3,
}
#[test]
fn enum_repr() {
test!(Enum)
.assert_snapshot()
.assert_allows_ser_roundtrip([Enum::Zero, Enum::One, Enum::Five, Enum::Six, Enum::Three])
.assert_allows_de_roundtrip([
Value::from(0),
Value::from(1),
Value::from(5),
Value::from(6),
Value::from(3),
])
.assert_rejects_de([
Value::from("Zero"),
Value::from("One"),
Value::from("Five"),
Value::from("Six"),
Value::from("Three"),
])
.assert_matches_de_roundtrip(arbitrary_values());
}

View file

@ -0,0 +1,360 @@
use std::collections::BTreeMap;
use crate::prelude::*;
#[derive(JsonSchema, Deserialize, Serialize)]
struct UnitStruct;
#[derive(JsonSchema, Deserialize, Serialize, Default)]
struct Struct {
foo: i32,
bar: bool,
}
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
enum External {
UnitOne,
StringMap(BTreeMap<String, String>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
Tuple(i32, bool),
UnitTwo,
#[serde(with = "unit_variant_as_u64")]
#[schemars(with = "u64")]
UnitAsInt,
#[serde(with = "tuple_variant_as_str")]
#[schemars(schema_with = "tuple_variant_as_str::json_schema")]
TupleAsStr(i32, bool),
}
impl External {
fn values() -> impl IntoIterator<Item = Self> {
[
Self::UnitOne,
Self::StringMap(
[("hello".to_owned(), "world".to_owned())]
.into_iter()
.collect(),
),
Self::UnitStructNewType(UnitStruct),
Self::StructNewType(Struct {
foo: 123,
bar: true,
}),
Self::Struct {
foo: 123,
bar: true,
},
Self::Tuple(456, false),
Self::UnitTwo,
Self::UnitAsInt,
Self::TupleAsStr(789, true),
]
}
}
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(tag = "tag")]
enum Internal {
UnitOne,
StringMap(BTreeMap<String, String>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct { foo: i32, bar: bool },
// Internally-tagged enums don't support tuple variants
// Tuple(i32, bool),
UnitTwo,
// Internally-tagged enum variants don't support non-object "payloads"
// #[serde(with = "unit_variant_as_u64")]
// #[schemars(with = "u64")]
// UnitAsInt,
// Internally-tagged enums don't support tuple variants
// #[serde(with = "tuple_variant_as_str")]
// #[schemars(schema_with = "tuple_variant_as_str::json_schema")]
// TupleAsStr(i32, bool),
}
impl Internal {
fn values() -> impl IntoIterator<Item = Self> {
[
Self::UnitOne,
Self::StringMap(
[("hello".to_owned(), "world".to_owned())]
.into_iter()
.collect(),
),
Self::UnitStructNewType(UnitStruct),
Self::StructNewType(Struct {
foo: 123,
bar: true,
}),
Self::Struct {
foo: 123,
bar: true,
},
// Self::Tuple(456, false),
Self::UnitTwo,
// Self::UnitAsInt,
// Self::TupleAsStr(789, true),
]
}
}
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(tag = "tag", content = "content")]
enum Adjacent {
UnitOne,
StringMap(BTreeMap<String, String>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
Tuple(i32, bool),
UnitTwo,
#[serde(with = "unit_variant_as_u64")]
#[schemars(with = "u64")]
UnitAsInt,
#[serde(with = "tuple_variant_as_str")]
#[schemars(schema_with = "tuple_variant_as_str::json_schema")]
TupleAsStr(i32, bool),
}
impl Adjacent {
fn values() -> impl IntoIterator<Item = Self> {
[
Self::UnitOne,
Self::StringMap(
[("hello".to_owned(), "world".to_owned())]
.into_iter()
.collect(),
),
Self::UnitStructNewType(UnitStruct),
Self::StructNewType(Struct {
foo: 123,
bar: true,
}),
Self::Struct {
foo: 123,
bar: true,
},
Self::Tuple(456, false),
Self::UnitTwo,
Self::UnitAsInt,
Self::TupleAsStr(789, true),
]
}
}
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(untagged)]
enum Untagged {
UnitOne,
StringMap(BTreeMap<String, String>),
UnitStructNewType(UnitStruct),
StructNewType(Struct),
Struct {
foo: i32,
bar: bool,
},
Tuple(i32, bool),
UnitTwo,
#[serde(with = "unit_variant_as_u64")]
#[schemars(with = "u64")]
UnitAsInt,
#[serde(with = "tuple_variant_as_str")]
#[schemars(schema_with = "tuple_variant_as_str::json_schema")]
TupleAsStr(i32, bool),
}
impl Untagged {
fn values() -> impl IntoIterator<Item = Self> {
[
Self::UnitOne,
Self::StringMap(
[("hello".to_owned(), "world".to_owned())]
.into_iter()
.collect(),
),
Self::UnitStructNewType(UnitStruct),
Self::StructNewType(Struct {
foo: 123,
bar: true,
}),
Self::Struct {
foo: 123,
bar: true,
},
Self::Tuple(456, false),
Self::UnitTwo,
Self::UnitAsInt,
Self::TupleAsStr(789, true),
]
}
}
mod unit_variant_as_u64 {
pub(super) fn serialize<S>(ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
ser.serialize_u64(42)
}
pub(super) fn deserialize<'de, D>(deser: D) -> Result<(), D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Deserialize;
u64::deserialize(deser).map(|_| ())
}
}
mod tuple_variant_as_str {
pub(super) fn serialize<S>(i: &i32, b: &bool, ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
ser.collect_str(&format_args!("{i} {b}"))
}
pub(super) fn deserialize<'de, D>(deser: D) -> Result<(i32, bool), D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Deserialize, Error};
let error = || Error::custom("invalid string");
let (i, b) = <&str>::deserialize(deser)?
.split_once(' ')
.ok_or_else(error)?;
Ok((
i.parse().map_err(|_| error())?,
b.parse().map_err(|_| error())?,
))
}
pub(super) fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "string",
"pattern": r"^\d+ (true|false)$"
})
}
}
#[test]
fn externally_tagged_enum() {
test!(External)
.assert_snapshot()
.assert_allows_ser_roundtrip(External::values())
.assert_matches_de_roundtrip(arbitrary_values());
}
#[test]
fn internally_tagged_enum() {
test!(Internal)
.assert_snapshot()
.assert_allows_ser_roundtrip(Internal::values())
.assert_matches_de_roundtrip(arbitrary_values());
}
#[test]
fn adjacently_tagged_enum() {
test!(Adjacent)
.assert_snapshot()
.assert_allows_ser_roundtrip(Adjacent::values())
.assert_matches_de_roundtrip(arbitrary_values());
}
#[test]
fn untagged_enum() {
test!(Untagged)
.assert_snapshot()
.assert_allows_ser_roundtrip(Untagged::values())
.assert_matches_de_roundtrip(arbitrary_values());
}
#[derive(JsonSchema, Serialize, Deserialize)]
enum NoVariants {}
#[test]
fn no_variants() {
test!(NoVariants)
.assert_snapshot()
.assert_rejects_de(arbitrary_values());
}
#[derive(JsonSchema, Serialize, Deserialize)]
#[serde(rename_all_fields = "UPPERCASE", rename_all = "snake_case")]
enum Renamed {
StructVariant {
field: String,
},
#[serde(rename = "custom name variant")]
RenamedStructVariant {
#[serde(rename = "custom name field")]
field: String,
},
}
#[test]
fn renamed() {
test!(Renamed)
.assert_snapshot()
.assert_allows_ser_roundtrip([
Renamed::StructVariant {
field: "foo".to_owned(),
},
Renamed::RenamedStructVariant {
field: "bar".to_owned(),
},
])
.assert_rejects_de(arbitrary_values());
}
#[allow(dead_code)]
#[derive(JsonSchema, Deserialize, Serialize)]
enum SoundOfMusic {
/// # A deer
///
/// A female deer
Do,
/// A drop of golden sun
Re,
/// A name I call myself
Mi,
}
#[test]
fn unit_variants_with_doc_comments() {
test!(SoundOfMusic)
.assert_snapshot()
.assert_allows_ser_roundtrip([SoundOfMusic::Do, SoundOfMusic::Re, SoundOfMusic::Mi])
.assert_rejects_de(arbitrary_values())
.custom(|schema, _| {
assert_eq!(
schema.as_value().pointer("/oneOf/0/title"),
Some(&("A deer".into())),
);
assert_eq!(
schema.as_value().pointer("/oneOf/0/description"),
Some(&("A female deer".into())),
);
assert_eq!(
schema.as_value().pointer("/oneOf/1/description"),
Some(&("A drop of golden sun".into())),
);
assert_eq!(
schema.as_value().pointer("/oneOf/2/description"),
Some(&("A name I call myself".into())),
);
});
}

View file

@ -0,0 +1,162 @@
use crate::prelude::*;
use std::collections::BTreeMap;
macro_rules! fn_values {
() => {
fn values() -> impl IntoIterator<Item = Self> {
[
Self::Unit,
Self::StringMap(
[("hello".to_owned(), "world".to_owned())]
.into_iter()
.collect(),
),
Self::StructNewType(Struct {
foo: 123,
bar: true,
}),
Self::Struct {
foo: 123,
bar: true,
},
]
}
};
}
#[derive(JsonSchema, Deserialize, Serialize, Default)]
struct Struct {
foo: i32,
bar: bool,
}
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
enum External {
Unit,
StringMap(BTreeMap<String, String>),
StructNewType(Struct),
Struct { foo: i32, bar: bool },
}
impl External {
fn_values!();
}
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(tag = "tag", deny_unknown_fields)]
enum Internal {
Unit,
StringMap(BTreeMap<String, String>),
StructNewType(Struct),
Struct { foo: i32, bar: bool },
}
impl Internal {
fn_values!();
}
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(tag = "tag", content = "content", deny_unknown_fields)]
enum Adjacent {
Unit,
StringMap(BTreeMap<String, String>),
StructNewType(Struct),
Struct { foo: i32, bar: bool },
}
impl Adjacent {
fn_values!();
}
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(untagged, deny_unknown_fields)]
enum Untagged {
Unit,
StringMap(BTreeMap<String, String>),
StructNewType(Struct),
Struct { foo: i32, bar: bool },
}
impl Untagged {
fn_values!();
}
#[test]
fn externally_tagged_enum() {
test!(External)
.assert_snapshot()
.assert_allows_ser_roundtrip(External::values())
.assert_matches_de_roundtrip(arbitrary_values())
.assert_rejects_de([json!({
"Struct": {
"foo": 123,
"bar": true,
"extra": null
}
})])
.assert_allows_de_roundtrip([json!({
"StructNewType": {
"foo": 123,
"bar": true,
"extra": null
}
})]);
}
#[test]
fn internally_tagged_enum() {
test!(Internal)
.assert_snapshot()
.assert_allows_ser_roundtrip(Internal::values())
.assert_matches_de_roundtrip(arbitrary_values())
.assert_rejects_de([json!({
"tag": "Struct",
"foo": 123,
"bar": true,
"extra": null
})])
.assert_allows_de_roundtrip([json!({
"tag": "StructNewType",
"foo": 123,
"bar": true,
"extra": null
})]);
}
#[test]
fn adjacently_tagged_enum() {
test!(Adjacent)
.assert_snapshot()
.assert_allows_ser_roundtrip(Adjacent::values())
.assert_matches_de_roundtrip(arbitrary_values())
.assert_rejects_de([json!({
"tag": "Struct",
"content": {
"foo": 123,
"bar": true,
"extra": null
}
})])
.assert_allows_de_roundtrip([json!({
"tag": "StructNewType",
"content": {
"foo": 123,
"bar": true,
"extra": null
}
})]);
}
#[test]
fn untagged_enum() {
test!(Untagged)
.assert_snapshot()
.assert_allows_ser_roundtrip(Untagged::values())
.assert_matches_de_roundtrip(arbitrary_values())
.assert_allows_de_roundtrip([json!({
"foo": 123,
"bar": true,
"extra": null
})]);
}

View file

@ -0,0 +1,135 @@
use crate::prelude::*;
use schemars::generate::SchemaSettings;
macro_rules! fn_values {
() => {
fn values() -> impl IntoIterator<Item = Self> {
[
Self {
f: 1.23,
e1: Enum1::B(true),
e2: Enum2::F(4.56),
e3: Enum3::S2("abc".into()),
e4: Enum4::U2(789),
e5: Enum5::B3(false),
},
Self {
f: 9.87,
e1: Enum1::S("def".into()),
e2: Enum2::U(654),
e3: Enum3::B2(true),
e4: Enum4::F2(3.21),
e5: Enum5::S3("ghi".into()),
},
]
}
};
}
#[derive(JsonSchema, Deserialize, Serialize)]
enum Enum1 {
B(bool),
S(String),
}
#[derive(JsonSchema, Deserialize, Serialize)]
enum Enum2 {
U(u32),
F(f64),
}
#[derive(JsonSchema, Deserialize, Serialize)]
enum Enum3 {
B2(bool),
S2(String),
}
#[derive(JsonSchema, Deserialize, Serialize)]
enum Enum4 {
U2(u32),
F2(f64),
}
#[derive(JsonSchema, Deserialize, Serialize)]
enum Enum5 {
B3(bool),
S3(String),
}
#[derive(JsonSchema, Deserialize, Serialize)]
struct Container {
f: f32,
#[serde(flatten)]
e1: Enum1,
#[serde(flatten)]
e2: Enum2,
#[serde(flatten)]
e3: Enum3,
#[serde(flatten)]
e4: Enum4,
#[serde(flatten)]
e5: Enum5,
}
impl Container {
fn_values!();
}
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
struct ContainerDenyUnknownFields {
f: f32,
#[serde(flatten)]
e1: Enum1,
#[serde(flatten)]
e2: Enum2,
#[serde(flatten)]
e3: Enum3,
#[serde(flatten)]
e4: Enum4,
#[serde(flatten)]
e5: Enum5,
}
impl ContainerDenyUnknownFields {
fn_values!();
}
fn json_with_extra_field() -> Value {
json!({
"f": 1.23,
"B": true,
"F": 4.56,
"S2": "abc",
"U2": 789,
"B3": false,
"extra": null
})
}
#[test]
fn enums_flattened() {
test!(Container)
.assert_snapshot()
.assert_allows_ser_roundtrip(Container::values())
.assert_matches_de_roundtrip(arbitrary_values())
.assert_allows_de_roundtrip([json_with_extra_field()]);
}
#[test]
fn enums_flattened_deny_unknown_fields() {
test!(ContainerDenyUnknownFields)
.assert_snapshot()
.assert_allows_ser_roundtrip(ContainerDenyUnknownFields::values())
.assert_matches_de_roundtrip(arbitrary_values())
.assert_rejects_de([json_with_extra_field()]);
}
#[test]
fn enums_flattened_deny_unknown_fields_draft07() {
test!(ContainerDenyUnknownFields, SchemaSettings::draft07())
.assert_snapshot()
.assert_allows_ser_roundtrip(ContainerDenyUnknownFields::values())
.assert_matches_de_roundtrip(arbitrary_values())
.assert_rejects_de([json_with_extra_field()]);
}

View file

@ -1,7 +1,4 @@
mod util; use crate::prelude::*;
use schemars::JsonSchema;
use serde::Serialize;
use util::*;
#[derive(Default, JsonSchema, Serialize)] #[derive(Default, JsonSchema, Serialize)]
#[schemars(example = "Struct::default", example = "null")] #[schemars(example = "Struct::default", example = "null")]
@ -20,6 +17,6 @@ fn eight() -> i32 {
fn null() {} fn null() {}
#[test] #[test]
fn examples() -> TestResult { fn examples() {
test_default_generated_schema::<Struct>("examples") test!(Struct).assert_snapshot();
} }

View file

@ -0,0 +1,169 @@
#![allow(clippy::approx_constant)]
use crate::prelude::*;
static THREE: f64 = 3.0;
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(extend("obj" = {"array": [null, ()]}))]
#[schemars(extend("3" = THREE), extend("pi" = THREE + 0.14))]
struct Struct {
#[schemars(extend("foo" = "bar"))]
value: Value,
#[schemars(extend("type" = ["number", "string"]))]
int: i32,
}
#[test]
fn extend_struct() {
test!(Struct).assert_snapshot().custom(|schema, _| {
assert_eq!(schema.get("obj"), Some(&json!({ "array": [null, null] })));
assert_eq!(schema.get("3"), Some(&json!(3.0)));
assert_eq!(schema.get("pi"), Some(&json!(3.14)));
assert_eq!(
schema.as_value().pointer("/properties/value"),
Some(&json!({ "foo": "bar" }))
);
assert_eq!(
schema.as_value().pointer("/properties/int/type"),
Some(&json!(["number", "string"]))
);
});
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(extend("obj" = {"array": [null, ()]}))]
#[schemars(extend("3" = THREE), extend("pi" = THREE + 0.14))]
struct TupleStruct(
#[schemars(extend("foo" = "bar"))] Value,
#[schemars(extend("type" = ["number", "string"]))] usize,
);
#[test]
fn extend_tuple_struct() {
test!(TupleStruct).assert_snapshot().custom(|schema, _| {
assert_eq!(schema.get("obj"), Some(&json!({ "array": [null, null] })));
assert_eq!(schema.get("3"), Some(&json!(3.0)));
assert_eq!(schema.get("pi"), Some(&json!(3.14)));
assert_eq!(
schema.as_value().pointer("/prefixItems/0"),
Some(&json!({ "foo": "bar" }))
);
assert_eq!(
schema.as_value().pointer("/prefixItems/1/type"),
Some(&json!(["number", "string"]))
);
});
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(extend("foo" = "bar"))]
enum ExternalEnum {
#[schemars(extend("foo" = "bar"))]
Unit,
#[schemars(extend("foo" = "bar"))]
NewType(Value),
#[schemars(extend("foo" = "bar"))]
Tuple(i32, bool),
#[schemars(extend("foo" = "bar"))]
Struct { i: i32, b: bool },
}
#[test]
fn extend_externally_tagged_enum() {
test!(ExternalEnum).assert_snapshot().custom(|schema, _| {
assert_eq!(schema.get("foo"), Some(&json!("bar")));
for i in 0..4 {
assert_eq!(
schema.as_value().pointer(&format!("/oneOf/{i}/foo")),
Some(&json!("bar"))
);
}
});
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(tag = "t", extend("foo" = "bar"))]
enum InternalEnum {
#[schemars(extend("foo" = "bar"))]
Unit,
#[schemars(extend("foo" = "bar"))]
NewType(Value),
#[schemars(extend("foo" = "bar"))]
Struct { i: i32, b: bool },
}
#[test]
fn extend_internally_tagged_enum() {
test!(InternalEnum).assert_snapshot().custom(|schema, _| {
assert_eq!(schema.get("foo"), Some(&json!("bar")));
for i in 0..3 {
assert_eq!(
schema.as_value().pointer(&format!("/oneOf/{i}/foo")),
Some(&json!("bar"))
);
}
});
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(tag = "t", content = "c", extend("foo" = "bar"))]
enum AdjacentEnum {
#[schemars(extend("foo" = "bar"))]
Unit,
#[schemars(extend("foo" = "bar"))]
NewType(Value),
#[schemars(extend("foo" = "bar"))]
Tuple(i32, bool),
#[schemars(extend("foo" = "bar"))]
Struct { i: i32, b: bool },
}
#[test]
fn extend_adjacently_tagged_enum() {
test!(AdjacentEnum).assert_snapshot().custom(|schema, _| {
assert_eq!(schema.get("foo"), Some(&json!("bar")));
for i in 0..4 {
assert_eq!(
schema.as_value().pointer(&format!("/oneOf/{i}/foo")),
Some(&json!("bar"))
);
}
});
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(untagged, extend("foo" = "bar"))]
enum UntaggedEnum {
#[schemars(extend("foo" = "bar"))]
Unit,
#[schemars(extend("foo" = "bar"))]
NewType(Value),
#[schemars(extend("foo" = "bar"))]
Tuple(i32, bool),
#[schemars(extend("foo" = "bar"))]
Struct { i: i32, b: bool },
}
#[test]
fn extend_untagged_enum() {
test!(UntaggedEnum).assert_snapshot().custom(|schema, _| {
assert_eq!(schema.get("foo"), Some(&json!("bar")));
for i in 0..4 {
assert_eq!(
schema.as_value().pointer(&format!("/anyOf/{i}/foo")),
Some(&json!("bar"))
);
}
});
}

View file

@ -0,0 +1,93 @@
use crate::prelude::*;
use std::collections::BTreeMap;
#[derive(JsonSchema, Deserialize, Serialize, Default)]
struct Flat {
f: f32,
b: bool,
#[serde(default, skip_serializing_if = "str::is_empty")]
s: String,
v: Vec<i32>,
}
#[derive(JsonSchema, Deserialize, Serialize, Default)]
#[schemars(rename = "Flat")]
struct Deep1 {
f: f32,
#[serde(flatten)]
deep2: Deep2,
v: Vec<i32>,
}
#[derive(JsonSchema, Deserialize, Serialize, Default)]
struct Deep2 {
b: bool,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
deep3: Option<Deep3>,
}
#[derive(JsonSchema, Deserialize, Serialize)]
struct Deep3 {
s: String,
}
#[test]
fn flattened_struct() {
test!(Deep1)
.assert_snapshot()
.assert_identical::<Flat>()
.assert_allows_ser_roundtrip([
Deep1::default(),
Deep1 {
f: 1.0,
deep2: Deep2 {
b: true,
deep3: Some(Deep3 {
s: "test".to_owned(),
}),
},
v: vec![123],
},
])
.assert_matches_de_roundtrip(arbitrary_values());
}
#[derive(JsonSchema, Deserialize, Serialize, Default)]
struct FlattenValue {
flag: bool,
#[serde(flatten)]
value: Value,
}
#[derive(JsonSchema, Deserialize, Serialize, Default)]
#[schemars(rename = "FlattenValue")]
struct FlattenMap {
flag: bool,
#[serde(flatten)]
value: BTreeMap<String, Value>,
}
#[test]
fn flattened_value() {
test!(FlattenValue)
.assert_snapshot()
.assert_allows_ser_roundtrip([
FlattenValue {
flag: false,
value: Value::Null,
},
FlattenValue {
flag: true,
value: Value::Object(Default::default()),
},
])
.assert_matches_de_roundtrip(arbitrary_values());
}
#[test]
fn flattened_map() {
test!(FlattenMap)
.assert_identical::<FlattenValue>()
.assert_allows_ser_roundtrip_default()
.assert_matches_de_roundtrip(arbitrary_values());
}

View file

@ -0,0 +1,84 @@
use crate::prelude::*;
use schemars::generate::SchemaSettings;
use std::collections::BTreeMap;
#[derive(JsonSchema, Deserialize, Serialize, Default, Clone)]
#[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(JsonSchema, Deserialize, Serialize, Default, Clone)]
pub struct MyInnerStruct {
pub my_map: BTreeMap<String, f64>,
pub my_vec: Vec<f32>,
pub my_empty_map: BTreeMap<String, f64>,
pub my_empty_vec: Vec<f32>,
pub my_tuple: (char, u8),
}
#[derive(JsonSchema, Deserialize, Serialize, Clone)]
pub enum MyEnum {
NewType(String),
Struct { floats: Vec<f32> },
}
fn struct_value() -> MyStruct {
MyStruct {
my_int: 123,
my_bool: true,
my_nullable_enum: None,
my_inner_struct: MyInnerStruct {
my_map: [("k".to_owned(), 1.23)].into_iter().collect(),
my_vec: vec![1.0, 2.0, 3.0],
my_empty_map: BTreeMap::new(),
my_empty_vec: Vec::new(),
my_tuple: ('💩', 42),
},
_skip: 123,
skip_if_none: None,
}
}
#[test]
fn custom_struct() {
let value = struct_value();
test!(value: value.clone())
.assert_snapshot()
.assert_allows_ser_roundtrip([value, MyStruct::default()]);
}
#[test]
fn custom_struct_openapi3() {
let value = struct_value();
test!(value: value.clone(), SchemaSettings::openapi3())
.assert_snapshot()
.assert_allows_ser_roundtrip([value, MyStruct::default()]);
}
#[test]
fn json_value() {
let value = json!({
"zero": 0,
"zeroPointZero": 0.0,
"bool": true,
"null": null,
"object": {
"strings": ["foo", "bar"],
"mixed": [1, true]
},
});
test!(value: value.clone())
.assert_snapshot()
.assert_allows_ser_roundtrip([value]);
}

View file

@ -0,0 +1,159 @@
use crate::prelude::*;
use garde::Validate;
const ONE: usize = 1;
const HUNDRED: usize = 10;
#[derive(JsonSchema, Deserialize, Serialize, Validate)]
pub struct GardeAttrStruct {
#[garde(range(min = 1.0, max = 100.0))]
min_max: f32,
#[garde(range(min = ONE as f32, max = HUNDRED as f32))]
min_max2: f32,
#[garde(pattern(r"^[Hh]ello"))]
regex_str: String,
#[garde(contains(concat!("sub","string...")))]
contains_str: String,
#[garde(email)]
email_address: String,
#[garde(url)]
homepage: String,
#[garde(length(min = ONE, max = HUNDRED))]
non_empty_str: String,
#[garde(length(equal = 2), inner(length(min = 1)))]
pair: Vec<String>,
#[garde(required)]
required_option: Option<bool>,
#[garde(required, dive)]
#[serde(flatten)]
required_flattened: Option<GardeAttrInner>,
}
#[derive(JsonSchema, Deserialize, Serialize, Validate)]
pub struct GardeAttrInner {
#[garde(range(min = -100, max = 100))]
x: i32,
}
impl Default for GardeAttrStruct {
fn default() -> Self {
Self {
min_max: 1.0,
min_max2: 1.0,
regex_str: "Hello world".to_owned(),
contains_str: "Contains substring...".to_owned(),
email_address: "test@test.test".to_owned(),
homepage: "http://test.test".to_owned(),
non_empty_str: "test".to_owned(),
pair: vec!["a".to_owned(), "b".to_owned()],
required_option: Some(true),
required_flattened: Some(GardeAttrInner { x: 0 }),
}
}
}
impl GardeAttrStruct {
pub fn invalid_values() -> impl IntoIterator<Item = Self> {
static MUTATORS: &[fn(&mut GardeAttrStruct)] = &[
|v| v.min_max = 0.9,
|v| v.min_max = 100.1,
|v| v.min_max2 = 0.9,
|v| v.min_max2 = 100.1,
|v| v.regex_str = "fail".to_owned(),
|v| v.contains_str = "fail".to_owned(),
|v| v.email_address = "fail".to_owned(),
|v| v.homepage = "fail".to_owned(),
|v| v.non_empty_str = String::new(),
|v| v.pair = Vec::new(),
|v| v.pair = vec!["a".to_owned(), "b".to_owned(), "c".to_owned()],
|v| v.pair = vec!["".to_owned(), "b".to_owned()],
|v| v.required_option = None,
|v| v.required_flattened = None,
|v| v.required_flattened = Some(GardeAttrInner { x: -101 }),
|v| v.required_flattened = Some(GardeAttrInner { x: 101 }),
];
MUTATORS.iter().map(|f| {
let mut result = GardeAttrStruct::default();
f(&mut result);
result
})
}
}
#[test]
fn garde_attrs() {
test!(GardeAttrStruct)
.with_validator(|v| v.validate().is_ok())
.assert_snapshot()
.assert_allows_ser_roundtrip_default()
.assert_rejects_invalid(GardeAttrStruct::invalid_values())
.assert_matches_de_roundtrip(arbitrary_values());
}
#[allow(dead_code)]
#[derive(JsonSchema)]
#[schemars(rename = "GardeAttrStruct")]
pub struct SchemarsAttrStruct {
#[schemars(range(min = 1.0, max = 100.0))]
min_max: f32,
#[schemars(range(min = ONE as f32, max = HUNDRED as f32))]
min_max2: f32,
#[schemars(pattern(r"^[Hh]ello"))]
regex_str: String,
#[schemars(contains(concat!("sub","string...")))]
contains_str: String,
#[schemars(email)]
email_address: String,
#[schemars(url)]
homepage: String,
#[schemars(length(min = ONE, max = HUNDRED))]
non_empty_str: String,
#[schemars(length(equal = 2), inner(length(min = 1)))]
pair: Vec<String>,
#[schemars(required)]
required_option: Option<bool>,
#[schemars(required)]
#[serde(flatten)]
required_flattened: Option<SchemarsAttrInner>,
}
#[allow(dead_code)]
#[derive(JsonSchema)]
pub struct SchemarsAttrInner {
#[schemars(range(min = -100, max = 100))]
x: i32,
}
#[test]
fn schemars_attrs() {
test!(SchemarsAttrStruct).assert_identical::<GardeAttrStruct>();
}
#[derive(JsonSchema, Deserialize, Serialize, Validate)]
pub struct GardeAttrTuple(
#[garde(range(max = 10))] u8,
#[garde(required)] Option<bool>,
);
#[test]
fn garde_attrs_tuple() {
test!(GardeAttrTuple)
.with_validator(|v| v.validate().is_ok())
.assert_snapshot()
.assert_allows_ser_roundtrip([GardeAttrTuple(10, Some(false))])
.assert_rejects_invalid([GardeAttrTuple(11, Some(false)), GardeAttrTuple(10, None)])
.assert_matches_de_roundtrip(arbitrary_values());
}
#[derive(JsonSchema, Deserialize, Serialize, Validate)]
pub struct GardeAttrNewType(#[garde(range(max = 10))] u8);
#[test]
fn garde_attrs_newtype() {
test!(GardeAttrNewType)
.with_validator(|v| v.validate().is_ok())
.assert_snapshot()
.assert_allows_ser_roundtrip([GardeAttrNewType(10)])
.assert_rejects_invalid([GardeAttrNewType(11)])
.assert_matches_de_roundtrip(arbitrary_values());
}

View file

@ -0,0 +1,19 @@
use crate::prelude::*;
use indexmap2::{indexmap, indexset, IndexMap, IndexSet};
use std::collections::{BTreeMap, BTreeSet};
#[test]
fn indexmap() {
test!(IndexMap<String, bool>)
.assert_identical::<BTreeMap<String, bool>>()
.assert_allows_ser_roundtrip([indexmap!(), indexmap!("key".to_owned() => true)])
.assert_matches_de_roundtrip(arbitrary_values());
}
#[test]
fn indexset() {
test!(IndexSet<String>)
.assert_identical::<BTreeSet<String>>()
.assert_allows_ser_roundtrip([indexset!(), indexset!("test".to_owned())])
.assert_matches_de_roundtrip(arbitrary_values());
}

View file

@ -0,0 +1,61 @@
use crate::prelude::*;
use schemars::generate::SchemaSettings;
#[derive(JsonSchema, Deserialize, Serialize, Default)]
struct MyJob {
spec: MyJobSpec,
}
#[derive(JsonSchema, Deserialize, Serialize, Default)]
struct MyJobSpec {
replicas: u32,
}
#[test]
fn struct_normal() {
let settings = SchemaSettings::default().with(|s| s.inline_subschemas = true);
test!(MyJob, settings)
.assert_snapshot()
.assert_allows_ser_roundtrip_default()
.assert_matches_de_roundtrip(arbitrary_values());
}
#[derive(JsonSchema, Deserialize, Serialize)]
struct RecursiveOuter {
direct: Option<Box<RecursiveOuter>>,
indirect: Option<Box<RecursiveInner>>,
}
#[derive(JsonSchema, Deserialize, Serialize)]
struct RecursiveInner {
recursive: RecursiveOuter,
}
#[test]
fn struct_recursive() {
let settings = SchemaSettings::default().with(|s| s.inline_subschemas = true);
test!(RecursiveOuter, settings)
.assert_snapshot()
.assert_allows_ser_roundtrip([
RecursiveOuter {
direct: None,
indirect: None,
},
RecursiveOuter {
direct: Some(Box::new(RecursiveOuter {
direct: None,
indirect: None,
})),
indirect: Some(Box::new(RecursiveInner {
recursive: RecursiveOuter {
direct: Some(Box::new(RecursiveOuter {
direct: None,
indirect: None,
})),
indirect: None,
},
})),
},
])
.assert_matches_de_roundtrip(arbitrary_values());
}

Some files were not shown because too many files have changed in this diff Show more