Do not ignore multiple types when serializing to 3.0 - #2960
Conversation
| var schema = new OpenApiSchema() | ||
| { | ||
| Type = JsonSchemaType.String | JsonSchemaType.Integer, | ||
| }; |
There was a problem hiding this comment.
Before my change, this was resulting in an empty schema, which will match anything.
| { | ||
| var schema = new OpenApiSchema() | ||
| { | ||
| Type = JsonSchemaType.String | JsonSchemaType.Integer | JsonSchemaType.Null, |
There was a problem hiding this comment.
Before my change, the type was ignored, and only anyOf was emitted.
| { | ||
| var schema = new OpenApiSchema() | ||
| { | ||
| Type = JsonSchemaType.String | JsonSchemaType.Integer, |
There was a problem hiding this comment.
Before my change, the type was ignored and only anyOf was emitted.
| { | ||
| var schema = new OpenApiSchema() | ||
| { | ||
| Type = JsonSchemaType.String | JsonSchemaType.Integer | JsonSchemaType.Null, |
There was a problem hiding this comment.
Before my change, the type was ignored and only oneOf was emitted.
| { | ||
| var schema = new OpenApiSchema() | ||
| { | ||
| Type = JsonSchemaType.String | JsonSchemaType.Integer, |
There was a problem hiding this comment.
Before my change, only oneOf was emitted.
| { | ||
| var schema = new OpenApiSchema() | ||
| { | ||
| Type = JsonSchemaType.String | JsonSchemaType.Integer | JsonSchemaType.Null, |
There was a problem hiding this comment.
Before my change, an empty schema was generated, allowing anything to match.
165e0eb to
a663f6f
Compare
| { | ||
| var schema = new OpenApiSchema() | ||
| { | ||
| Type = JsonSchemaType.String | JsonSchemaType.Integer, |
There was a problem hiding this comment.
In this test, the behavior is the same as in the past. We are not respecting Type, and I can't see an easy way to respect it.
It's technically possible to emit "allOf" that contains a schema representing the types as "oneOf" or "anyOf" schema, and then another schema that wraps the existing anyOf/oneOf. But that's a major re-write I didn't want to introduce.
| { | ||
| var schema = new OpenApiSchema() | ||
| { | ||
| Type = JsonSchemaType.String | JsonSchemaType.Integer | JsonSchemaType.Null, |
There was a problem hiding this comment.
Same here. This wasn't (and is still not) respected. We could respect it but it will be a more bigger re-write.
There was a problem hiding this comment.
Pull request overview
This pull request updates OpenApiSchema JSON serialization to properly represent schemas with multiple JsonSchemaType flags when targeting OpenAPI 3.0, emitting anyOf/oneOf (when possible) instead of silently ignoring additional types, and adds coverage to validate the new behavior.
Changes:
- Split type serialization logic between OpenAPI 2.0 and OpenAPI 3.0+ paths, adding 3.0-specific handling for multi-type schemas via
anyOf/oneOf. - Add comprehensive unit tests covering multi-type combinations (with/without
null, and with existingoneOf/anyOfpresent). - Simplify
ToSingleIdentifierby using a lookup and providing a clearer exception for unexpected values.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs | Adds tests validating multi-type serialization behavior for OpenAPI 3.0 across multiple composition scenarios. |
| src/Microsoft.OpenApi/Models/OpenApiSchema.cs | Refactors and extends type serialization to support multi-type handling in 3.0 using anyOf/oneOf, keeping 2.0 behavior unchanged. |
| src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs | Updates ToSingleIdentifier to use a lookup and throw a clearer exception for unexpected inputs. |
Comments suppressed due to low confidence (1)
src/Microsoft.OpenApi/Models/OpenApiSchema.cs:1091
- In the v3.1+ path, TrySerializeTypePropertyForVersion3AndLater also assumes Enum.GetValues() will match at least one flag in the provided Type. If Type is 0 or contains only unknown bits, array will be empty and array[0] will throw IndexOutOfRangeException. Add an empty-array guard and return false when no valid flags are present.
var array = (from JsonSchemaType flag in jsonSchemaTypeValues
where type.HasFlag(flag)
select flag).ToArray();
if (array.Length > 1)
{
writer.WriteOptionalCollection(OpenApiConstants.Type, array, (w, s) => w.WriteValue(s.ToSingleIdentifier()));
}
else
{
writer.WriteProperty(OpenApiConstants.Type, array[0].ToSingleIdentifier());
}
Given the investigations I did for #2967, I think it's fine for us to continue ensuring, as much as possible, that we produce semantically equivalent document for all versions. The cases presented in #2967 fall into two groups:
I don't believe there is a need for a new public API to do the semantic transformations, and I don't think all transformations we might want to do in future will be representable in the object model. So we will end up with logic scattered between dedicated walkers and in the serialization itself. We shouldn't need to pay additional performance cost for deep cloning the whole document as well. |
Fixes #2939
ToSingleIdentifieris only a simplification to make it easier to read.Typehad multiple values (e.g, String and Integer). This is now handled usingoneOforanyOf(whichever isn't used by the current schema already). If both are used, we ignore as it we used to in the past.Type.Nullfrom it (if it exists).For context: https://spec.openapis.org/oas/v3.0.4.html#json-schema-keywords