Skip to content

Commit 43ad8f9

Browse files
Represent nullable references within anyOf correctly (#247)
* Represent nullbale references within anyOf correctly * Use anyOf in schema if ref is nullable
1 parent f7f2689 commit 43ad8f9

File tree

9 files changed

+414
-200
lines changed

9 files changed

+414
-200
lines changed

src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiEdmTypeSchemaGenerator.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -420,10 +420,9 @@ private static OpenApiSchema CreateEnumTypeSchema(this ODataContext context, IEd
420420
Debug.Assert(typeReference != null);
421421

422422
OpenApiSchema schema = new OpenApiSchema();
423-
schema.Nullable = typeReference.IsNullable;
424423
schema.Reference = null;
425424

426-
if (context.Settings.OpenApiSpecVersion >= OpenApiSpecVersion.OpenApi3_0)
425+
if (typeReference.IsNullable && context.Settings.OpenApiSpecVersion >= OpenApiSpecVersion.OpenApi3_0)
427426
{
428427
schema.AnyOf = new List<OpenApiSchema>
429428
{
@@ -435,6 +434,11 @@ private static OpenApiSchema CreateEnumTypeSchema(this ODataContext context, IEd
435434
Type = ReferenceType.Schema,
436435
Id = typeReference.Definition.FullTypeName()
437436
}
437+
},
438+
new OpenApiSchema
439+
{
440+
Type = "object",
441+
Nullable = true
438442
}
439443
};
440444
}
@@ -448,6 +452,7 @@ private static OpenApiSchema CreateEnumTypeSchema(this ODataContext context, IEd
448452
Id = typeReference.Definition.FullTypeName()
449453
};
450454
schema.UnresolvedReference = true;
455+
schema.Nullable = typeReference.IsNullable;
451456
}
452457

453458
return schema;
@@ -459,7 +464,6 @@ private static OpenApiSchema CreateStructuredTypeSchema(this ODataContext contex
459464
Debug.Assert(typeReference != null);
460465

461466
OpenApiSchema schema = new OpenApiSchema();
462-
schema.Nullable = typeReference.IsNullable;
463467

464468
// AnyOf will only be valid openApi for version 3
465469
// otherwise the reference should be set directly
@@ -478,6 +482,11 @@ private static OpenApiSchema CreateStructuredTypeSchema(this ODataContext contex
478482
Type = ReferenceType.Schema,
479483
Id = typeReference.Definition.FullTypeName()
480484
}
485+
},
486+
new OpenApiSchema
487+
{
488+
Type = "object",
489+
Nullable = true
481490
}
482491
};
483492
}
@@ -490,7 +499,8 @@ private static OpenApiSchema CreateStructuredTypeSchema(this ODataContext contex
490499
Type = ReferenceType.Schema,
491500
Id = typeReference.Definition.FullTypeName()
492501
};
493-
schema.UnresolvedReference = true;
502+
schema.UnresolvedReference = true;
503+
schema.Nullable = typeReference.IsNullable;
494504
}
495505

496506
return schema;
@@ -502,10 +512,9 @@ private static OpenApiSchema CreateTypeDefinitionSchema(this ODataContext contex
502512
Debug.Assert(reference != null);
503513

504514
OpenApiSchema schema = new OpenApiSchema();
505-
schema.Nullable = reference.IsNullable;
506515
schema.Reference = null;
507516

508-
if (context.Settings.OpenApiSpecVersion >= OpenApiSpecVersion.OpenApi3_0)
517+
if (reference.IsNullable && context.Settings.OpenApiSpecVersion >= OpenApiSpecVersion.OpenApi3_0)
509518
{
510519
schema.AnyOf = new List<OpenApiSchema>
511520
{
@@ -517,6 +526,11 @@ private static OpenApiSchema CreateTypeDefinitionSchema(this ODataContext contex
517526
Type = ReferenceType.Schema,
518527
Id = reference.Definition.FullTypeName()
519528
}
529+
},
530+
new OpenApiSchema
531+
{
532+
Type = "object",
533+
Nullable = true
520534
}
521535
};
522536
}
@@ -530,6 +544,7 @@ private static OpenApiSchema CreateTypeDefinitionSchema(this ODataContext contex
530544
Id = reference.Definition.FullTypeName()
531545
};
532546
schema.UnresolvedReference = true;
547+
schema.Nullable = reference.IsNullable;
533548
}
534549

535550

src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
- Fixes request body and response representation for ref POST and PUT operations #228
3030
- Adds discriminator object to complex types which have derived types #233
3131
- Adds @odata.type property and makes this a required property in schemas that have discriminator objects #243
32+
- Represent nullable references within anyOf correctly #190
3233
</PackageReleaseNotes>
3334
<AssemblyName>Microsoft.OpenApi.OData.Reader</AssemblyName>
3435
<AssemblyOriginatorKeyFile>..\..\tool\Microsoft.OpenApi.OData.snk</AssemblyOriginatorKeyFile>

test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiEdmTypeSchemaGeneratorTest.cs

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,12 @@ public void CreateEdmTypeSchemaReturnSchemaForNullableCollectionComplexType(Open
8181
""anyOf"": [
8282
{
8383
""$ref"": ""#/components/schemas/Microsoft.OData.Service.Sample.TrippinInMemory.Models.AirportLocation""
84+
},
85+
{
86+
""type"": ""object"",
87+
""nullable"": true
8488
}
85-
],
86-
""nullable"": true
89+
]
8790
}
8891
}".ChangeLineBreaks(), json);
8992
}
@@ -185,26 +188,40 @@ public void CreateEdmTypeSchemaReturnSchemaForEnumType(bool isNullable, OpenApiS
185188
// & Assert
186189
Assert.NotNull(schema);
187190

188-
// Although the schema will be set
189-
// for openApiV2 nullable will not be serialized
190-
Assert.Equal(isNullable, schema.Nullable);
191-
191+
192192
if (specVersion == OpenApiSpecVersion.OpenApi2_0)
193193
{
194194
Assert.NotNull(schema.Reference);
195195
Assert.Null(schema.AnyOf);
196196
Assert.Equal(ReferenceType.Schema, schema.Reference.Type);
197197
Assert.Equal(enumType.FullTypeName(), schema.Reference.Id);
198+
Assert.Equal(isNullable, schema.Nullable);
198199
}
199200
else
200201
{
201-
Assert.Null(schema.Reference);
202-
Assert.NotNull(schema.AnyOf);
203-
Assert.NotEmpty(schema.AnyOf);
204-
var anyOf = Assert.Single(schema.AnyOf);
205-
Assert.NotNull(anyOf.Reference);
206-
Assert.Equal(ReferenceType.Schema, anyOf.Reference.Type);
207-
Assert.Equal(enumType.FullTypeName(), anyOf.Reference.Id);
202+
203+
if (isNullable)
204+
{
205+
Assert.NotNull(schema.AnyOf);
206+
Assert.NotEmpty(schema.AnyOf);
207+
Assert.Null(schema.Reference);
208+
Assert.Equal(2, schema.AnyOf.Count);
209+
var anyOfRef = schema.AnyOf.FirstOrDefault();
210+
Assert.NotNull(anyOfRef.Reference);
211+
Assert.Equal(ReferenceType.Schema, anyOfRef.Reference.Type);
212+
Assert.Equal(enumType.FullTypeName(), anyOfRef.Reference.Id);
213+
var anyOfNull = schema.AnyOf.Skip(1).FirstOrDefault();
214+
Assert.NotNull(anyOfNull.Type);
215+
Assert.Equal("object", anyOfNull.Type);
216+
Assert.True(anyOfNull.Nullable);
217+
}
218+
else
219+
{
220+
Assert.Null(schema.AnyOf);
221+
Assert.NotNull(schema.Reference);
222+
Assert.Equal(ReferenceType.Schema, schema.Reference.Type);
223+
Assert.Equal(enumType.FullTypeName(), schema.Reference.Id);
224+
}
208225
}
209226
}
210227

@@ -242,7 +259,8 @@ public void CreateEdmTypeSchemaReturnSchemaForComplexType(bool isNullable, OpenA
242259
Assert.Null(schema.Reference);
243260
Assert.NotNull(schema.AnyOf);
244261
Assert.NotEmpty(schema.AnyOf);
245-
var anyOf = Assert.Single(schema.AnyOf);
262+
Assert.Equal(2, schema.AnyOf.Count);
263+
var anyOf = schema.AnyOf.FirstOrDefault();
246264
Assert.NotNull(anyOf.Reference);
247265
Assert.Equal(ReferenceType.Schema, anyOf.Reference.Type);
248266
Assert.Equal(complex.FullTypeName(), anyOf.Reference.Id);
@@ -283,10 +301,14 @@ public void CreateEdmTypeSchemaReturnSchemaForEntityType(bool isNullable, OpenAp
283301
Assert.Null(schema.Reference);
284302
Assert.NotNull(schema.AnyOf);
285303
Assert.NotEmpty(schema.AnyOf);
286-
var anyOf = Assert.Single(schema.AnyOf);
287-
Assert.NotNull(anyOf.Reference);
288-
Assert.Equal(ReferenceType.Schema, anyOf.Reference.Type);
289-
Assert.Equal(entity.FullTypeName(), anyOf.Reference.Id);
304+
var anyOfRef = schema.AnyOf.FirstOrDefault();
305+
Assert.NotNull(anyOfRef.Reference);
306+
Assert.Equal(ReferenceType.Schema, anyOfRef.Reference.Type);
307+
Assert.Equal(entity.FullTypeName(), anyOfRef.Reference.Id);
308+
var anyOfNull = schema.AnyOf.Skip(1).FirstOrDefault();
309+
Assert.NotNull(anyOfNull.Type);
310+
Assert.Equal("object", anyOfNull.Type);
311+
Assert.True(anyOfNull.Nullable);
290312
}
291313
}
292314

test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiResponseGeneratorTests.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,25 +225,29 @@ public void CreateResponseForEdmFunctionReturnCorrectResponses(bool isFunctionIm
225225
Assert.NotNull(response.Content);
226226
OpenApiMediaType mediaType = response.Content["application/json"];
227227

228-
// For either version, nullable should be set
229-
// and the serializer will ignore for v2
230-
Assert.True(mediaType.Schema.Nullable);
231-
232228
// openApi version 2 should have not use nullable
233229
if (specVersion == OpenApiSpecVersion.OpenApi2_0)
234230
{
235231
Assert.NotNull(mediaType.Schema);
236232
Assert.Null(mediaType.Schema.AnyOf);
237233
Assert.NotNull(mediaType.Schema.Reference);
238234
Assert.Equal("Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person", mediaType.Schema.Reference.Id);
235+
Assert.True(mediaType.Schema.Nullable);
239236
}
240237
else
241238
{
242239
Assert.NotNull(mediaType.Schema);
243240
Assert.Null(mediaType.Schema.Reference);
244241
Assert.NotNull(mediaType.Schema.AnyOf);
245-
var anyOf = Assert.Single(mediaType.Schema.AnyOf);
246-
Assert.Equal("Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person", anyOf.Reference.Id);
242+
Assert.Equal(2, mediaType.Schema.AnyOf.Count);
243+
var anyOfRef = mediaType.Schema.AnyOf.FirstOrDefault();
244+
Assert.NotNull(anyOfRef);
245+
Assert.Equal("Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person", anyOfRef.Reference.Id);
246+
var anyOfNull = mediaType.Schema.AnyOf.Skip(1).FirstOrDefault();
247+
Assert.NotNull(anyOfNull.Type);
248+
Assert.Equal("object", anyOfNull.Type);
249+
Assert.True(anyOfNull.Nullable);
250+
247251
}
248252
}
249253

test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -259,26 +259,35 @@ public void CreateStructuredTypePropertiesSchemaWithCustomAttributeReturnsCorrec
259259
""anyOf"": [
260260
{
261261
""$ref"": ""#/components/schemas/microsoft.graph.userInsightsSettings""
262+
},
263+
{
264+
""type"": ""object"",
265+
""nullable"": true
262266
}
263267
],
264-
""nullable"": true,
265268
""x-ms-isHidden"": ""true""
266269
},
267270
""regionalAndLanguageSettings"": {
268271
""anyOf"": [
269272
{
270273
""$ref"": ""#/components/schemas/microsoft.graph.regionalAndLanguageSettings""
274+
},
275+
{
276+
""type"": ""object"",
277+
""nullable"": true
271278
}
272-
],
273-
""nullable"": true
279+
]
274280
},
275281
""shiftPreferences"": {
276282
""anyOf"": [
277283
{
278284
""$ref"": ""#/components/schemas/microsoft.graph.shiftPreferences""
285+
},
286+
{
287+
""type"": ""object"",
288+
""nullable"": true
279289
}
280-
],
281-
""nullable"": true
290+
]
282291
}
283292
}
284293
}
@@ -687,7 +696,7 @@ public void CreatePropertySchemaForNonNullableEnumPropertyReturnSchema(OpenApiSp
687696

688697
IEdmEnumType enumType = model.SchemaElements.OfType<IEdmEnumType>().First(e => e.Name == "Color");
689698
EdmEntityType entitType = new EdmEntityType("NS", "Entity");
690-
IEdmProperty property = new EdmStructuralProperty(entitType, "ColorEnumValue", new EdmEnumTypeReference(enumType, false), "yellow");
699+
IEdmProperty property = new EdmStructuralProperty(entitType, "ColorEnumValue", new EdmEnumTypeReference(enumType, false));
691700

692701
// Act
693702
var schema = context.CreatePropertySchema(property);
@@ -705,12 +714,7 @@ public void CreatePropertySchemaForNonNullableEnumPropertyReturnSchema(OpenApiSp
705714
else
706715
{
707716
Assert.Equal(@"{
708-
""anyOf"": [
709-
{
710-
""$ref"": ""#/components/schemas/DefaultNs.Color""
711-
}
712-
],
713-
""default"": ""yellow""
717+
""$ref"": ""#/components/schemas/DefaultNs.Color""
714718
}".ChangeLineBreaks(), json);
715719
}
716720
}
@@ -749,10 +753,13 @@ public void CreatePropertySchemaForNullableEnumPropertyReturnSchema(OpenApiSpecV
749753
""anyOf"": [
750754
{
751755
""$ref"": ""#/components/schemas/DefaultNs.Color""
756+
},
757+
{
758+
""type"": ""object"",
759+
""nullable"": true
752760
}
753761
],
754-
""default"": ""yellow"",
755-
""nullable"": true
762+
""default"": ""yellow""
756763
}".ChangeLineBreaks(), json);
757764
}
758765
}

0 commit comments

Comments
 (0)