- draft implementation of odata cast segments

This commit is contained in:
Vincent Biret 2021-11-24 16:26:58 -05:00
parent b5b9e08908
commit 9716b1d0bf
No known key found for this signature in database
GPG key ID: 32426322EDFFB7E3
9 changed files with 250 additions and 24 deletions

View file

@ -289,13 +289,15 @@ namespace Microsoft.OpenApi.OData.Edm
{
return ODataPathKind.Metadata;
}
if (Segments.Last().Kind == ODataSegmentKind.DollarCount)
else if (Segments.Last().Kind == ODataSegmentKind.DollarCount)
{
return ODataPathKind.DollarCount;
}
if (Segments.Any(c => c.Kind == ODataSegmentKind.StreamProperty || c.Kind == ODataSegmentKind.StreamContent))
else if (Segments.Last().Kind == ODataSegmentKind.TypeCast)
{
return ODataPathKind.TypeCast;
}
else if (Segments.Any(c => c.Kind == ODataSegmentKind.StreamProperty || c.Kind == ODataSegmentKind.StreamContent))
{
return ODataPathKind.MediaEntity;
}
@ -315,22 +317,17 @@ namespace Microsoft.OpenApi.OData.Edm
{
return ODataPathKind.NavigationProperty;
}
if (Segments.Count == 1)
else if (Segments.Count == 1 && Segments[0] is ODataNavigationSourceSegment segment)
{
ODataNavigationSourceSegment segment = Segments[0] as ODataNavigationSourceSegment;
if (segment != null)
if (segment.NavigationSource is IEdmSingleton)
{
if (segment.NavigationSource is IEdmSingleton)
{
return ODataPathKind.Singleton;
}
else
{
return ODataPathKind.EntitySet;
}
return ODataPathKind.Singleton;
}
}
else
{
return ODataPathKind.EntitySet;
}
}
else if (Segments.Count == 2 && Segments.Last().Kind == ODataSegmentKind.Key)
{
return ODataPathKind.Entity;

View file

@ -60,9 +60,14 @@ namespace Microsoft.OpenApi.OData.Edm
/// </summary>
DollarCount,
/// <summary>
/// Represents a type cast path, for example: ~/groups/{id}/members/microsoft.graph.user
/// </summary>
TypeCast,
/// <summary>
/// Represents an un-supported/unknown path.
/// </summary>
Unknown
}
Unknown,
}
}

View file

@ -127,6 +127,7 @@ namespace Microsoft.OpenApi.OData.Edm
ODataPathKind kind = path.Kind;
switch(kind)
{
case ODataPathKind.TypeCast:
case ODataPathKind.DollarCount:
case ODataPathKind.Entity:
case ODataPathKind.EntitySet:
@ -285,12 +286,25 @@ namespace Microsoft.OpenApi.OData.Edm
IEdmEntityType navEntityType = navigationProperty.ToEntityType();
var targetsMany = navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many;
var propertyPath = navigationProperty.GetPartnerPath()?.Path;
var propertyPathIsEmpty = string.IsNullOrEmpty(propertyPath);
if (targetsMany && (string.IsNullOrEmpty(propertyPath) ||
(count?.IsNonCountableNavigationProperty(propertyPath) ?? true)))
if (targetsMany)
{
// ~/entityset/{key}/collection-valued-Nav/$count
CreateCountPath(currentPath, convertSettings);
if(propertyPathIsEmpty ||
(count?.IsNonCountableNavigationProperty(propertyPath) ?? true))
{
// ~/entityset/{key}/collection-valued-Nav/$count
CreateCountPath(currentPath, convertSettings);
}
//TODO read the cast restrictions annotation
var derivedTypes = _model
.FindAllDerivedTypes(navigationProperty.DeclaringType)
.Where(x => x.TypeKind == EdmTypeKind.Entity)
.OfType<IEdmEntityType>()
.ToArray();
if(derivedTypes.Any())
CreateTypeCastPaths(currentPath, convertSettings, derivedTypes);
}
if (!navigationProperty.ContainsTarget)
@ -393,6 +407,26 @@ namespace Microsoft.OpenApi.OData.Edm
AppendPath(countPath);
}
/// <summary>
/// Create OData type cast paths.
/// </summary>
/// <param name="currentPath">The current OData path.</param>
/// <param name="convertSettings">The settings for the current conversion.</param>
/// <param name="targetTypes">The target types to generate a path for.</param>
private void CreateTypeCastPaths(ODataPath currentPath, OpenApiConvertSettings convertSettings, params IEdmEntityType[] targetTypes)
{
if(currentPath == null) throw new ArgumentNullException(nameof(currentPath));
if(convertSettings == null) throw new ArgumentNullException(nameof(convertSettings));
if(!convertSettings.EnableODataTypeCast || targetTypes == null || !targetTypes.Any()) return;
foreach(var targetType in targetTypes)
{
var castPath = currentPath.Clone();
castPath.Push(new ODataTypeCastSegment(targetType));
AppendPath(castPath);
}
}
/// <summary>
/// Retrieve all bounding <see cref="IEdmOperation"/>.
/// </summary>

View file

@ -188,6 +188,11 @@ namespace Microsoft.OpenApi.OData
/// </summary>
public bool EnableDollarCountPath { get; set; } = true;
/// <summary>
/// Gets/sets a value indicating whether or not to include the OData type cast segments on entity sets.
/// </summary>
public bool EnableODataTypeCast { get; set; } = true;
internal OpenApiConvertSettings Clone()
{
var newSettings = new OpenApiConvertSettings
@ -219,6 +224,7 @@ namespace Microsoft.OpenApi.OData
ShowRootPath = this.ShowRootPath,
PathProvider = this.PathProvider,
EnableDollarCountPath = this.EnableDollarCountPath,
EnableODataTypeCast = this.EnableODataTypeCast,
};
return newSettings;

View file

@ -0,0 +1,146 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Common;
using Microsoft.OpenApi.OData.Edm;
using Microsoft.OpenApi.OData.Generator;
namespace Microsoft.OpenApi.OData.Operation;
/// <summary>
/// Retrieves a .../namespace.typename get
/// </summary>
internal class ODataTypeCastGetOperationHandler : OperationHandler
{
/// <inheritdoc/>
public override OperationType OperationType => OperationType.Get;
/// <summary>
/// Gets/sets the segment before cast.
/// this segment could be "entity set", "Collection property", etc.
/// </summary>
internal ODataSegment LastSecondSegment { get; set; }
private IEdmEntityType parentEntityType;
private IEdmEntityType targetEntityType;
private const int SecondLastSegmentIndex = 2;
/// <inheritdoc/>
protected override void Initialize(ODataContext context, ODataPath path)
{
base.Initialize(context, path);
// get the last second segment
int count = path.Segments.Count;
if(count >= SecondLastSegmentIndex)
LastSecondSegment = path.Segments.ElementAt(count - SecondLastSegmentIndex);
parentEntityType = LastSecondSegment.EntityType;
if(path.Last() is ODataTypeCastSegment oDataTypeCastSegment)
{
targetEntityType = oDataTypeCastSegment.EntityType;
}
else throw new NotImplementedException($"type cast type {path.Last().GetType().FullName} not implemented");
}
/// <inheritdoc/>
protected override void SetBasicInfo(OpenApiOperation operation)
{
// Summary
operation.Summary = $"Get the items of type {targetEntityType.ShortQualifiedName()} in the {parentEntityType.ShortQualifiedName()} collection";
// OperationId
if (Context.Settings.EnableOperationId)
{
operation.OperationId = $"Get.{parentEntityType.ShortQualifiedName()}.As.{targetEntityType.ShortQualifiedName()}";
}
base.SetBasicInfo(operation);
}
/// <inheritdoc/>
protected override void SetResponses(OpenApiOperation operation)
{
OpenApiSchema schema = null;
if (Context.Settings.EnableDerivedTypesReferencesForResponses)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(parentEntityType, Context.Model);
}
if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = $"{parentEntityType.FullName()}.To.{targetEntityType.FullName()}"
}
};
}
var properties = new Dictionary<string, OpenApiSchema>
{
{
"value",
new OpenApiSchema
{
Type = "array",
Items = schema
}
}
};
if (Context.Settings.EnablePagination)
{
properties.Add(
"@odata.nextLink",
new OpenApiSchema
{
Type = "string"
});
}
operation.Responses = new OpenApiResponses
{
{
Constants.StatusCode200,
new OpenApiResponse
{
Description = "Retrieved entities",
Content = new Dictionary<string, OpenApiMediaType>
{
{
Constants.ApplicationJsonMediaType,
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Title = $"Collection of items of type {targetEntityType.ShortQualifiedName()} in the {parentEntityType.ShortQualifiedName()} collection",
Type = "object",
Properties = properties
}
}
}
}
}
}
};
operation.Responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse());
base.SetResponses(operation);
}
//TODO query parameters?
//TODO extensions?
}
//TODO unit tests

View file

@ -89,6 +89,12 @@ namespace Microsoft.OpenApi.OData.Operation
{
{OperationType.Get, new DollarCountGetOperationHandler() }
}},
// .../namespace.typename (cast, get)
{ODataPathKind.TypeCast, new Dictionary<OperationType, IOperationHandler>
{
{OperationType.Get, new ODataTypeCastGetOperationHandler() },
}},
};
/// <inheritdoc/>

View file

@ -0,0 +1,26 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Edm;
namespace Microsoft.OpenApi.OData.PathItem;
/// <summary>
/// Path item handler for type cast for example: ~/groups/{id}/members/microsoft.graph.user
/// </summary>
internal class ODataTypeCastPathItemHandler : PathItemHandler
{
/// <inheritdoc/>
protected override ODataPathKind HandleKind => ODataPathKind.TypeCast;
/// <inheritdoc/>
protected override void SetOperations(OpenApiPathItem item)
{
AddOperation(item, OperationType.Get);
}
}
//TODO unit test for the ODataTypeCastPathItemHandler

View file

@ -45,6 +45,9 @@ namespace Microsoft.OpenApi.OData.PathItem
// $count
{ ODataPathKind.DollarCount, new DollarCountPathItemHandler() },
// ~/groups/{id}/members/microsoft.graph.user
{ ODataPathKind.TypeCast, new ODataTypeCastPathItemHandler() },
// Unknown
{ ODataPathKind.Unknown, null },
};

View file

@ -61,7 +61,8 @@ Microsoft.OpenApi.OData.Edm.ODataPathKind.Operation = 3 -> Microsoft.OpenApi.ODa
Microsoft.OpenApi.OData.Edm.ODataPathKind.OperationImport = 4 -> Microsoft.OpenApi.OData.Edm.ODataPathKind
Microsoft.OpenApi.OData.Edm.ODataPathKind.Ref = 6 -> Microsoft.OpenApi.OData.Edm.ODataPathKind
Microsoft.OpenApi.OData.Edm.ODataPathKind.Singleton = 2 -> Microsoft.OpenApi.OData.Edm.ODataPathKind
Microsoft.OpenApi.OData.Edm.ODataPathKind.Unknown = 10 -> Microsoft.OpenApi.OData.Edm.ODataPathKind
Microsoft.OpenApi.OData.Edm.ODataPathKind.TypeCast = 10 -> Microsoft.OpenApi.OData.Edm.ODataPathKind
Microsoft.OpenApi.OData.Edm.ODataPathKind.Unknown = 11 -> Microsoft.OpenApi.OData.Edm.ODataPathKind
Microsoft.OpenApi.OData.Edm.ODataPathProvider
Microsoft.OpenApi.OData.Edm.ODataPathProvider.ODataPathProvider() -> void
Microsoft.OpenApi.OData.Edm.ODataRefSegment
@ -132,6 +133,8 @@ Microsoft.OpenApi.OData.OpenApiConvertSettings.PathProvider.get -> Microsoft.Ope
Microsoft.OpenApi.OData.OpenApiConvertSettings.PathProvider.set -> void
Microsoft.OpenApi.OData.OpenApiConvertSettings.EnableDollarCountPath.get -> bool
Microsoft.OpenApi.OData.OpenApiConvertSettings.EnableDollarCountPath.set -> void
Microsoft.OpenApi.OData.OpenApiConvertSettings.EnableODataTypeCast.get -> bool
Microsoft.OpenApi.OData.OpenApiConvertSettings.EnableODataTypeCast.set -> void
Microsoft.OpenApi.OData.OpenApiConvertSettings.PrefixEntityTypeNameBeforeKey.get -> bool
Microsoft.OpenApi.OData.OpenApiConvertSettings.PrefixEntityTypeNameBeforeKey.set -> void
Microsoft.OpenApi.OData.OpenApiConvertSettings.RequireDerivedTypesConstraintForBoundOperations.get -> bool