Skip to content

Commit

Permalink
optimize internal and externally tagged enums
Browse files Browse the repository at this point in the history
  • Loading branch information
icewind1991 committed Jan 31, 2024
1 parent 9b68871 commit 1a82a9f
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 54 deletions.
59 changes: 58 additions & 1 deletion schemars/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,71 @@ impl Schema {
Schema::Object(schema_obj)
}

/// Create a schema for an externally tagged unit enum
/// Create a schema for a unit enum
pub fn new_unit_enum(variant: &str) -> Self {
Self::Object(SchemaObject {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(vec![variant.into()]),
..SchemaObject::default()
})
}

/// Create a schema for an externally tagged enum
pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Self {
Self::Object(SchemaObject {
instance_type: Some(InstanceType::Object.into()),
object: Some(Box::new(ObjectValidation {
properties: {
let mut props = Map::new();
props.insert(variant.to_owned(), sub_schema);
props
},
required: {
let mut required = Set::new();
required.insert(variant.to_owned());
required
},
// Externally tagged variants must prohibit additional
// properties irrespective of the disposition of
// `deny_unknown_fields`. If additional properties were allowed
// one could easily construct an object that validated against
// multiple variants since here it's the properties rather than
// the values of a property that distingish between variants.
additional_properties: Some(Box::new(false.into())),
..Default::default()
})),
..SchemaObject::default()
})
}

/// Create a schema for an internally tagged enum
pub fn new_internally_tagged_enum(tag_name: &str, variant: &str, deny_unknown_fields: bool) -> Self {
let tag_schema = Schema::Object(SchemaObject {
instance_type: Some(
InstanceType::String.into(),
),
enum_values: Some(vec![variant.into()]),
..Default::default()
});
Self::Object(SchemaObject {
instance_type: Some(InstanceType::Object.into()),
object: Some(Box::new(ObjectValidation {
properties: {
let mut props = Map::new();
props.insert(tag_name.to_owned(), tag_schema);
props
},
required: {
let mut required = Set::new();
required.insert(tag_name.to_owned());
required
},
additional_properties: deny_unknown_fields.then(|| Box::new(false.into())),
..Default::default()
})),
..SchemaObject::default()
})
}
}

impl From<SchemaObject> for Schema {
Expand Down
59 changes: 6 additions & 53 deletions schemars_derive/src/schema_exprs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,29 +183,9 @@ fn expr_for_external_tagged_enum<'a>(
}
} else {
let sub_schema = expr_for_untagged_enum_variant(variant, deny_unknown_fields);
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(#name.to_owned(), #sub_schema);
props
},
required: {
let mut required = schemars::Set::new();
required.insert(#name.to_owned());
required
},
// Externally tagged variants must prohibit additional
// properties irrespective of the disposition of
// `deny_unknown_fields`. If additional properties were allowed
// one could easily construct an object that validated against
// multiple variants since here it's the properties rather than
// the values of a property that distingish between variants.
additional_properties: Some(Box::new(false.into())),
..Default::default()
})),
})
quote! {
schemars::schema::Schema::new_externally_tagged_enum(#name, #sub_schema)
}
};

variant
Expand All @@ -226,43 +206,16 @@ fn expr_for_internal_tagged_enum<'a>(
) -> TokenStream {
let mut unique_names = HashSet::new();
let mut count = 0;
let set_additional_properties = if deny_unknown_fields {
quote! {
additional_properties: Some(Box::new(false.into())),
}
} else {
TokenStream::new()
};
let variant_schemas = variants
.map(|variant| {
unique_names.insert(variant.name());
count += 1;

let name = variant.name();
let type_schema = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::String.into()),
enum_values: Some(vec![#name.into()]),
});

let mut tag_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(), #type_schema);
props
},
required: {
let mut required = schemars::Set::new();
required.insert(#tag_name.to_owned());
required
},
// As we're creating a "wrapper" object, we can honor the
// disposition of deny_unknown_fields.
#set_additional_properties
..Default::default()
})),
});
let mut tag_schema = quote! {
schemars::schema::Schema::new_internally_tagged_enum(#tag_name, #name, #deny_unknown_fields)
};

variant.attrs.as_metadata().apply_to_schema(&mut tag_schema);

Expand Down

0 comments on commit 1a82a9f

Please sign in to comment.