Skip to content

Commit 4081b98

Browse files
authored
Apply extend/transform attributes after any other attributes (#506)
Fixes #505
1 parent 5bfec04 commit 4081b98

File tree

8 files changed

+137
-26
lines changed

8 files changed

+137
-26
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use crate::prelude::*;
2+
use pretty_assertions::assert_eq;
3+
use schemars::Schema;
4+
use std::fmt::Write;
5+
6+
// This test ensures that `extend` and `transform` attributes are applied after other attributes,
7+
// and transforms are applied in the order they are defined.
8+
9+
#[derive(JsonSchema, Deserialize, Serialize)]
10+
#[schemars(transform = suffix_description, description = "[Overwritten]", extend("description" = "The enum", "suffix" = "..."))]
11+
#[serde(untagged)]
12+
enum Untagged {
13+
#[schemars(transform = suffix_description, description = "The variant", extend("suffix" = "?"))]
14+
A {
15+
#[schemars(transform = suffix_description, description = "The field", extend("suffix" = "!"))]
16+
#[schemars(range(min = 1), transform = remove_minimum_and_default)]
17+
#[serde(default)]
18+
i: i32,
19+
},
20+
}
21+
22+
fn suffix_description(schema: &mut Schema) {
23+
let minimum = schema.get("minimum").map(Value::to_string);
24+
25+
let Some(Value::String(suffix)) = schema.remove("suffix") else {
26+
panic!("expected `suffix` to be present and a string");
27+
};
28+
let Some(Value::String(description)) = schema.get_mut("description") else {
29+
panic!("expected `description` to be present and a string");
30+
};
31+
32+
description.push_str(&suffix);
33+
34+
if let Some(minimum) = minimum {
35+
write!(description, " (At least {})", minimum).unwrap();
36+
}
37+
}
38+
39+
fn remove_minimum_and_default(schema: &mut Schema) {
40+
schema.remove("minimum");
41+
schema.remove("default");
42+
}
43+
44+
#[test]
45+
fn attributes_applied_in_order() {
46+
test!(Untagged).assert_snapshot().custom(|schema, _| {
47+
assert_eq!(schema.pointer("/description"), Some(&json!("The enum...")));
48+
assert_eq!(
49+
schema.pointer("/anyOf/0/description"),
50+
Some(&json!("The variant?"))
51+
);
52+
assert_eq!(
53+
schema.pointer("/anyOf/0/properties/i/description"),
54+
Some(&json!("The field! (At least 1)"))
55+
);
56+
assert_eq!(schema.pointer("/anyOf/0/properties/i/default"), None);
57+
assert_eq!(schema.pointer("/anyOf/0/properties/i/minimum"), None);
58+
});
59+
}

schemars/tests/integration/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#[cfg(feature = "arrayvec07")]
44
mod arrayvec;
5+
mod attr_order;
56
mod bound;
67
#[cfg(feature = "bytes1")]
78
mod bytes;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"title": "Untagged",
4+
"description": "The enum...",
5+
"anyOf": [
6+
{
7+
"description": "The variant?",
8+
"type": "object",
9+
"properties": {
10+
"i": {
11+
"description": "The field! (At least 1)",
12+
"type": "integer",
13+
"format": "int32"
14+
}
15+
}
16+
}
17+
]
18+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"title": "Untagged",
4+
"description": "The enum...",
5+
"anyOf": [
6+
{
7+
"description": "The variant?",
8+
"type": "object",
9+
"properties": {
10+
"i": {
11+
"description": "The field! (At least 1)",
12+
"type": "integer",
13+
"format": "int32"
14+
}
15+
},
16+
"required": [
17+
"i"
18+
]
19+
}
20+
]
21+
}

schemars_derive/src/ast/mod.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod from_serde;
22

33
use crate::attr::{ContainerAttrs, FieldAttrs, VariantAttrs};
44
use crate::idents::{GENERATOR, SCHEMA};
5+
use crate::schema_exprs::SchemaExpr;
56
use from_serde::FromSerde;
67
use proc_macro2::TokenStream;
78
use serde_derive_internals::ast as serde_ast;
@@ -65,8 +66,8 @@ impl<'a> Container<'a> {
6566
None
6667
}
6768

68-
pub fn add_mutators(&self, mutators: &mut Vec<TokenStream>) {
69-
self.attrs.common.add_mutators(mutators);
69+
pub fn add_mutators(&self, expr: &mut SchemaExpr) {
70+
self.attrs.common.add_mutators(expr);
7071
}
7172

7273
pub fn name(&'a self) -> std::borrow::Cow<'a, str> {
@@ -89,8 +90,8 @@ impl Variant<'_> {
8990
matches!(self.style, serde_ast::Style::Unit)
9091
}
9192

92-
pub fn add_mutators(&self, mutators: &mut Vec<TokenStream>) {
93-
self.attrs.common.add_mutators(mutators);
93+
pub fn add_mutators(&self, expr: &mut SchemaExpr) {
94+
self.attrs.common.add_mutators(expr);
9495
}
9596

9697
pub fn with_contract_check(&self, action: TokenStream) -> TokenStream {
@@ -107,17 +108,17 @@ impl Field<'_> {
107108
Name(self.serde_attrs.name())
108109
}
109110

110-
pub fn add_mutators(&self, mutators: &mut Vec<TokenStream>) {
111-
self.attrs.common.add_mutators(mutators);
112-
self.attrs.validation.add_mutators(mutators);
111+
pub fn add_mutators(&self, expr: &mut SchemaExpr) {
112+
self.attrs.common.add_mutators(expr);
113+
self.attrs.validation.add_mutators(expr);
113114

114115
if self.serde_attrs.skip_deserializing() {
115-
mutators.push(quote! {
116+
expr.mutators.push(quote! {
116117
#SCHEMA.insert("readOnly".into(), true.into());
117118
});
118119
}
119120
if self.serde_attrs.skip_serializing() {
120-
mutators.push(quote! {
121+
expr.mutators.push(quote! {
121122
#SCHEMA.insert("writeOnly".into(), true.into());
122123
});
123124
}

schemars_derive/src/attr/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use validation::ValidationAttrs;
1818

1919
use crate::ast::Data;
2020
use crate::idents::SCHEMA;
21+
use crate::schema_exprs::SchemaExpr;
2122

2223
pub use custom_meta::*;
2324
pub use schemars_to_serde::process_serde_attrs;
@@ -179,7 +180,9 @@ impl CommonAttrs {
179180
)
180181
}
181182

182-
pub fn add_mutators(&self, mutators: &mut Vec<TokenStream>) {
183+
pub fn add_mutators(&self, expr: &mut SchemaExpr) {
184+
let mutators = &mut expr.mutators;
185+
183186
let mut title = self.title.as_ref().map(ToTokens::to_token_stream);
184187
let mut description = self.description.as_ref().map(ToTokens::to_token_stream);
185188
if let Some(doc) = &self.doc {
@@ -224,14 +227,16 @@ impl CommonAttrs {
224227
});
225228
}
226229

230+
// Post-Mutators - extensions and transforms will be applied after all other mutators
231+
227232
for (k, v) in &self.extensions {
228-
mutators.push(quote! {
233+
expr.post_mutators.push(quote! {
229234
#SCHEMA.insert(#k.into(), schemars::_private::serde_json::json!(#v));
230235
});
231236
}
232237

233238
for transform in &self.transforms {
234-
mutators.push(quote_spanned! {transform.span()=>
239+
expr.post_mutators.push(quote_spanned! {transform.span()=>
235240
schemars::transform::Transform::transform(&mut #transform, &mut #SCHEMA);
236241
});
237242
}

schemars_derive/src/attr/validation.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use proc_macro2::TokenStream;
22
use syn::Expr;
33

4-
use crate::idents::SCHEMA;
4+
use crate::{idents::SCHEMA, schema_exprs::SchemaExpr};
55

66
use super::{
77
parse_meta::{
@@ -66,8 +66,8 @@ pub struct ValidationAttrs {
6666
}
6767

6868
impl ValidationAttrs {
69-
pub fn add_mutators(&self, mutators: &mut Vec<TokenStream>) {
70-
self.add_mutators2(mutators, &quote!(&mut #SCHEMA));
69+
pub fn add_mutators(&self, expr: &mut SchemaExpr) {
70+
self.add_mutators2(&mut expr.mutators, &quote!(&mut #SCHEMA));
7171
}
7272

7373
fn add_mutators2(&self, mutators: &mut Vec<TokenStream>, mut_ref_schema: &TokenStream) {

schemars_derive/src/schema_exprs.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ use syn::spanned::Spanned;
77

88
pub struct SchemaExpr {
99
/// Definitions for types or functions that may be used within the creator or mutators
10-
definitions: Vec<TokenStream>,
10+
pub definitions: Vec<TokenStream>,
1111
/// An expression that produces a `Schema`
12-
creator: TokenStream,
12+
pub creator: TokenStream,
1313
/// Statements (including terminating semicolon) that mutate a var `schema` of type `Schema`
14-
mutators: Vec<TokenStream>,
14+
pub mutators: Vec<TokenStream>,
15+
/// Same as `mutators`, but always applied last
16+
pub post_mutators: Vec<TokenStream>,
1517
}
1618

1719
impl From<TokenStream> for SchemaExpr {
@@ -20,6 +22,7 @@ impl From<TokenStream> for SchemaExpr {
2022
definitions: Vec::new(),
2123
creator,
2224
mutators: Vec::new(),
25+
post_mutators: Vec::new(),
2326
}
2427
}
2528
}
@@ -30,14 +33,16 @@ impl ToTokens for SchemaExpr {
3033
definitions,
3134
creator,
3235
mutators,
36+
post_mutators,
3337
} = self;
3438

35-
tokens.extend(if mutators.is_empty() {
39+
tokens.extend(if mutators.is_empty() && post_mutators.is_empty() {
3640
quote!({
3741
#(#definitions)*
3842
#creator
3943
})
4044
} else {
45+
let mutators = mutators.iter().chain(post_mutators.iter());
4146
quote!({
4247
#(#definitions)*
4348
let mut #SCHEMA = #creator;
@@ -105,7 +110,7 @@ pub fn expr_for_container(cont: &Container) -> SchemaExpr {
105110
}
106111
};
107112

108-
cont.add_mutators(&mut schema_expr.mutators);
113+
cont.add_mutators(&mut schema_expr);
109114

110115
schema_expr
111116
}
@@ -152,7 +157,7 @@ pub fn expr_for_repr(cont: &Container) -> Result<SchemaExpr, syn::Error> {
152157
schemars::Schema::from(map)
153158
}));
154159

155-
cont.add_mutators(&mut schema_expr.mutators);
160+
cont.add_mutators(&mut schema_expr);
156161

157162
Ok(schema_expr)
158163
}
@@ -193,7 +198,7 @@ fn expr_for_field(
193198
let mut schema_expr = SchemaExpr::from(schema_expr);
194199

195200
schema_expr.definitions.extend(type_def);
196-
field.add_mutators(&mut schema_expr.mutators);
201+
field.add_mutators(&mut schema_expr);
197202

198203
schema_expr
199204
}
@@ -331,7 +336,7 @@ fn expr_for_external_tagged_enum<'a>(
331336
}
332337
});
333338

334-
variant.add_mutators(&mut schema_expr.mutators);
339+
variant.add_mutators(&mut schema_expr);
335340

336341
(Some(variant), schema_expr)
337342
}));
@@ -358,7 +363,7 @@ fn expr_for_internal_tagged_enum<'a>(
358363
schemars::_private::apply_internal_enum_variant_tag(&mut #SCHEMA, #tag_name, #name, #deny_unknown_fields);
359364
));
360365

361-
variant.add_mutators(&mut schema_expr.mutators);
366+
variant.add_mutators(&mut schema_expr);
362367

363368
(Some(variant), schema_expr)
364369
})
@@ -453,7 +458,7 @@ fn expr_for_adjacent_tagged_enum<'a>(
453458
#set_additional_properties
454459
})));
455460

456-
variant.add_mutators(&mut outer_schema.mutators);
461+
variant.add_mutators(&mut outer_schema);
457462

458463
(Some(variant), outer_schema)
459464
})
@@ -592,7 +597,7 @@ fn expr_for_untagged_enum_variant(
592597
});
593598
}
594599

595-
variant.add_mutators(&mut schema_expr.mutators);
600+
variant.add_mutators(&mut schema_expr);
596601
}
597602

598603
schema_expr
@@ -760,6 +765,7 @@ fn expr_for_struct(
760765
#set_additional_properties
761766
})),
762767
mutators: properties,
768+
post_mutators: Vec::new(),
763769
}
764770
}
765771

0 commit comments

Comments
 (0)