Skip to content

Commit ab4ba87

Browse files
authored
Merge branch 'GREsau:master' into better-map-support
2 parents bb383a9 + ac0b651 commit ab4ba87

File tree

46 files changed

+2777
-420
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2777
-420
lines changed

.github/workflows/ci.yml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
allow_failure: true
2424
fail-fast: false
2525
steps:
26-
- uses: actions/checkout@v4
26+
- uses: actions/checkout@v5
2727
- name: Downgrade packages for MSRV
2828
if: matrix.rust == '1.74.0'
2929
run: |
@@ -45,3 +45,32 @@ jobs:
4545
run: cargo test --verbose --all-features --no-fail-fast
4646
continue-on-error: ${{ matrix.allow_failure }}
4747
working-directory: ./schemars_derive
48+
- name: Check with latest dependency versions
49+
if: matrix.rust != '1.74.0'
50+
run: |
51+
cargo update
52+
cargo check --all-features --verbose --example main
53+
- name: Check with minimal dependency versions
54+
run: |
55+
# Ignore dev-dependencies because they're irrelevant for consumers of schemars
56+
# Modified workaround from https://github.com/SpriteOvO/spdlog-rs/blob/09b1f25ad7654a5535eea1497bd70382fb0c16ae/.github/workflows/ci.yml#L110-L112
57+
sed -i 's/\[dev-dependencies]/[package.metadata.workaround-avoid-dev-deps]/g' ./schemars/Cargo.toml ./schemars_derive/Cargo.toml
58+
cargo +nightly generate-lockfile -Z direct-minimal-versions
59+
cargo check --all-features --verbose --example main
60+
61+
clippy:
62+
runs-on: ubuntu-latest
63+
steps:
64+
- uses: actions/checkout@v5
65+
- uses: dtolnay/rust-toolchain@clippy
66+
- run: cargo clippy --all-targets -- -D warnings
67+
- run: cargo clippy --all-targets --all-features -- -D warnings
68+
69+
semver-checks:
70+
runs-on: ubuntu-latest
71+
steps:
72+
- uses: actions/checkout@v5
73+
- name: Check semver
74+
uses: obi1kenobi/cargo-semver-checks-action@v2
75+
with:
76+
release-type: minor

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
# Changelog
22

3+
## [1.1.0] - 2025-11-05
4+
5+
### Added
6+
7+
- Public functions that have no side-effects are now marked with [`#[must_use]`](https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute) so that they report a lint warning when the returned value is unused, as this likely indicates a mistake.
8+
9+
### Fixed
10+
11+
- Improve accuracy of schemas for flattened enums, in particular: unit variants of externally-tagged enums, and enums wrapped in `Option<>`. (https://github.com/GREsau/schemars/issues/464 / https://github.com/GREsau/schemars/pull/483)
12+
13+
## [1.0.5] - 2025-11-02
14+
15+
### Fixed
16+
17+
- Fix `schema.pointer_mut()` to resolve URI fragment identifiers like `#/$defs/foo`, matching current behaviour of `schema.pointer()` (https://github.com/GREsau/schemars/issues/478 / https://github.com/GREsau/schemars/pull/479)
18+
19+
## [1.0.4] - 2025-07-06
20+
21+
### Fixed
22+
23+
- Fix `JsonSchema` impl on [atomic](https://doc.rust-lang.org/std/sync/atomic/) types being ignored on non-nightly compilers due to a buggy `cfg` check (https://github.com/GREsau/schemars/issues/453)
24+
- Fix compatibility with minimal dependency versions, e.g. old(-ish) versions of `syn` (https://github.com/GREsau/schemars/issues/450)
25+
- Fix derive for empty tuple variants (https://github.com/GREsau/schemars/issues/455)
26+
327
## [1.0.3] - 2025-06-28
428

529
### Fixed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,5 +290,5 @@ For example, to implement `JsonSchema` on types from `chrono`, enable it as a fe
290290

291291
```toml
292292
[dependencies]
293-
schemars = { version = "0.9.0", features = ["chrono04"] }
293+
schemars = { version = "1.0", features = ["chrono04"] }
294294
```

docs/_includes/attributes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ Serde docs: [container](https://serde.rs/container-attrs.html#transparent)
215215

216216
</h3>
217217

218-
Where-clause for the JsonSchema impl. This replaces any trait bounds inferred by schemars. Schemars does **not** use trait bounds from `#[serde(bound)]` attributes.
218+
Where-clause for the `JsonSchema` impl. This replaces any trait bounds inferred by schemars. Schemars does **not** use trait bounds from `#[serde(bound)]` attributes.
219219

220220
Serde docs: [container](https://serde.rs/container-attrs.html#bound)
221221

docs/_includes/deriving.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Deriving JsonSchema
1+
# Deriving `JsonSchema`
22

33
The most important trait in Schemars is `JsonSchema`, and the most important function of that trait is `json_schema(...)` which returns a JSON schema describing the type. Implementing this manually on many types would be slow and error-prone, so Schemars includes a derive macro which can implement that trait for you. Any derived implementation of `JsonSchema` should create a schema that describes the JSON representation of the type if it were to be serialized by serde_json.
44

docs/examples/3-schemars_attrs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ nav_order: 3
55
summary: "Deriving JsonSchema on types that use #[schemars] attributes to customise serialization behaviour."
66
---
77

8-
# Using Serde Attributes
8+
# Using Schemars Attributes
99

1010
`#[serde(...)]` attributes can be overriden (or replaced) with `#[schemars(...)]` attributes, which behave identically. You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde.
1111

schemars/Cargo.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "schemars"
33
description = "Generate JSON Schemas from Rust code"
44
homepage = "https://graham.cool/schemars/"
55
repository = "https://github.com/GREsau/schemars"
6-
version = "1.0.3"
6+
version = "1.1.0"
77
authors = ["Graham Esau <gesau@hotmail.co.uk>"]
88
edition = "2021"
99
license = "MIT"
@@ -13,10 +13,10 @@ categories = ["encoding", "no-std"]
1313
rust-version = "1.74"
1414

1515
[dependencies]
16-
schemars_derive = { version = "=1.0.3", optional = true, path = "../schemars_derive" }
17-
serde = { version = "1.0", default-features = false, features = ["alloc"]}
16+
schemars_derive = { version = "=1.1.0", optional = true, path = "../schemars_derive" }
17+
serde = { version = "1.0.194", default-features = false, features = ["alloc"]}
1818
serde_json = { version = "1.0.127", default-features = false, features = ["alloc"] }
19-
dyn-clone = "1.0"
19+
dyn-clone = "1.0.17"
2020
ref-cast = "1.0.22"
2121

2222
# optional dependencies
@@ -25,9 +25,9 @@ bigdecimal04 = { version = "0.4", default-features = false, optional = true, pac
2525
bytes1 = { version = "1.0", default-features = false, optional = true, package = "bytes" }
2626
chrono04 = { version = "0.4.39", default-features = false, optional = true, package = "chrono" }
2727
either1 = { version = "1.3", default-features = false, optional = true, package = "either" }
28-
indexmap2 = { version = "2.0", default-features = false, optional = true, package = "indexmap" }
28+
indexmap2 = { version = "2.2.3", default-features = false, optional = true, package = "indexmap" }
2929
jiff02 = { version = "0.2", default-features = false, optional = true, package = "jiff" }
30-
rust_decimal1 = { version = "1", default-features = false, optional = true, package = "rust_decimal" }
30+
rust_decimal1 = { version = "1.13", default-features = false, optional = true, package = "rust_decimal" }
3131
semver1 = { version = "1.0.9", default-features = false, optional = true, package = "semver" }
3232
smallvec1 = { version = "1.0", default-features = false, optional = true, package = "smallvec" }
3333
smol_str02 = { version = "0.2.1", default-features = false, optional = true, package = "smol_str" }

schemars/src/_private/mod.rs

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,103 @@ pub fn json_schema_for_internally_tagged_enum_newtype_variant<T: ?Sized + JsonSc
4646
generator.subschema_for::<T>()
4747
}
4848

49-
// Helper for generating schemas for flattened `Option` fields.
49+
// Helper for generating schemas for flattened enums and `Option` fields.
5050
pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
5151
generator: &mut SchemaGenerator,
5252
required: bool,
5353
) -> Schema {
54-
let mut schema = T::_schemars_private_non_optional_json_schema(generator);
54+
/// Non-generic inner function to reduce monomorphization overhead
55+
fn inner(mut schema: Schema, is_optional: bool) -> Schema {
56+
// Special handling for externally-tagged enums with unit variants.
57+
// Unit variants are normally serialized as strings, but when flattened, are serialized
58+
// as objects like `{ "VariantName": null }`
59+
if let Some(unit_variants) = remove_unit_variants(&mut schema) {
60+
if let Value::Array(one_of) = schema
61+
.ensure_object()
62+
.entry("oneOf")
63+
.or_insert(Value::Array(Vec::new()))
64+
{
65+
one_of.extend(
66+
unit_variants
67+
.iter()
68+
.filter_map(Value::as_str)
69+
.map(|variant| {
70+
json!({
71+
"type": "object",
72+
"properties": {
73+
variant: {
74+
"type": "null"
75+
}
76+
},
77+
"required": [variant],
78+
})
79+
}),
80+
);
81+
}
82+
}
83+
84+
if is_optional {
85+
schema.remove("required");
86+
87+
// Handle `Option<>` of externally/internally/adjacently-tagged enums
88+
if let Some(one_of) = schema.remove("oneOf") {
89+
// We can't just add `{}` to the existing `oneOf`, because its items must be
90+
// mutually-exclusive, and `{}` matches everything.
91+
flatten(
92+
&mut schema,
93+
json_schema!({
94+
"anyOf": [
95+
{ "oneOf": one_of },
96+
{}
97+
]
98+
}),
99+
);
100+
}
101+
102+
// Handle `Option<>` of untagged enums
103+
if let Some(Value::Array(any_of)) = schema.get_mut("anyOf") {
104+
let empty_object = Value::Object(Map::new());
105+
if !any_of.contains(&empty_object) {
106+
any_of.push(empty_object);
107+
}
108+
}
109+
}
55110

56-
if T::_schemars_private_is_option() && !required {
57-
schema.remove("required");
111+
// Always allow aditional/unevaluated properties, because the outer struct determines
112+
// whether it denies unknown fields.
113+
AllowUnknownProperties::default().transform(&mut schema);
114+
115+
schema
58116
}
59117

60-
// Always allow aditional/unevaluated properties, because the outer struct determines
61-
// whether it denies unknown fields.
62-
AllowUnknownProperties::default().transform(&mut schema);
118+
fn remove_unit_variants(schema: &mut Schema) -> Option<Vec<Value>> {
119+
// For enums that only have unit variants, all variants are in `enum`
120+
if schema.get("type").and_then(Value::as_str) == Some("string") {
121+
// Remove both `"enum": [...]`...
122+
if let Some(Value::Array(a)) = schema.remove("enum") {
123+
// ...and `"type": "string"`, since the variants are not serialized as strings
124+
schema.remove("type");
125+
return Some(a);
126+
}
127+
}
128+
129+
// For enums that have unit and other variants, unit variants are in the first `oneOf` item
130+
let one_of = schema.get_mut("oneOf")?.as_array_mut()?;
131+
let first = <&mut Schema>::try_from(one_of.get_mut(0)?).ok()?;
132+
if first.get("type").and_then(Value::as_str) == Some("string") {
133+
if let Some(Value::Array(a)) = first.remove("enum") {
134+
one_of.remove(0);
135+
return Some(a);
136+
}
137+
}
138+
139+
None
140+
}
63141

64-
schema
142+
inner(
143+
T::_schemars_private_non_optional_json_schema(generator),
144+
T::_schemars_private_is_option() && !required,
145+
)
65146
}
66147

67148
#[derive(Default)]
@@ -116,6 +197,7 @@ impl<T: Serialize> MaybeSerializeWrapper<T> {
116197
}
117198

118199
/// Create a schema for a unit enum variant
200+
#[must_use]
119201
pub fn new_unit_enum_variant(variant: &str) -> Schema {
120202
json_schema!({
121203
"type": "string",
@@ -143,6 +225,7 @@ macro_rules! _schemars_maybe_schema_id {
143225
pub struct MaybeJsonSchemaWrapper<T: ?Sized>(core::marker::PhantomData<T>);
144226

145227
pub trait NoJsonSchema {
228+
#[must_use]
146229
fn maybe_schema_id() -> Cow<'static, str> {
147230
Cow::Borrowed(core::any::type_name::<Self>())
148231
}
@@ -151,13 +234,15 @@ pub trait NoJsonSchema {
151234
impl<T: ?Sized> NoJsonSchema for T {}
152235

153236
impl<T: JsonSchema + ?Sized> MaybeJsonSchemaWrapper<T> {
237+
#[must_use]
154238
pub fn maybe_schema_id() -> Cow<'static, str> {
155239
T::schema_id()
156240
}
157241
}
158242

159243
/// Create a schema for an externally tagged enum variant
160244
#[allow(clippy::needless_pass_by_value)]
245+
#[must_use]
161246
pub fn new_externally_tagged_enum_variant(variant: &str, sub_schema: Schema) -> Schema {
162247
// TODO: this can be optimised by inserting the `sub_schema` as a `Value` rather than
163248
// using the `json_schema!` macro which borrows and serializes the sub_schema

schemars/src/_private/rustdoc.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#[must_use]
12
pub const fn get_title_and_description(doc: &str) -> (&str, &str) {
23
let doc_bytes = trim_ascii(doc.as_bytes());
34

0 commit comments

Comments
 (0)