Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch annotations for paths ending with navigation properties using target path #509

Merged
merged 10 commits into from
Apr 24, 2024
Merged
58 changes: 55 additions & 3 deletions src/Microsoft.OpenApi.OData.Reader/Edm/EdmAnnotationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Vocabularies;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Common;
using Microsoft.OpenApi.OData.Vocabulary;
using Microsoft.OpenApi.OData.Vocabulary.Authorization;
Expand Down Expand Up @@ -163,6 +162,28 @@ public static T GetRecord<T>(this IEdmModel model, IEdmVocabularyAnnotatable tar
});
}

/// <summary>
/// Gets the record value (a complex type) for the given target path.
/// </summary>
/// <typeparam name="T">The CLR mapping type.</typeparam>
/// <param name="model">The Edm model.</param>
/// <param name="targetPath">The string representation of the Edm target path.</param>
/// <param name="qualifiedName">The Term qualified name.</param>
/// <returns></returns>
public static T GetRecord<T>(this IEdmModel model, string targetPath, string qualifiedName)
where T : IRecord, new()
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(targetPath, nameof(targetPath));
Utils.CheckArgumentNull(qualifiedName, nameof(qualifiedName));

IEdmTargetPath target = model.GetTargetPath(targetPath);
if (target == null)
return default;

return model.GetRecord<T>(target, qualifiedName);
}

/// <summary>
/// Gets the collection of string term value for the given <see cref="IEdmVocabularyAnnotatable"/>.
/// </summary>
Expand Down Expand Up @@ -265,12 +286,31 @@ public static IEnumerable<T> GetCollection<T>(this IEdmModel model, IEdmVocabula
/// <param name="target">The Edm target.</param>
/// <param name="linkRel">The link relation type for path operation.</param>
/// <returns>Null or the links record value (a complex type) for this annotation.</returns>
public static Link GetLinkRecord(this IEdmModel model, IEdmVocabularyAnnotatable target, string linkRel)
public static LinkType GetLinkRecord(this IEdmModel model, IEdmVocabularyAnnotatable target, string linkRel)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));

return model.GetCollection<Link>(target, CoreConstants.Links)?.FirstOrDefault(x => x.Rel == linkRel);
return model.GetCollection<LinkType>(target, CoreConstants.Links)?.FirstOrDefault(x => x.Rel == linkRel);
}

/// <summary>
/// Gets the links record value (a complex type) for the given target path.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="targetPath">The string representation of the Edm target path.</param>
/// <param name="linkRel">The link relation type for path operation.</param>
/// <returns>Null or the links record value (a complex type) for this annotation.</returns>
public static LinkType GetLinkRecord(this IEdmModel model, string targetPath, string linkRel)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(targetPath, nameof(targetPath));

IEdmTargetPath target = model.GetTargetPath(targetPath);
if (target == null)
return null;

return model.GetLinkRecord(target, linkRel);
}

/// <summary>
Expand Down Expand Up @@ -311,6 +351,18 @@ public static IEnumerable<Authorization> GetAuthorizations(this IEdmModel model,
});
}

public static string GetDescriptionAnnotation(this IEdmModel model, string targetPath)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(targetPath, nameof(targetPath));

IEdmTargetPath target = model.GetTargetPath(targetPath);
if (target == null)
return null;

return model.GetDescriptionAnnotation(target);
}

private static T GetOrAddCached<T>(this IEdmModel model, IEdmVocabularyAnnotatable target, string qualifiedName, Func<T> createFunc)
{
if (model == null || target == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,24 @@ public static OpenApiParameter CreateTop(this ODataContext context, IEdmVocabula
return null;
}

/// <summary>
/// Create the $top parameter for Edm target path.
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="targetPath">The string representation of the Edm target path.</param>
/// <returns></returns>
public static OpenApiParameter CreateTop(this ODataContext context, string targetPath)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(targetPath, nameof(targetPath));

IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
if (target == null)
return null;

return context.CreateTop(target);
}

/// <summary>
/// Create the $skip parameter.
/// </summary>
Expand All @@ -396,6 +414,24 @@ public static OpenApiParameter CreateSkip(this ODataContext context, IEdmVocabul
return null;
}

/// <summary>
/// Create the $skip parameter for Edm target path.
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="targetPath">The string representation of the Edm target path.</param>
/// <returns></returns>
public static OpenApiParameter CreateSkip(this ODataContext context, string targetPath)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(targetPath, nameof(targetPath));

IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
if (target == null)
return null;

return context.CreateSkip(target);
}

/// <summary>
/// Create the $search parameter.
/// </summary>
Expand All @@ -420,6 +456,24 @@ public static OpenApiParameter CreateSearch(this ODataContext context, IEdmVocab
return null;
}

/// <summary>
/// Create the $search parameter for Edm target path.
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="targetPath">The string representation of the Edm target path.</param>
/// <returns></returns>
public static OpenApiParameter CreateSearch(this ODataContext context, string targetPath)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(targetPath, nameof(targetPath));

IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
if (target == null)
return null;

return context.CreateSearch(target);
}

/// <summary>
/// Create the $count parameter.
/// </summary>
Expand All @@ -444,6 +498,24 @@ public static OpenApiParameter CreateCount(this ODataContext context, IEdmVocabu
return null;
}

/// <summary>
/// Create the $count parameter for Edm target path.
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="targetPath">The string representation of the Edm target path.</param>
/// <returns></returns>
public static OpenApiParameter CreateCount(this ODataContext context, string targetPath)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(targetPath, nameof(targetPath));

IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
if (target == null)
return null;

return context.CreateCount(target);
}

/// <summary>
/// Create the $filter parameter.
/// </summary>
Expand All @@ -468,6 +540,24 @@ public static OpenApiParameter CreateFilter(this ODataContext context, IEdmVocab
return null;
}

/// <summary>
/// Create the $filter parameter for Edm target path.
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="targetPath">The string representation of the Edm target path.</param>
/// <returns></returns>
public static OpenApiParameter CreateFilter(this ODataContext context, string targetPath)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(targetPath, nameof(targetPath));

IEdmTargetPath target = context.Model.GetTargetPath(targetPath);
if (target == null)
return null;

return context.CreateFilter(target);
}

public static OpenApiParameter CreateOrderBy(this ODataContext context, IEdmEntitySet entitySet)
{
Utils.CheckArgumentNull(context, nameof(context));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<PackageId>Microsoft.OpenApi.OData</PackageId>
<SignAssembly>true</SignAssembly>
<Version>1.6.1</Version>
<Version>1.6.2</Version>
<Description>This package contains the codes you need to convert OData CSDL to Open API Document of Model.</Description>
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<PackageTags>Microsoft OpenApi OData EDM</PackageTags>
<RepositoryUrl>https://github.com/Microsoft/OpenAPI.NET.OData</RepositoryUrl>
<PackageReleaseNotes>
- Generates unique DELETE operation ids of $ref paths for indexed collection navigation properties #513
</PackageReleaseNotes>
- Adds support for fetching annotations using Edm target path preferentially #514
</PackageReleaseNotes>
<AssemblyName>Microsoft.OpenApi.OData.Reader</AssemblyName>
<AssemblyOriginatorKeyFile>..\..\tool\Microsoft.OpenApi.OData.snk</AssemblyOriginatorKeyFile>
<OutputPath Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">..\..\bin\Debug\</OutputPath>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Common;
using Microsoft.OpenApi.OData.Edm;
using Microsoft.OpenApi.OData.Vocabulary.Core;

namespace Microsoft.OpenApi.OData.Operation;

Expand All @@ -17,6 +18,7 @@ internal abstract class ComplexPropertyBaseOperationHandler : OperationHandler
/// <inheritdoc/>
protected override void Initialize(ODataContext context, ODataPath path)
{
base.Initialize(context, path);
ComplexPropertySegment = path.LastSegment as ODataComplexPropertySegment ?? throw Error.ArgumentNull(nameof(path));
}

Expand All @@ -40,4 +42,23 @@ protected override void SetTags(OpenApiOperation operation)

base.SetTags(operation);
}

/// <inheritdoc/>
protected override void SetExternalDocs(OpenApiOperation operation)
{
if (Context.Settings.ShowExternalDocs)
{
var externalDocs = Context.Model.GetLinkRecord(TargetPath, CustomLinkRel) ??
Context.Model.GetLinkRecord(ComplexPropertySegment.Property, CustomLinkRel);

if (externalDocs != null)
{
operation.ExternalDocs = new OpenApiExternalDocs()
{
Description = CoreConstants.ExternalDocsDescription,
Url = externalDocs.Href
};
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,17 @@ protected override void Initialize(ODataContext context, ODataPath path)
{
base.Initialize(context, path);

_readRestrictions = Context.Model.GetRecord<ReadRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.ReadRestrictions);
_readRestrictions = Context.Model.GetRecord<ReadRestrictionsType>(TargetPath, CapabilitiesConstants.ReadRestrictions);
var complexPropertyReadRestrictions = Context.Model.GetRecord<ReadRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.ReadRestrictions);

if (_readRestrictions == null)
{
_readRestrictions = complexPropertyReadRestrictions;
}
else
{
_readRestrictions.MergePropertiesIfNull(complexPropertyReadRestrictions);
}
}

/// <inheritdoc/>
Expand Down Expand Up @@ -54,41 +64,40 @@ protected override void SetParameters(OpenApiOperation operation)
OpenApiParameter parameter;
if(ComplexPropertySegment.Property.Type.IsCollection())
{

// The parameters array contains Parameter Objects for all system query options allowed for this collection,
// and it does not list system query options not allowed for this collection, see terms
// Capabilities.TopSupported, Capabilities.SkipSupported, Capabilities.SearchRestrictions,
// Capabilities.FilterRestrictions, and Capabilities.CountRestrictions
// $top
parameter = Context.CreateTop(ComplexPropertySegment.Property);
parameter = Context.CreateTop(TargetPath) ?? Context.CreateTop(ComplexPropertySegment.Property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}

// $skip
parameter = Context.CreateSkip(ComplexPropertySegment.Property);
parameter = Context.CreateSkip(TargetPath) ?? Context.CreateSkip(ComplexPropertySegment.Property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}

// $search
parameter = Context.CreateSearch(ComplexPropertySegment.Property);
parameter = Context.CreateSearch(TargetPath) ?? Context.CreateSearch(ComplexPropertySegment.Property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}

// $filter
parameter = Context.CreateFilter(ComplexPropertySegment.Property);
parameter = Context.CreateFilter(TargetPath) ?? Context.CreateFilter(ComplexPropertySegment.Property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}

// $count
parameter = Context.CreateCount(ComplexPropertySegment.Property);
parameter = Context.CreateCount(TargetPath) ?? Context.CreateCount(ComplexPropertySegment.Property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,17 @@ protected override void Initialize(ODataContext context, ODataPath path)
throw new InvalidOperationException("OData conventions do not support POSTing to a complex property that is not a collection.");
}

_insertRestrictions = Context.Model.GetRecord<InsertRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.InsertRestrictions);
_insertRestrictions = Context.Model.GetRecord<InsertRestrictionsType>(TargetPath, CapabilitiesConstants.InsertRestrictions);
var complexPropertyInsertRestrictions = Context.Model.GetRecord<InsertRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.InsertRestrictions);

if (_insertRestrictions == null)
{
_insertRestrictions = complexPropertyInsertRestrictions;
}
else
{
_insertRestrictions.MergePropertiesIfNull(complexPropertyInsertRestrictions);
}
}
/// <inheritdoc />
public override OperationType OperationType => OperationType.Post;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@ protected override void Initialize(ODataContext context, ODataPath path)
{
base.Initialize(context, path);

_updateRestrictions = Context.Model.GetRecord<UpdateRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.UpdateRestrictions);
_updateRestrictions = Context.Model.GetRecord<UpdateRestrictionsType>(TargetPath, CapabilitiesConstants.UpdateRestrictions);
var complexPropertyUpdateRestrictions = Context.Model.GetRecord<UpdateRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.UpdateRestrictions);

if (_updateRestrictions == null)
{
_updateRestrictions = complexPropertyUpdateRestrictions;
}
else
{
_updateRestrictions.MergePropertiesIfNull(complexPropertyUpdateRestrictions);
}
}

/// <inheritdoc/>
Expand Down
Loading
Loading