Define Schema
as a newtype around serde_json::Value
(#289)
This commit is contained in:
parent
7f6a7b7e32
commit
342cd5fd09
79 changed files with 1410 additions and 2394 deletions
|
@ -325,119 +325,85 @@ impl ValidationAttrs {
|
|||
}
|
||||
|
||||
pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) {
|
||||
if let Some(apply_expr) = self.apply_to_schema_expr() {
|
||||
*schema_expr = quote! {
|
||||
{
|
||||
let mut schema = #schema_expr;
|
||||
#apply_expr
|
||||
schema
|
||||
}
|
||||
}
|
||||
let setters = self.make_setters(quote!(&mut schema));
|
||||
if !setters.is_empty() {
|
||||
*schema_expr = quote!({
|
||||
let mut schema = #schema_expr;
|
||||
#(#setters)*
|
||||
schema
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_to_schema_expr(&self) -> Option<TokenStream> {
|
||||
let mut array_validation = Vec::new();
|
||||
let mut number_validation = Vec::new();
|
||||
let mut object_validation = Vec::new();
|
||||
let mut string_validation = Vec::new();
|
||||
fn make_setters(&self, mut_schema: impl ToTokens) -> Vec<TokenStream> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
if let Some(length_min) = self.length_min.as_ref().or(self.length_equal.as_ref()) {
|
||||
string_validation.push(quote! {
|
||||
validation.min_length = Some(#length_min as u32);
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "string", "minLength", #length_min);
|
||||
});
|
||||
array_validation.push(quote! {
|
||||
validation.min_items = Some(#length_min as u32);
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "array", "minItems", #length_min);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(length_max) = self.length_max.as_ref().or(self.length_equal.as_ref()) {
|
||||
string_validation.push(quote! {
|
||||
validation.max_length = Some(#length_max as u32);
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "string", "maxLength", #length_max);
|
||||
});
|
||||
array_validation.push(quote! {
|
||||
validation.max_items = Some(#length_max as u32);
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "array", "maxItems", #length_max);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(range_min) = &self.range_min {
|
||||
number_validation.push(quote! {
|
||||
validation.minimum = Some(#range_min as f64);
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "number", "minimum", #range_min);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(range_max) = &self.range_max {
|
||||
number_validation.push(quote! {
|
||||
validation.maximum = Some(#range_max as f64);
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "number", "maximum", #range_max);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(regex) = &self.regex {
|
||||
string_validation.push(quote! {
|
||||
validation.pattern = Some(#regex.to_string());
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "string", "pattern", #regex);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(contains) = &self.contains {
|
||||
object_validation.push(quote! {
|
||||
validation.required.insert(#contains.to_string());
|
||||
result.push(quote! {
|
||||
schemars::_private::append_required(#mut_schema, #contains);
|
||||
});
|
||||
|
||||
if self.regex.is_none() {
|
||||
let pattern = crate::regex_syntax::escape(contains);
|
||||
string_validation.push(quote! {
|
||||
validation.pattern = Some(#pattern.to_string());
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "string", "pattern", #pattern);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let format = self.format.as_ref().map(|f| {
|
||||
let f = f.schema_str();
|
||||
quote! {
|
||||
schema_object.format = Some(#f.to_string());
|
||||
}
|
||||
});
|
||||
|
||||
let inner_validation = self
|
||||
.inner
|
||||
.as_deref()
|
||||
.and_then(|inner| inner.apply_to_schema_expr())
|
||||
.map(|apply_expr| {
|
||||
quote! {
|
||||
if schema_object.has_type(schemars::schema::InstanceType::Array) {
|
||||
if let Some(schemars::schema::SingleOrVec::Single(inner_schema)) = &mut schema_object.array().items {
|
||||
let mut schema = &mut **inner_schema;
|
||||
#apply_expr
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let array_validation = wrap_array_validation(array_validation);
|
||||
let number_validation = wrap_number_validation(number_validation);
|
||||
let object_validation = wrap_object_validation(object_validation);
|
||||
let string_validation = wrap_string_validation(string_validation);
|
||||
|
||||
if array_validation.is_some()
|
||||
|| number_validation.is_some()
|
||||
|| object_validation.is_some()
|
||||
|| string_validation.is_some()
|
||||
|| format.is_some()
|
||||
|| inner_validation.is_some()
|
||||
{
|
||||
Some(quote! {
|
||||
if let schemars::schema::Schema::Object(schema_object) = &mut schema {
|
||||
#array_validation
|
||||
#number_validation
|
||||
#object_validation
|
||||
#string_validation
|
||||
#format
|
||||
#inner_validation
|
||||
}
|
||||
if let Some(format) = &self.format {
|
||||
let f = format.schema_str();
|
||||
result.push(quote! {
|
||||
schema.ensure_object().insert("format".to_owned(), #f.into());
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(inner) = &self.inner {
|
||||
let inner_setters = inner.make_setters(quote!(schema));
|
||||
if !inner_setters.is_empty() {
|
||||
result.push(quote! {
|
||||
schemars::_private::apply_inner_validation(#mut_schema, |schema| { #(#inner_setters)* });
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -456,59 +422,6 @@ fn parse_lit_into_expr_path(
|
|||
})
|
||||
}
|
||||
|
||||
fn wrap_array_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
|
||||
if v.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
if schema_object.has_type(schemars::schema::InstanceType::Array) {
|
||||
let validation = schema_object.array();
|
||||
#(#v)*
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_number_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
|
||||
if v.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
if schema_object.has_type(schemars::schema::InstanceType::Integer)
|
||||
|| schema_object.has_type(schemars::schema::InstanceType::Number) {
|
||||
let validation = schema_object.number();
|
||||
#(#v)*
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_object_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
|
||||
if v.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
if schema_object.has_type(schemars::schema::InstanceType::Object) {
|
||||
let validation = schema_object.object();
|
||||
#(#v)*
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_string_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
|
||||
if v.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
if schema_object.has_type(schemars::schema::InstanceType::String) {
|
||||
let validation = schema_object.string();
|
||||
#(#v)*
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, expr: Expr) -> Option<Expr> {
|
||||
// this odd double-parsing is to make `-10` parsed as an Lit instead of an Expr::Unary
|
||||
let lit: Lit = match syn::parse2(expr.to_token_stream()) {
|
||||
|
|
|
@ -68,11 +68,11 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result<To
|
|||
<#ty as schemars::JsonSchema>::schema_id()
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
|
||||
<#ty as schemars::JsonSchema>::json_schema(gen)
|
||||
}
|
||||
|
||||
fn _schemars_private_non_optional_json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
fn _schemars_private_non_optional_json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
|
||||
<#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(gen)
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result<To
|
|||
#schema_id
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
|
||||
#schema_expr
|
||||
}
|
||||
};
|
||||
|
|
|
@ -13,32 +13,46 @@ pub struct SchemaMetadata<'a> {
|
|||
|
||||
impl<'a> SchemaMetadata<'a> {
|
||||
pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) {
|
||||
let setters = self.make_setters();
|
||||
if !setters.is_empty() {
|
||||
*schema_expr = quote! {{
|
||||
let mut schema = #schema_expr;
|
||||
let obj = schema.ensure_object();
|
||||
#(#setters)*
|
||||
schema
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_setters(&self) -> Vec<TokenStream> {
|
||||
let mut setters = Vec::<TokenStream>::new();
|
||||
|
||||
if let Some(title) = &self.title {
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_title(#schema_expr, #title)
|
||||
};
|
||||
setters.push(quote! {
|
||||
obj.insert("title".to_owned(), #title.into());
|
||||
});
|
||||
}
|
||||
if let Some(description) = &self.description {
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_description(#schema_expr, #description)
|
||||
};
|
||||
setters.push(quote! {
|
||||
obj.insert("description".to_owned(), #description.into());
|
||||
});
|
||||
}
|
||||
|
||||
if self.deprecated {
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_deprecated(#schema_expr, true)
|
||||
};
|
||||
setters.push(quote! {
|
||||
obj.insert("deprecated".to_owned(), true.into());
|
||||
});
|
||||
}
|
||||
|
||||
if self.read_only {
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_read_only(#schema_expr, true)
|
||||
};
|
||||
setters.push(quote! {
|
||||
obj.insert("readOnly".to_owned(), true.into());
|
||||
});
|
||||
}
|
||||
if self.write_only {
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_write_only(#schema_expr, true)
|
||||
};
|
||||
setters.push(quote! {
|
||||
obj.insert("writeOnly".to_owned(), true.into());
|
||||
});
|
||||
}
|
||||
|
||||
if !self.examples.is_empty() {
|
||||
|
@ -47,16 +61,19 @@ impl<'a> SchemaMetadata<'a> {
|
|||
schemars::_serde_json::value::to_value(#eg())
|
||||
}
|
||||
});
|
||||
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_examples(#schema_expr, [#(#examples),*].into_iter().flatten())
|
||||
};
|
||||
setters.push(quote! {
|
||||
obj.insert("examples".to_owned(), schemars::_serde_json::Value::Array([#(#examples),*].into_iter().flatten().collect()));
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(default) = &self.default {
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_default(#schema_expr, #default.and_then(|d| schemars::_schemars_maybe_to_value!(d)))
|
||||
};
|
||||
setters.push(quote! {
|
||||
if let Some(default) = #default.and_then(|d| schemars::_schemars_maybe_to_value!(d)) {
|
||||
obj.insert("default".to_owned(), default);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setters
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,9 +49,18 @@ pub fn expr_for_repr(cont: &Container) -> Result<TokenStream, syn::Error> {
|
|||
let enum_ident = &cont.ident;
|
||||
let variant_idents = variants.iter().map(|v| &v.ident);
|
||||
|
||||
let mut schema_expr = schema_object(quote! {
|
||||
instance_type: Some(schemars::schema::InstanceType::Integer.into()),
|
||||
enum_values: Some(vec![#((#enum_ident::#variant_idents as #repr_type).into()),*]),
|
||||
let mut schema_expr = quote!({
|
||||
let mut map = schemars::_serde_json::Map::new();
|
||||
map.insert("type".to_owned(), "integer".into());
|
||||
map.insert(
|
||||
"enum".to_owned(),
|
||||
schemars::_serde_json::Value::Array({
|
||||
let mut enum_values = Vec::new();
|
||||
#(enum_values.push((#enum_ident::#variant_idents as #repr_type).into());)*
|
||||
enum_values
|
||||
}),
|
||||
);
|
||||
schemars::Schema::from(map)
|
||||
});
|
||||
|
||||
cont.attrs.as_metadata().apply_to_schema(&mut schema_expr);
|
||||
|
@ -118,7 +127,7 @@ fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option<TokenStream>) {
|
|||
))
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
|
||||
#fun(gen)
|
||||
}
|
||||
}
|
||||
|
@ -160,9 +169,18 @@ fn expr_for_external_tagged_enum<'a>(
|
|||
})
|
||||
.partition(|v| v.is_unit() && v.attrs.is_default());
|
||||
let unit_names = unit_variants.iter().map(|v| v.name());
|
||||
let unit_schema = schema_object(quote! {
|
||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||
enum_values: Some(vec![#(#unit_names.into()),*]),
|
||||
let unit_schema = quote!({
|
||||
let mut map = schemars::_serde_json::Map::new();
|
||||
map.insert("type".to_owned(), "string".into());
|
||||
map.insert(
|
||||
"enum".to_owned(),
|
||||
schemars::_serde_json::Value::Array({
|
||||
let mut enum_values = Vec::new();
|
||||
#(enum_values.push((#unit_names).into());)*
|
||||
enum_values
|
||||
}),
|
||||
);
|
||||
schemars::Schema::from(map)
|
||||
});
|
||||
|
||||
if complex_variants.is_empty() {
|
||||
|
@ -276,47 +294,44 @@ fn expr_for_adjacent_tagged_enum<'a>(
|
|||
let (add_content_to_props, add_content_to_required) = content_schema
|
||||
.map(|content_schema| {
|
||||
(
|
||||
quote!(props.insert(#content_name.to_owned(), #content_schema);),
|
||||
quote!(required.insert(#content_name.to_owned());),
|
||||
quote!(#content_name: (#content_schema),),
|
||||
quote!(#content_name,),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let name = variant.name();
|
||||
let tag_schema = schema_object(quote! {
|
||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||
enum_values: Some(vec![#name.into()]),
|
||||
});
|
||||
let tag_schema = quote! {
|
||||
schemars::json_schema!({
|
||||
"type": "string",
|
||||
"enum": [#name],
|
||||
})
|
||||
};
|
||||
|
||||
let set_additional_properties = if deny_unknown_fields {
|
||||
quote! {
|
||||
additional_properties: Some(Box::new(false.into())),
|
||||
"additionalProperties": false,
|
||||
}
|
||||
} else {
|
||||
TokenStream::new()
|
||||
};
|
||||
|
||||
let mut outer_schema = schema_object(quote! {
|
||||
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
||||
object: Some(Box::new(schemars::schema::ObjectValidation {
|
||||
properties: {
|
||||
let mut props = schemars::Map::new();
|
||||
props.insert(#tag_name.to_owned(), #tag_schema);
|
||||
let mut outer_schema = quote! {
|
||||
schemars::json_schema!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
#tag_name: (#tag_schema),
|
||||
#add_content_to_props
|
||||
props
|
||||
},
|
||||
required: {
|
||||
let mut required = schemars::Set::new();
|
||||
required.insert(#tag_name.to_owned());
|
||||
"required": [
|
||||
#tag_name,
|
||||
#add_content_to_required
|
||||
required
|
||||
},
|
||||
],
|
||||
// As we're creating a "wrapper" object, we can honor the
|
||||
// disposition of deny_unknown_fields.
|
||||
#set_additional_properties
|
||||
..Default::default()
|
||||
})),
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
variant
|
||||
.attrs
|
||||
|
@ -333,21 +348,19 @@ fn expr_for_adjacent_tagged_enum<'a>(
|
|||
/// Callers must determine if all subschemas are mutually exclusive. This can
|
||||
/// be done for most tagging regimes by checking that all tag names are unique.
|
||||
fn variant_subschemas(unique: bool, schemas: Vec<TokenStream>) -> TokenStream {
|
||||
if unique {
|
||||
schema_object(quote! {
|
||||
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
||||
one_of: Some(vec![#(#schemas),*]),
|
||||
..Default::default()
|
||||
})),
|
||||
})
|
||||
} else {
|
||||
schema_object(quote! {
|
||||
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
||||
any_of: Some(vec![#(#schemas),*]),
|
||||
..Default::default()
|
||||
})),
|
||||
})
|
||||
}
|
||||
let keyword = if unique { "oneOf" } else { "anyOf" };
|
||||
quote!({
|
||||
let mut map = schemars::_serde_json::Map::new();
|
||||
map.insert(
|
||||
#keyword.to_owned(),
|
||||
schemars::_serde_json::Value::Array({
|
||||
let mut enum_values = Vec::new();
|
||||
#(enum_values.push(#schemas.to_value());)*
|
||||
enum_values
|
||||
}),
|
||||
);
|
||||
schemars::Schema::from(map)
|
||||
})
|
||||
}
|
||||
|
||||
fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) -> TokenStream {
|
||||
|
@ -412,16 +425,11 @@ fn expr_for_tuple_struct(fields: &[Field]) -> TokenStream {
|
|||
let len = fields.len() as u32;
|
||||
|
||||
quote! {
|
||||
schemars::schema::Schema::Object(
|
||||
schemars::schema::SchemaObject {
|
||||
instance_type: Some(schemars::schema::InstanceType::Array.into()),
|
||||
array: Some(Box::new(schemars::schema::ArrayValidation {
|
||||
items: Some(vec![#(#fields),*].into()),
|
||||
max_items: Some(#len),
|
||||
min_items: Some(#len),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
schemars::json_schema!({
|
||||
"type": "array",
|
||||
"items": [#((#fields)),*],
|
||||
"minItems": #len,
|
||||
"maxItems": #len,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -477,7 +485,7 @@ fn expr_for_struct(
|
|||
quote! {
|
||||
{
|
||||
#type_def
|
||||
schemars::_private::insert_object_property::<#ty>(object_validation, #name, #has_default, #required, #schema_expr);
|
||||
schemars::_private::insert_object_property::<#ty>(&mut schema, #name, #has_default, #required, #schema_expr);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -502,7 +510,7 @@ fn expr_for_struct(
|
|||
|
||||
let set_additional_properties = if deny_unknown_fields {
|
||||
quote! {
|
||||
object_validation.additional_properties = Some(Box::new(false.into()));
|
||||
"additionalProperties": false,
|
||||
}
|
||||
} else {
|
||||
TokenStream::new()
|
||||
|
@ -510,15 +518,12 @@ fn expr_for_struct(
|
|||
quote! {
|
||||
{
|
||||
#set_container_default
|
||||
let mut schema_object = schemars::schema::SchemaObject {
|
||||
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
||||
..Default::default()
|
||||
};
|
||||
let object_validation = schema_object.object();
|
||||
#set_additional_properties
|
||||
let mut schema = schemars::json_schema!({
|
||||
"type": "object",
|
||||
#set_additional_properties
|
||||
});
|
||||
#(#properties)*
|
||||
schemars::schema::Schema::Object(schema_object)
|
||||
#(.flatten(#flattens))*
|
||||
schema #(.flatten(#flattens))*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -578,16 +583,6 @@ fn field_default_expr(field: &Field, container_has_default: bool) -> Option<Toke
|
|||
})
|
||||
}
|
||||
|
||||
fn schema_object(properties: TokenStream) -> TokenStream {
|
||||
quote! {
|
||||
schemars::schema::Schema::Object(
|
||||
schemars::schema::SchemaObject {
|
||||
#properties
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn prepend_type_def(type_def: Option<TokenStream>, schema_expr: &mut TokenStream) {
|
||||
if let Some(type_def) = type_def {
|
||||
*schema_expr = quote! {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue