Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions schemars/tests/integration/attr_order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use crate::prelude::*;
use pretty_assertions::assert_eq;
use schemars::Schema;
use std::fmt::Write;

// This test ensures that `extend` and `transform` attributes are applied after other attributes,
// and transforms are applied in the order they are defined.

#[derive(JsonSchema, Deserialize, Serialize)]
#[schemars(transform = suffix_description, description = "[Overwritten]", extend("description" = "The enum", "suffix" = "..."))]
#[serde(untagged)]
enum Untagged {
#[schemars(transform = suffix_description, description = "The variant", extend("suffix" = "?"))]
A {
#[schemars(transform = suffix_description, description = "The field", extend("suffix" = "!"))]
#[schemars(range(min = 1), transform = remove_minimum_and_default)]
#[serde(default)]
i: i32,
},
}

fn suffix_description(schema: &mut Schema) {
let minimum = schema.get("minimum").map(Value::to_string);

let Some(Value::String(suffix)) = schema.remove("suffix") else {
panic!("expected `suffix` to be present and a string");
};
let Some(Value::String(description)) = schema.get_mut("description") else {
panic!("expected `description` to be present and a string");
};

description.push_str(&suffix);

if let Some(minimum) = minimum {
write!(description, " (At least {})", minimum).unwrap();
}
}

fn remove_minimum_and_default(schema: &mut Schema) {
schema.remove("minimum");
schema.remove("default");
}

#[test]
fn attributes_applied_in_order() {
test!(Untagged).assert_snapshot().custom(|schema, _| {
assert_eq!(schema.pointer("/description"), Some(&json!("The enum...")));
assert_eq!(
schema.pointer("/anyOf/0/description"),
Some(&json!("The variant?"))
);
assert_eq!(
schema.pointer("/anyOf/0/properties/i/description"),
Some(&json!("The field! (At least 1)"))
);
assert_eq!(schema.pointer("/anyOf/0/properties/i/default"), None);
assert_eq!(schema.pointer("/anyOf/0/properties/i/minimum"), None);
});
}
1 change: 1 addition & 0 deletions schemars/tests/integration/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#[cfg(feature = "arrayvec07")]
mod arrayvec;
mod attr_order;
mod bound;
#[cfg(feature = "bytes1")]
mod bytes;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Untagged",
"description": "The enum...",
"anyOf": [
{
"description": "The variant?",
"type": "object",
"properties": {
"i": {
"description": "The field! (At least 1)",
"type": "integer",
"format": "int32"
}
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Untagged",
"description": "The enum...",
"anyOf": [
{
"description": "The variant?",
"type": "object",
"properties": {
"i": {
"description": "The field! (At least 1)",
"type": "integer",
"format": "int32"
}
},
"required": [
"i"
]
}
]
}
19 changes: 10 additions & 9 deletions schemars_derive/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod from_serde;

use crate::attr::{ContainerAttrs, FieldAttrs, VariantAttrs};
use crate::idents::{GENERATOR, SCHEMA};
use crate::schema_exprs::SchemaExpr;
use from_serde::FromSerde;
use proc_macro2::TokenStream;
use serde_derive_internals::ast as serde_ast;
Expand Down Expand Up @@ -65,8 +66,8 @@ impl<'a> Container<'a> {
None
}

pub fn add_mutators(&self, mutators: &mut Vec<TokenStream>) {
self.attrs.common.add_mutators(mutators);
pub fn add_mutators(&self, expr: &mut SchemaExpr) {
self.attrs.common.add_mutators(expr);
}

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

pub fn add_mutators(&self, mutators: &mut Vec<TokenStream>) {
self.attrs.common.add_mutators(mutators);
pub fn add_mutators(&self, expr: &mut SchemaExpr) {
self.attrs.common.add_mutators(expr);
}

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

pub fn add_mutators(&self, mutators: &mut Vec<TokenStream>) {
self.attrs.common.add_mutators(mutators);
self.attrs.validation.add_mutators(mutators);
pub fn add_mutators(&self, expr: &mut SchemaExpr) {
self.attrs.common.add_mutators(expr);
self.attrs.validation.add_mutators(expr);

if self.serde_attrs.skip_deserializing() {
mutators.push(quote! {
expr.mutators.push(quote! {
#SCHEMA.insert("readOnly".into(), true.into());
});
}
if self.serde_attrs.skip_serializing() {
mutators.push(quote! {
expr.mutators.push(quote! {
#SCHEMA.insert("writeOnly".into(), true.into());
});
}
Expand Down
11 changes: 8 additions & 3 deletions schemars_derive/src/attr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use validation::ValidationAttrs;

use crate::ast::Data;
use crate::idents::SCHEMA;
use crate::schema_exprs::SchemaExpr;

pub use custom_meta::*;
pub use schemars_to_serde::process_serde_attrs;
Expand Down Expand Up @@ -179,7 +180,9 @@ impl CommonAttrs {
)
}

pub fn add_mutators(&self, mutators: &mut Vec<TokenStream>) {
pub fn add_mutators(&self, expr: &mut SchemaExpr) {
let mutators = &mut expr.mutators;

let mut title = self.title.as_ref().map(ToTokens::to_token_stream);
let mut description = self.description.as_ref().map(ToTokens::to_token_stream);
if let Some(doc) = &self.doc {
Expand Down Expand Up @@ -224,14 +227,16 @@ impl CommonAttrs {
});
}

// Post-Mutators - extensions and transforms will be applied after all other mutators

for (k, v) in &self.extensions {
mutators.push(quote! {
expr.post_mutators.push(quote! {
#SCHEMA.insert(#k.into(), schemars::_private::serde_json::json!(#v));
});
}

for transform in &self.transforms {
mutators.push(quote_spanned! {transform.span()=>
expr.post_mutators.push(quote_spanned! {transform.span()=>
schemars::transform::Transform::transform(&mut #transform, &mut #SCHEMA);
});
}
Expand Down
6 changes: 3 additions & 3 deletions schemars_derive/src/attr/validation.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use proc_macro2::TokenStream;
use syn::Expr;

use crate::idents::SCHEMA;
use crate::{idents::SCHEMA, schema_exprs::SchemaExpr};

use super::{
parse_meta::{
Expand Down Expand Up @@ -66,8 +66,8 @@ pub struct ValidationAttrs {
}

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

fn add_mutators2(&self, mutators: &mut Vec<TokenStream>, mut_ref_schema: &TokenStream) {
Expand Down
28 changes: 17 additions & 11 deletions schemars_derive/src/schema_exprs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ use syn::spanned::Spanned;

pub struct SchemaExpr {
/// Definitions for types or functions that may be used within the creator or mutators
definitions: Vec<TokenStream>,
pub definitions: Vec<TokenStream>,
/// An expression that produces a `Schema`
creator: TokenStream,
pub creator: TokenStream,
/// Statements (including terminating semicolon) that mutate a var `schema` of type `Schema`
mutators: Vec<TokenStream>,
pub mutators: Vec<TokenStream>,
/// Same as `mutators`, but always applied last
pub post_mutators: Vec<TokenStream>,
}

impl From<TokenStream> for SchemaExpr {
Expand All @@ -20,6 +22,7 @@ impl From<TokenStream> for SchemaExpr {
definitions: Vec::new(),
creator,
mutators: Vec::new(),
post_mutators: Vec::new(),
}
}
}
Expand All @@ -30,14 +33,16 @@ impl ToTokens for SchemaExpr {
definitions,
creator,
mutators,
post_mutators,
} = self;

tokens.extend(if mutators.is_empty() {
tokens.extend(if mutators.is_empty() && post_mutators.is_empty() {
quote!({
#(#definitions)*
#creator
})
} else {
let mutators = mutators.iter().chain(post_mutators.iter());
quote!({
#(#definitions)*
let mut #SCHEMA = #creator;
Expand Down Expand Up @@ -105,7 +110,7 @@ pub fn expr_for_container(cont: &Container) -> SchemaExpr {
}
};

cont.add_mutators(&mut schema_expr.mutators);
cont.add_mutators(&mut schema_expr);

schema_expr
}
Expand Down Expand Up @@ -152,7 +157,7 @@ pub fn expr_for_repr(cont: &Container) -> Result<SchemaExpr, syn::Error> {
schemars::Schema::from(map)
}));

cont.add_mutators(&mut schema_expr.mutators);
cont.add_mutators(&mut schema_expr);

Ok(schema_expr)
}
Expand Down Expand Up @@ -193,7 +198,7 @@ fn expr_for_field(
let mut schema_expr = SchemaExpr::from(schema_expr);

schema_expr.definitions.extend(type_def);
field.add_mutators(&mut schema_expr.mutators);
field.add_mutators(&mut schema_expr);

schema_expr
}
Expand Down Expand Up @@ -331,7 +336,7 @@ fn expr_for_external_tagged_enum<'a>(
}
});

variant.add_mutators(&mut schema_expr.mutators);
variant.add_mutators(&mut schema_expr);

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

variant.add_mutators(&mut schema_expr.mutators);
variant.add_mutators(&mut schema_expr);

(Some(variant), schema_expr)
})
Expand Down Expand Up @@ -453,7 +458,7 @@ fn expr_for_adjacent_tagged_enum<'a>(
#set_additional_properties
})));

variant.add_mutators(&mut outer_schema.mutators);
variant.add_mutators(&mut outer_schema);

(Some(variant), outer_schema)
})
Expand Down Expand Up @@ -592,7 +597,7 @@ fn expr_for_untagged_enum_variant(
});
}

variant.add_mutators(&mut schema_expr.mutators);
variant.add_mutators(&mut schema_expr);
}

schema_expr
Expand Down Expand Up @@ -760,6 +765,7 @@ fn expr_for_struct(
#set_additional_properties
})),
mutators: properties,
post_mutators: Vec::new(),
}
}

Expand Down