Compare commits

...

10 commits

Author SHA1 Message Date
Dmitry Dygalo
f8c1fe21b7 chore: Update jsonschema to 0.20.0
Signed-off-by: Dmitry Dygalo <dmitry.dygalo@workato.com>
2025-01-13 10:25:47 +00:00
Andre Popovitch
13ffa14d1f Fix clippy lints 2025-01-13 10:22:16 +00:00
Graham Esau
ae4fe29592 1.0.0-alpha.17 2024-12-02 22:19:33 +00:00
Graham Esau
b54680e7dc 1.0.0-alpha.16 2024-11-25 10:44:25 +00:00
Graham Esau
95023c2ab0
Avoid inlining schemas in internally-tagged enum newtype variants (#355)
Schemas are still inlined in some cases, e.g. when the inner type has `deny_unknown_fields`, because then `$ref` would cause an unsatisfiable schema due to the variant tag not being allowed
2024-11-25 10:26:37 +00:00
Graham Esau
e5168819a4
Allow example attribute value to be any expression (#354) 2024-11-20 22:18:31 +00:00
Graham Esau
a479e6cc0e
Improvements to test coverage (#340)
This increases msrv to 1.70
2024-09-16 10:06:22 +01:00
Graham Esau
092dc17ae4
Delete unused enumset test schema 2024-09-09 22:08:46 +01:00
Graham Esau
65346d6683
Remove enumset optional dependency (#339)
The `JsonSchema` impl for `EnumSet` did not accurately represent how it is de/serialized by default
2024-09-09 21:52:16 +01:00
Graham Esau
6a03758284 Switch from actions-rs/toolchain to dtolnay/rust-toolchain
Also update checkout action because why not
2024-09-09 10:25:12 +01:00
254 changed files with 5998 additions and 4959 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
@ -23,12 +23,10 @@ jobs:
allow_failure: true allow_failure: true
fail-fast: false fail-fast: false
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@master
with: with:
profile: minimal
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
override: true
- name: Check with no feature flags - name: Check with no feature flags
run: cargo check --verbose --no-default-features run: cargo check --verbose --no-default-features
continue-on-error: ${{ matrix.allow_failure }} continue-on-error: ${{ matrix.allow_failure }}

View file

@ -1,5 +1,27 @@
# Changelog # Changelog
## [1.0.0-alpha.17] - 2024-12-02
### Changed
- For newtype variants of internally-tagged enums, prefer referencing the inner type's schema via `$ref` instead of always inlining the schema (https://github.com/GREsau/schemars/pull/355) _(this change was included in the release notes for 1.0.0-alpha.16, but was accidentally excluded from the published crate)_
## [1.0.0-alpha.16] - 2024-11-25
### Removed (_⚠ breaking changes ⚠_)
- 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 (_⚠ breaking changes ⚠_)
- MSRV is now 1.70
- [The `example` attribute](https://graham.cool/schemars/deriving/attributes/#example) value is now an arbitrary expression, rather than a string literal identifying a function to call. To avoid silent behaviour changes, the expression must not be a string literal where the value can be parsed as a function path - e.g. `#[schemars(example = "foo")]` is now a compile error, but `#[schemars(example = foo())]` is allowed (as is `#[schemars(example = &"foo")]` if you want the the literal string value `"foo"` to be the example).
### 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

1136
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
@ -268,7 +268,6 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable
- `bytes1` - [bytes](https://crates.io/crates/bytes) (^1.0) - `bytes1` - [bytes](https://crates.io/crates/bytes) (^1.0)
- `chrono04` - [chrono](https://crates.io/crates/chrono) (^0.4) - `chrono04` - [chrono](https://crates.io/crates/chrono) (^0.4)
- `either1` - [either](https://crates.io/crates/either) (^1.3) - `either1` - [either](https://crates.io/crates/either) (^1.3)
- `enumset1` - [enumset](https://crates.io/crates/enumset) (^1.0)
- `indexmap2` - [indexmap](https://crates.io/crates/indexmap) (^2.0) - `indexmap2` - [indexmap](https://crates.io/crates/indexmap) (^2.0)
- `rust_decimal1` - [rust_decimal](https://crates.io/crates/rust_decimal) (^1.0) - `rust_decimal1` - [rust_decimal](https://crates.io/crates/rust_decimal) (^1.0)
- `semver1` - [semver](https://crates.io/crates/semver) (^1.0.9) - `semver1` - [semver](https://crates.io/crates/semver) (^1.0.9)
@ -281,5 +280,5 @@ For example, to implement `JsonSchema` on types from `chrono`, enable it as a fe
```toml ```toml
[dependencies] [dependencies]
schemars = { version = "1.0.0-alpha.15", features = ["chrono04"] } schemars = { version = "1.0.0-alpha.17", features = ["chrono04"] }
``` ```

View file

@ -23,10 +23,9 @@ All optional dependencies are now suffixed by their version:
- `url` is now `url2` - `url` is now `url2`
- `bytes` is now `bytes1` - `bytes` is now `bytes1`
- `rust_decimal` is now `rust_decimal1` - `rust_decimal` is now `rust_decimal1`
- `enumset` is now `enumset1`
- `smol_str` is now `smol_str02` - `smol_str` is now `smol_str02`
- `semver` is now `semver1` - `semver` is now `semver1`
- `indexmap`, `uuid08`, `arrayvec05` and `bigdecimal03` have been removed - `enumset`, `indexmap`, `uuid08`, `arrayvec05` and `bigdecimal03` have been removed
- `indexmap2`, `arrayvec07` and `bigdecimal04` are unchanged - `indexmap2`, `arrayvec07` and `bigdecimal04` are unchanged
## `Schema` is now a wrapper around `serde_json::Value` ## `Schema` is now a wrapper around `serde_json::Value`

View file

@ -18,7 +18,6 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable
- `bytes1` - [bytes](https://crates.io/crates/bytes) (^1.0) - `bytes1` - [bytes](https://crates.io/crates/bytes) (^1.0)
- `chrono04` - [chrono](https://crates.io/crates/chrono) (^0.4) - `chrono04` - [chrono](https://crates.io/crates/chrono) (^0.4)
- `either1` - [either](https://crates.io/crates/either) (^1.3) - `either1` - [either](https://crates.io/crates/either) (^1.3)
- `enumset1` - [enumset](https://crates.io/crates/enumset) (^1.0)
- `indexmap2` - [indexmap](https://crates.io/crates/indexmap) (^2.0) - `indexmap2` - [indexmap](https://crates.io/crates/indexmap) (^2.0)
- `rust_decimal1` - [rust_decimal](https://crates.io/crates/rust_decimal) (^1.0) - `rust_decimal1` - [rust_decimal](https://crates.io/crates/rust_decimal) (^1.0)
- `semver1` - [semver](https://crates.io/crates/semver) (^1.0.9) - `semver1` - [semver](https://crates.io/crates/semver) (^1.0.9)
@ -31,5 +30,5 @@ For example, to implement `JsonSchema` on types from `chrono`, enable it as a fe
```toml ```toml
[dependencies] [dependencies]
schemars = { version = "1.0.0-alpha.15", features = ["chrono04"] } schemars = { version = "1.0.0-alpha.17", features = ["chrono04"] }
``` ```

View file

@ -298,13 +298,15 @@ Set on a container, variant or field to set the generated schema's `title` and/o
<h3 id="example"> <h3 id="example">
`#[schemars(example = "some::function")]` `#[schemars(example = value)]`
</h3> </h3>
Set on a container, variant or field to include the result of the given function in the generated schema's `examples`. The function should take no parameters and can return any type that implements serde's `Serialize` trait - it does not need to return the same type as the attached struct/field. This attribute can be repeated to specify multiple examples. Set on a container, variant or field to include the given value in the generated schema's `examples`. The value can be any type that implements serde's `Serialize` trait - it does not need to be the same type as the attached struct/field. This attribute can be repeated to specify multiple examples.
To use the result of arbitrary expressions as examples, you can instead use the [`extend`](#extend) attribute, e.g. `[schemars(extend("examples" = ["example string"]))]`. In previous versions of schemars, the value had to be a string literal identifying a defined function that would be called to return the actual example value (similar to the [`default`](#default) attribute). To avoid the new attribute behaviour from silently breaking old consumers, string literals consisting of a single word (e.g. `#[schemars(example = "my_fn")]`) or a path (e.g. `#[schemars(example = "my_mod::my_fn")]`) are currently disallowed. This restriction may be relaxed in a future version of schemars, but for now if you want to include such a string as the literal example value, this can be done by borrowing the value, e.g. `#[schemars(example = &"my_fn")]`. If you instead want to call a function to get the example value (mirrorring the old behaviour), you must use an explicit function call expression, e.g. `#[schemars(example = my_fn())]`.
Alternatively, to directly set multiple examples without repeating `example = ...` attribute, you can instead use the [`extend`](#extend) attribute, e.g. `#[schemars(extend("examples" = [1, 2, 3]))]`.
<h3 id="deprecated"> <h3 id="deprecated">

View file

@ -3,17 +3,17 @@ name = "schemars"
description = "Generate JSON Schemas from Rust code" description = "Generate JSON Schemas from Rust code"
homepage = "https://graham.cool/schemars/" homepage = "https://graham.cool/schemars/"
repository = "https://github.com/GREsau/schemars" repository = "https://github.com/GREsau/schemars"
version = "1.0.0-alpha.15" version = "1.0.0-alpha.17"
authors = ["Graham Esau <gesau@hotmail.co.uk>"] authors = ["Graham Esau <gesau@hotmail.co.uk>"]
edition = "2021" edition = "2021"
license = "MIT" 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.17", optional = true, path = "../schemars_derive" }
serde = { version = "1.0", default-features = false, features = ["alloc"]} serde = { version = "1.0", default-features = false, features = ["alloc"]}
serde_json = { version = "1.0.127", default-features = false, features = ["alloc"] } serde_json = { version = "1.0.127", default-features = false, features = ["alloc"] }
dyn-clone = "1.0" dyn-clone = "1.0"
@ -24,10 +24,9 @@ arrayvec07 = { version = "0.7", default-features = false, optional = true, packa
bigdecimal04 = { version = "0.4", default-features = false, optional = true, package = "bigdecimal" } bigdecimal04 = { version = "0.4", default-features = false, optional = true, package = "bigdecimal" }
bytes1 = { version = "1.0", default-features = false, optional = true, package = "bytes" } bytes1 = { version = "1.0", default-features = false, optional = true, package = "bytes" }
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" }
enumset1 = { version = "1.0", default-features = false, optional = true, package = "enumset" }
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" }
@ -38,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.20", 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"]
@ -60,66 +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 = "enumset"
required-features = ["enumset1"]
[[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

@ -1,5 +1,5 @@
use crate::_alloc_prelude::*; use crate::_alloc_prelude::*;
use crate::transform::transform_immediate_subschemas; use crate::transform::{transform_immediate_subschemas, Transform};
use crate::{JsonSchema, Schema, SchemaGenerator}; use crate::{JsonSchema, Schema, SchemaGenerator};
use serde::Serialize; use serde::Serialize;
use serde_json::{json, map::Entry, Map, Value}; use serde_json::{json, map::Entry, Map, Value};
@ -12,6 +12,39 @@ pub extern crate serde_json;
pub use rustdoc::get_title_and_description; pub use rustdoc::get_title_and_description;
pub fn json_schema_for_internally_tagged_enum_newtype_variant<T: ?Sized + JsonSchema>(
generator: &mut SchemaGenerator,
) -> Schema {
let mut schema = T::json_schema(generator);
// Inline the newtype's inner schema if any of:
// - The type specifies that its schema should always be inlined
// - The generator settings specify that all schemas should be inlined
// - The inner type is a unit struct, which would cause an unsatisfiable schema due to mismatched `type`.
// In this case, we replace its type with "object" in `apply_internal_enum_variant_tag`
// - The inner schema specified `"additionalProperties": false` or `"unevaluatedProperties": false`,
// since that would disallow the variant tag. If additional/unevaluatedProperties is in the top-level
// schema, then we can leave it there, because it will "see" the variant tag property. But if it is
// nested e.g. in an `allOf`, then it must be removed, which is why we run `AllowUnknownProperties`
// but only on immediate subschemas.
let mut transform = AllowUnknownProperties::default();
transform_immediate_subschemas(&mut transform, &mut schema);
if T::always_inline_schema()
|| generator.settings().inline_subschemas
|| schema.get("type").and_then(Value::as_str) == Some("null")
|| schema.get("additionalProperties").and_then(Value::as_bool) == Some(false)
|| schema.get("unevaluatedProperties").and_then(Value::as_bool) == Some(false)
|| transform.did_modify
{
return schema;
}
// ...otherwise, we can freely refer to the schema via a `$ref`
generator.subschema_for::<T>()
}
// Helper for generating schemas for flattened `Option` fields. // Helper for generating schemas for flattened `Option` fields.
pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>( pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
generator: &mut SchemaGenerator, generator: &mut SchemaGenerator,
@ -25,20 +58,29 @@ pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
// Always allow aditional/unevaluated properties, because the outer struct determines // Always allow aditional/unevaluated properties, because the outer struct determines
// whether it denies unknown fields. // whether it denies unknown fields.
allow_unknown_properties(&mut schema); AllowUnknownProperties::default().transform(&mut schema);
schema schema
} }
fn allow_unknown_properties(schema: &mut Schema) { #[derive(Default)]
if schema.get("additionalProperties").and_then(Value::as_bool) == Some(false) { struct AllowUnknownProperties {
schema.remove("additionalProperties"); did_modify: bool,
} }
if schema.get("unevaluatedProperties").and_then(Value::as_bool) == Some(false) {
schema.remove("unevaluatedProperties");
}
transform_immediate_subschemas(&mut allow_unknown_properties, schema); impl Transform for AllowUnknownProperties {
fn transform(&mut self, schema: &mut Schema) {
if schema.get("additionalProperties").and_then(Value::as_bool) == Some(false) {
schema.remove("additionalProperties");
self.did_modify = true;
}
if schema.get("unevaluatedProperties").and_then(Value::as_bool) == Some(false) {
schema.remove("unevaluatedProperties");
self.did_modify = true;
}
transform_immediate_subschemas(self, schema);
}
} }
/// Hack to simulate specialization: /// Hack to simulate specialization:

View file

@ -671,13 +671,14 @@ where
impl Debug for Box<dyn GenTransform> { impl Debug for Box<dyn GenTransform> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
#[allow(clippy::used_underscore_items)]
self._debug_type_name(f) self._debug_type_name(f)
} }
} }
fn _assert_send() { fn _assert_send() {
fn _assert<T: Send>() {} fn assert<T: Send>() {}
_assert::<SchemaSettings>(); assert::<SchemaSettings>();
_assert::<SchemaGenerator>(); assert::<SchemaGenerator>();
} }

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;
@ -75,9 +71,6 @@ mod decimal;
#[cfg(feature = "either1")] #[cfg(feature = "either1")]
mod either1; mod either1;
#[cfg(feature = "enumset1")]
forward_impl!((<T: enumset1::EnumSetType + crate::JsonSchema> crate::JsonSchema for enumset1::EnumSet<T>) => alloc::collections::BTreeSet<T>);
#[cfg(feature = "indexmap2")] #[cfg(feature = "indexmap2")]
mod indexmap2; mod indexmap2;

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

@ -145,7 +145,6 @@ pub mod r#gen {
/// ///
/// assert_eq!(<GenericType<i32>>::schema_id(), <&mut GenericType<&i32>>::schema_id()); /// assert_eq!(<GenericType<i32>>::schema_id(), <&mut GenericType<&i32>>::schema_id());
/// ``` /// ```
pub trait JsonSchema { pub trait JsonSchema {
/// Whether JSON Schemas generated for this type should be included directly in parent schemas, /// Whether JSON Schemas generated for this type should be included directly in parent schemas,
/// rather than being re-used where possible using the `$ref` keyword. /// rather than being re-used where possible using the `$ref` keyword.

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,18 +0,0 @@
mod util;
use enumset1::{EnumSet, EnumSetType};
use schemars::JsonSchema;
use util::*;
// needed to derive EnumSetType when using a crate alias
extern crate enumset1 as enumset;
#[derive(EnumSetType, JsonSchema)]
enum Foo {
Bar,
Baz,
}
#[test]
fn enumset() -> TestResult {
test_default_generated_schema::<EnumSet<Foo>>("enumset")
}

View file

@ -1,25 +0,0 @@
mod util;
use schemars::JsonSchema;
use serde::Serialize;
use util::*;
#[derive(Default, JsonSchema, Serialize)]
#[schemars(example = "Struct::default", example = "null")]
struct Struct {
#[schemars(example = "eight", example = "null")]
foo: i32,
bar: bool,
#[schemars(example = "null")]
baz: Option<&'static str>,
}
fn eight() -> i32 {
8
}
fn null() {}
#[test]
fn examples() -> TestResult {
test_default_generated_schema::<Struct>("examples")
}

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,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,18 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Set_of_Foo",
"type": "array",
"uniqueItems": true,
"items": {
"$ref": "#/$defs/Foo"
},
"$defs": {
"Foo": {
"type": "string",
"enum": [
"Bar",
"Baz"
]
}
}
}

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,73 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "External",
"oneOf": [
{
"type": "object",
"properties": {
"struct": {
"type": "object",
"properties": {
"foo": {
"type": "boolean"
}
},
"required": [
"foo"
]
}
},
"required": [
"struct"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"newType": {
"type": "boolean"
}
},
"required": [
"newType"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"tuple": {
"type": "array",
"prefixItems": [
{
"type": "boolean"
},
{
"type": "integer",
"format": "int32"
}
],
"minItems": 2,
"maxItems": 2
}
},
"required": [
"tuple"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"unit": {
"type": "boolean"
}
},
"required": [
"unit"
],
"additionalProperties": false
}
]
}

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,24 +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"
]
}
},
"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())),
);
});
}

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