Adds support for replacing base type references with their derived types #55

This commit is contained in:
Irvine Sunday 2020-04-02 19:46:14 -07:00 committed by Sam Xu
parent 112c566aee
commit 3a00f2a540
13 changed files with 380 additions and 130 deletions

View file

@ -0,0 +1,64 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData.Common
{
internal static class EdmModelHelper
{
/// <summary>
/// Adds the derived types references together with their base type reference in the OneOf property of an OpenAPI schema.
/// </summary>
/// <returns>The OpenAPI schema with the list of derived types references and their base type references set in the OneOf property.</returns>
internal static OpenApiSchema GetDerivedTypesReferenceSchema(IEdmEntityType entityType, IEdmModel edmModel)
{
if (entityType == null || edmModel == null)
{
return null;
}
IEnumerable<IEdmEntityType> derivedTypes = edmModel.FindDirectlyDerivedTypes(entityType).OfType<IEdmEntityType>();
if (!derivedTypes.Any())
{
return null;
}
OpenApiSchema schema = new OpenApiSchema
{
OneOf = new List<OpenApiSchema>()
};
OpenApiSchema baseTypeSchema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = entityType.FullName()
}
};
schema.OneOf.Add(baseTypeSchema);
foreach (IEdmEntityType derivedType in derivedTypes)
{
OpenApiSchema derivedTypeSchema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = derivedType.FullName()
}
};
schema.OneOf.Add(derivedTypeSchema);
};
return schema;
}
}
}

View file

@ -105,28 +105,41 @@ namespace Microsoft.OpenApi.OData
/// </summary>
public bool EnableDiscriminatorValue { get; set; } = false;
/// <summary>
/// Gets/sets a value indicating whether or not to show the derived types of a base type reference in the responses payload.
/// </summary>
public bool EnableDerivedTypesReferencesForResponses { get; set; } = false;
/// <summary>
/// Gets/sets a value indicating whether or not to show the derived types of a base type reference in the requestBody payload.
/// </summary>
public bool EnableDerivedTypesReferencesForRequestBody { get; set; } = false;
internal OpenApiConvertSettings Clone()
{
var newSettings = new OpenApiConvertSettings();
newSettings.ServiceRoot = this.ServiceRoot;
newSettings.Version = this.Version;
newSettings.EnableKeyAsSegment = this.EnableKeyAsSegment;
newSettings.EnableUnqualifiedCall = this.EnableUnqualifiedCall;
newSettings.EnableOperationPath = this.EnableOperationPath;
newSettings.EnableOperationImportPath = this.EnableOperationImportPath;
newSettings.EnableNavigationPropertyPath = this.EnableNavigationPropertyPath;
newSettings.TagDepth = this.TagDepth;
newSettings.PrefixEntityTypeNameBeforeKey = this.PrefixEntityTypeNameBeforeKey;
newSettings.OpenApiSpecVersion = this.OpenApiSpecVersion;
newSettings.EnableOperationId = this.EnableOperationId;
newSettings.VerifyEdmModel = this.VerifyEdmModel;
newSettings.IEEE754Compatible = this.IEEE754Compatible;
newSettings.TopExample = this.TopExample;
newSettings.EnableUriEscapeFunctionCall = this.EnableUriEscapeFunctionCall;
newSettings.EnablePagination = this.EnablePagination;
newSettings.PageableOperationName = this.PageableOperationName;
newSettings.EnableDiscriminatorValue = this.EnableDiscriminatorValue;
var newSettings = new OpenApiConvertSettings
{
ServiceRoot = this.ServiceRoot,
Version = this.Version,
EnableKeyAsSegment = this.EnableKeyAsSegment,
EnableUnqualifiedCall = this.EnableUnqualifiedCall,
EnableOperationPath = this.EnableOperationPath,
EnableOperationImportPath = this.EnableOperationImportPath,
EnableNavigationPropertyPath = this.EnableNavigationPropertyPath,
TagDepth = this.TagDepth,
PrefixEntityTypeNameBeforeKey = this.PrefixEntityTypeNameBeforeKey,
OpenApiSpecVersion = this.OpenApiSpecVersion,
EnableOperationId = this.EnableOperationId,
VerifyEdmModel = this.VerifyEdmModel,
IEEE754Compatible = this.IEEE754Compatible,
TopExample = this.TopExample,
EnableUriEscapeFunctionCall = this.EnableUriEscapeFunctionCall,
EnablePagination = this.EnablePagination,
PageableOperationName = this.PageableOperationName,
EnableDiscriminatorValue = this.EnableDiscriminatorValue,
EnableDerivedTypesReferencesForResponses = this.EnableDerivedTypesReferencesForResponses,
EnableDerivedTypesReferencesForRequestBody = this.EnableDerivedTypesReferencesForRequestBody
};
return newSettings;
}

View file

@ -63,6 +63,25 @@ namespace Microsoft.OpenApi.OData.Operation
/// <inheritdoc/>
protected override void SetResponses(OpenApiOperation operation)
{
OpenApiSchema schema = null;
if (Context.Settings.EnableDerivedTypesReferencesForResponses)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model);
}
if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = EntitySet.EntityType().FullName()
}
};
}
operation.Responses = new OpenApiResponses
{
{
@ -76,14 +95,7 @@ namespace Microsoft.OpenApi.OData.Operation
Constants.ApplicationJsonMediaType,
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = EntitySet.EntityType().FullName()
}
}
Schema = schema
}
}
},
@ -143,4 +155,4 @@ namespace Microsoft.OpenApi.OData.Operation
}
}
}
}
}

View file

@ -43,6 +43,25 @@ namespace Microsoft.OpenApi.OData.Operation
/// <inheritdoc/>
protected override void SetRequestBody(OpenApiOperation operation)
{
OpenApiSchema schema = null;
if (Context.Settings.EnableDerivedTypesReferencesForRequestBody)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model);
}
if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = EntitySet.EntityType().FullName()
}
};
}
operation.RequestBody = new OpenApiRequestBody
{
Required = true,
@ -52,14 +71,7 @@ namespace Microsoft.OpenApi.OData.Operation
{
Constants.ApplicationJsonMediaType, new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = EntitySet.EntityType().FullName()
}
}
Schema = schema
}
}
}

View file

@ -43,7 +43,7 @@ namespace Microsoft.OpenApi.OData.Operation
}
protected override void SetExtensions(OpenApiOperation operation)
{
{
if (Context.Settings.EnablePagination)
{
OpenApiObject extension = new OpenApiObject
@ -130,26 +130,38 @@ namespace Microsoft.OpenApi.OData.Operation
/// <inheritdoc/>
protected override void SetResponses(OpenApiOperation operation)
{
var properties = new Dictionary<string, OpenApiSchema>
{
OpenApiSchema schema = null;
if (Context.Settings.EnableDerivedTypesReferencesForResponses)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model);
}
if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = EntitySet.EntityType().FullName()
}
};
}
var properties = new Dictionary<string, OpenApiSchema>
{
{
"value",
new OpenApiSchema
{
Type = "array",
Items = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = EntitySet.EntityType().FullName()
}
}
Items = schema
}
}
};
if (Context.Settings.EnablePagination)
if (Context.Settings.EnablePagination)
{
properties.Add(
"@odata.nextLink",

View file

@ -43,6 +43,25 @@ namespace Microsoft.OpenApi.OData.Operation
/// <inheritdoc/>
protected override void SetRequestBody(OpenApiOperation operation)
{
OpenApiSchema schema = null;
if (Context.Settings.EnableDerivedTypesReferencesForRequestBody)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model);
}
if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = EntitySet.EntityType().FullName()
}
};
}
// The requestBody field contains a Request Body Object for the request body
// that references the schema of the entity sets entity type in the global schemas.
operation.RequestBody = new OpenApiRequestBody
@ -54,14 +73,7 @@ namespace Microsoft.OpenApi.OData.Operation
{
Constants.ApplicationJsonMediaType, new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = EntitySet.EntityType().FullName()
}
}
Schema = schema
}
}
}
@ -73,6 +85,25 @@ namespace Microsoft.OpenApi.OData.Operation
/// <inheritdoc/>
protected override void SetResponses(OpenApiOperation operation)
{
OpenApiSchema schema = null;
if (Context.Settings.EnableDerivedTypesReferencesForResponses)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model);
}
if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = EntitySet.EntityType().FullName()
}
};
}
operation.Responses = new OpenApiResponses
{
{
@ -86,14 +117,7 @@ namespace Microsoft.OpenApi.OData.Operation
Constants.ApplicationJsonMediaType,
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = EntitySet.EntityType().FullName()
}
}
Schema = schema
}
}
}

View file

@ -61,12 +61,31 @@ namespace Microsoft.OpenApi.OData.Operation
base.SetExtensions(operation);
}
}
}
}
/// <inheritdoc/>
protected override void SetResponses(OpenApiOperation operation)
{
OpenApiSchema schema = null;
if (Context.Settings.EnableDerivedTypesReferencesForResponses)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(NavigationProperty.ToEntityType(), Context.Model);
}
if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = NavigationProperty.ToEntityType().FullName()
}
};
}
if (!LastSegmentIsKeySegment && NavigationProperty.TargetMultiplicity() == EdmMultiplicity.Many)
{
var properties = new Dictionary<string, OpenApiSchema>
@ -76,14 +95,7 @@ namespace Microsoft.OpenApi.OData.Operation
new OpenApiSchema
{
Type = "array",
Items = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = NavigationProperty.ToEntityType().FullName()
}
}
Items = schema
}
}
};
@ -139,14 +151,7 @@ namespace Microsoft.OpenApi.OData.Operation
Constants.ApplicationJsonMediaType,
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = NavigationProperty.ToEntityType().FullName()
}
}
Schema = schema
}
}
}

View file

@ -41,6 +41,25 @@ namespace Microsoft.OpenApi.OData.Operation
/// <inheritdoc/>
protected override void SetRequestBody(OpenApiOperation operation)
{
OpenApiSchema schema = null;
if (Context.Settings.EnableDerivedTypesReferencesForRequestBody)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(NavigationProperty.ToEntityType(), Context.Model);
}
if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = NavigationProperty.ToEntityType().FullName()
}
};
}
operation.RequestBody = new OpenApiRequestBody
{
Required = true,
@ -50,14 +69,7 @@ namespace Microsoft.OpenApi.OData.Operation
{
Constants.ApplicationJsonMediaType, new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = NavigationProperty.ToEntityType().FullName()
}
}
Schema = schema
}
}
}
@ -85,7 +97,6 @@ namespace Microsoft.OpenApi.OData.Operation
return;
}
operation.Security = Context.CreateSecurityRequirements(Restriction.UpdateRestrictions.Permissions).ToList();
}

View file

@ -41,26 +41,38 @@ namespace Microsoft.OpenApi.OData.Operation
/// <inheritdoc/>
protected override void SetRequestBody(OpenApiOperation operation)
{
OpenApiSchema schema = null;
if (Context.Settings.EnableDerivedTypesReferencesForRequestBody)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(NavigationProperty.ToEntityType(), Context.Model);
}
if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = NavigationProperty.ToEntityType().FullName()
}
};
}
operation.RequestBody = new OpenApiRequestBody
{
Required = true,
Description = "New navigation property",
Content = new Dictionary<string, OpenApiMediaType>
{
{
Constants.ApplicationJsonMediaType, new OpenApiMediaType
{
Constants.ApplicationJsonMediaType, new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = NavigationProperty.ToEntityType().FullName()
}
}
}
Schema = schema
}
}
}
};
base.SetRequestBody(operation);
@ -69,6 +81,25 @@ namespace Microsoft.OpenApi.OData.Operation
/// <inheritdoc/>
protected override void SetResponses(OpenApiOperation operation)
{
OpenApiSchema schema = null;
if (Context.Settings.EnableDerivedTypesReferencesForResponses)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(NavigationProperty.ToEntityType(), Context.Model);
}
if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = NavigationProperty.ToEntityType().FullName()
}
};
}
operation.Responses = new OpenApiResponses
{
{
@ -82,14 +113,7 @@ namespace Microsoft.OpenApi.OData.Operation
Constants.ApplicationJsonMediaType,
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = NavigationProperty.ToEntityType().FullName()
}
}
Schema = schema
}
}
}

View file

@ -63,6 +63,25 @@ namespace Microsoft.OpenApi.OData.Operation
/// <inheritdoc/>
protected override void SetResponses(OpenApiOperation operation)
{
OpenApiSchema schema = null;
if (Context.Settings.EnableDerivedTypesReferencesForResponses)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(Singleton.EntityType(), Context.Model);
}
if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = Singleton.EntityType().FullName()
}
};
}
operation.Responses = new OpenApiResponses
{
{
@ -76,14 +95,7 @@ namespace Microsoft.OpenApi.OData.Operation
Constants.ApplicationJsonMediaType,
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = Singleton.EntityType().FullName()
}
}
Schema = schema
}
}
}

View file

@ -43,6 +43,25 @@ namespace Microsoft.OpenApi.OData.Operation
/// <inheritdoc/>
protected override void SetRequestBody(OpenApiOperation operation)
{
OpenApiSchema schema = null;
if (Context.Settings.EnableDerivedTypesReferencesForRequestBody)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(Singleton.EntityType(), Context.Model);
}
if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = Singleton.EntityType().FullName()
}
};
}
operation.RequestBody = new OpenApiRequestBody
{
Required = true,
@ -52,14 +71,7 @@ namespace Microsoft.OpenApi.OData.Operation
{
Constants.ApplicationJsonMediaType, new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = Singleton.EntityType().FullName()
}
}
Schema = schema
}
}
}

View file

@ -9,6 +9,7 @@ using Microsoft.OData.Edm;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Common;
using Microsoft.OpenApi.OData.Edm;
using Microsoft.OpenApi.OData.Generator;
using Xunit;
@ -471,6 +472,42 @@ namespace Microsoft.OpenApi.OData.Tests
}
#endregion
#region BaseTypeToDerivedTypesSchema
[Fact]
public void GetDerivedTypesReferenceSchemaReturnsDerivedTypesReferencesInSchemaIfExist()
{
// Arrange
IEdmModel edmModel = EdmModelHelper.GraphBetaModel;
IEdmEntityType entityType = edmModel.SchemaElements.OfType<IEdmEntityType>().First(c => c.Name == "directoryObject");
OpenApiSchema schema = null;
// Act
schema = Common.EdmModelHelper.GetDerivedTypesReferenceSchema(entityType, edmModel);
int derivedTypesCount = edmModel.FindDirectlyDerivedTypes(entityType).OfType<IEdmEntityType>().Count() + 1; // + 1 the base type
// Assert
Assert.NotNull(schema.OneOf);
Assert.Equal(derivedTypesCount, schema.OneOf.Count);
}
[Fact]
public void GetDerivedTypesReferenceSchemaReturnsNullSchemaIfNotExist()
{
// Arrange
IEdmModel edmModel = EdmModelHelper.GraphBetaModel;
IEdmEntityType entityType = edmModel.SchemaElements.OfType<IEdmEntityType>().First(c => c.Name == "administrativeUnit");
OpenApiSchema schema = null;
// Act
schema = Common.EdmModelHelper.GetDerivedTypesReferenceSchema(entityType, edmModel);
// Assert
Assert.Null(schema);
}
#endregion
[Fact]
public void NonNullableBooleanPropertyWithDefaultValueWorks()
{

View file

@ -23,16 +23,19 @@ namespace Microsoft.OpenApi.OData.Operation.Tests
private EntitySetGetOperationHandler _operationHandler = new EntitySetGetOperationHandler();
[Theory]
[InlineData(true)]
[InlineData(false)]
public void CreateEntitySetGetOperationReturnsCorrectOperation(bool enableOperationId)
[InlineData(true, true)]
[InlineData(false, true)]
[InlineData(false, false)]
[InlineData(true, false)]
public void CreateEntitySetGetOperationReturnsCorrectOperation(bool enableOperationId, bool enablePagination)
{
// Arrange
IEdmModel model = GetEdmModel("");
IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers");
OpenApiConvertSettings settings = new OpenApiConvertSettings
{
EnableOperationId = enableOperationId
EnableOperationId = enableOperationId,
EnablePagination = enablePagination
};
ODataContext context = new ODataContext(model, settings);
ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet));
@ -64,6 +67,15 @@ namespace Microsoft.OpenApi.OData.Operation.Tests
{
Assert.Null(get.OperationId);
}
if (enablePagination)
{
Assert.True(get.Extensions.ContainsKey(Constants.xMsPageable));
}
else
{
Assert.False(get.Extensions.ContainsKey(Constants.xMsPageable));
}
}
[Theory]