Skip to content

Commit

Permalink
Add allOf schema, support enums with references (#291)
Browse files Browse the repository at this point in the history
Introduce support for unnamed struct enums having a single field with
pointing reference to another component. This case should be handled via
allOf composite schema object but currently there is only support for oneOf.

fixes #285
  • Loading branch information
juhaku authored Sep 20, 2022
1 parent 64a5998 commit 61a99f7
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 50 deletions.
45 changes: 26 additions & 19 deletions utoipa-gen/src/component/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -683,19 +683,6 @@ impl ComplexEnum<'_> {
}
Fields::Unnamed(unnamed_fields) => {
if unnamed_fields.unnamed.len() == 1 {
let is_reference = unnamed_fields.unnamed.iter().any(|field| {
let ty = TypeTree::from_type(&field.ty);

ty.value_type == ValueType::Object
});

if is_reference {
abort!(
variant, "Unnamed field enum variant with schema references are not supported with serde `tag = ...` attribute.";
help = "See `https://github.com/juhaku/utoipa/issues/285#issuecomment-1249625860` for more details."
);
}

let unnamed_enum = UnnamedStructSchema {
attributes: &variant.attrs,
fields: &unnamed_fields.unnamed,
Expand All @@ -704,12 +691,31 @@ impl ComplexEnum<'_> {
let variant_name_tokens =
UnitVariantTokens::to_tokens(&EnumVariantTokens(&variant_name, None));

quote! {
#unnamed_enum
#variant_title
.schema_type(utoipa::openapi::schema::SchemaType::Object)
.property(#tag, #variant_name_tokens)
.required(#tag)
let is_reference = unnamed_fields.unnamed.iter().any(|field| {
let ty = TypeTree::from_type(&field.ty);

ty.value_type == ValueType::Object
});

if is_reference {
quote! {
utoipa::openapi::schema::AllOfBuilder::new()
#variant_title
.item(#unnamed_enum)
.item(utoipa::openapi::schema::ObjectBuilder::new()
.schema_type(utoipa::openapi::schema::SchemaType::Object)
.property(#tag, #variant_name_tokens)
.required(#tag)
)
}
} else {
quote! {
#unnamed_enum
#variant_title
.schema_type(utoipa::openapi::schema::SchemaType::Object)
.property(#tag, #variant_name_tokens)
.required(#tag)
}
}
} else {
abort!(
Expand Down Expand Up @@ -748,6 +754,7 @@ impl ToTokens for ComplexEnum<'_> {
let tag = container_rules
.as_ref()
.and_then(|rules| rules.tag.as_ref());

// serde, externally tagged format supported by now
let items: TokenStream = self
.variants
Expand Down
96 changes: 71 additions & 25 deletions utoipa-gen/tests/schema_derive_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,6 @@ fn derive_complex_enum_with_schema_properties() {

// TODO fixme https://github.com/juhaku/utoipa/issues/285#issuecomment-1249625860
#[test]
#[ignore = "fix me, see: https://github.com/juhaku/utoipa/issues/285#issuecomment-1249625860"]
fn derive_enum_with_unnamed_single_field_with_tag() {
#[derive(Serialize)]
struct ReferenceValue(String);
Expand All @@ -844,7 +843,7 @@ fn derive_enum_with_unnamed_single_field_with_tag() {
#[derive(Serialize)]
#[serde(tag = "enum")]
enum EnumWithReference {
Value(String),
Value(ReferenceValue),
}
};

Expand All @@ -853,27 +852,32 @@ fn derive_enum_with_unnamed_single_field_with_tag() {
json!({
"oneOf": [
{
"type": "object",
"properties": {
"enum": {
"type": "string",
"enum": ["Value"]

},
"Value": {
"allOf": [
{
"$ref": "#/components/schemas/ReferenceValue",
},
},
"required": ["enum"]
},
{
"type": "object",
"properties": {
"enum": {
"type": "string",
"enum": ["Value"]

},
},
"required": ["enum"]
},
],
}
],
"discriminator": {
"propertyName": "enum"
}
})
);
}

// TODO fixme https://github.com/juhaku/utoipa/issues/285#issuecomment-1249625860
#[test]
#[ignore = "fix me, see: https://github.com/juhaku/utoipa/issues/285#issuecomment-1249625860"]
fn derive_enum_with_named_fields_with_reference_with_tag() {
#[derive(Serialize)]
struct ReferenceValue(String);
Expand All @@ -886,31 +890,73 @@ fn derive_enum_with_named_fields_with_reference_with_tag() {
field: ReferenceValue,
a: String
},
// UnnamedValue(ReferenceValue),
UnnamedValue(ReferenceValue),
UnitValue,
}
};

println!("{}", serde_json::to_string_pretty(&value).unwrap());
assert_json_eq!(
value,
json!({
"oneOf": [
{
"type": "object",
"properties": {
"enum": {
"type": "string",
"enum": ["Value"]

"a": {
"type": "string"
},
"Value": {
"$ref": "#/components/schemas/ReferenceValue",
"enum": {
"enum": [
"Value"
],
"type": "string"
},
"field": {
"$ref": "#/components/schemas/ReferenceValue"
}
},
"required": ["enum"]
"required": [
"field",
"a",
"enum"
],
"type": "object"
},
{
"allOf": [
{
"$ref": "#/components/schemas/ReferenceValue",
},
{
"type": "object",
"properties": {
"enum": {
"type": "string",
"enum": ["UnnamedValue"]

},
},
"required": ["enum"]
}
],
},
{
"properties": {
"enum": {
"enum": [
"UnitValue"
],
"type": "string"
}
},
"required": [
"enum"
],
"type": "object"
}
],
"discriminator": {
"propertyName": "enum"
}
})
);
}
Expand Down
4 changes: 2 additions & 2 deletions utoipa/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ pub use self::{
path::{PathItem, PathItemType, Paths, PathsBuilder},
response::{Response, ResponseBuilder, Responses, ResponsesBuilder},
schema::{
Array, ArrayBuilder, Components, ComponentsBuilder, Discriminator, Object, ObjectBuilder,
OneOf, OneOfBuilder, Ref, Schema, SchemaFormat, SchemaType, ToArray,
AllOf, AllOfBuilder, Array, ArrayBuilder, Components, ComponentsBuilder, Discriminator,
Object, ObjectBuilder, OneOf, OneOfBuilder, Ref, Schema, SchemaFormat, SchemaType, ToArray,
},
security::SecurityRequirement,
server::{Server, ServerBuilder, ServerVariable, ServerVariableBuilder},
Expand Down
Loading

0 comments on commit 61a99f7

Please sign in to comment.