Skip to content

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

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

Merged
merged 10 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/Microsoft.OpenApi.OData.Reader/Edm/EdmAnnotationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,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 @@ -273,6 +295,25 @@ public static Link GetLinkRecord(this IEdmModel model, IEdmVocabularyAnnotatable
return model.GetCollection<Link>(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 Link 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>
/// Create the corresponding Authorization object.
/// </summary>
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,13 +15,13 @@
<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
- Prefer fetching descriptions and link annotations using Edm target path #514
</PackageReleaseNotes>
<AssemblyName>Microsoft.OpenApi.OData.Reader</AssemblyName>
<AssemblyOriginatorKeyFile>..\..\tool\Microsoft.OpenApi.OData.snk</AssemblyOriginatorKeyFile>
Expand All @@ -41,7 +41,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.OData.Edm" Version="7.20.0" />
<PackageReference Include="Microsoft.OData.Edm" Version="7.21.0" />
<PackageReference Include="Microsoft.OpenApi" Version="1.6.14" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,41 +54,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 @@ -179,13 +179,13 @@ protected override void SetParameters(OpenApiOperation operation)

OpenApiParameter parameter;

parameter = Context.CreateSearch(annotatable);
parameter = Context.CreateSearch(TargetPath) ?? Context.CreateSearch(annotatable);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}

parameter = Context.CreateFilter(annotatable);
parameter = Context.CreateFilter(TargetPath) ?? Context.CreateFilter(annotatable);
if (parameter != null)
{
operation.Parameters.Add(parameter);
Expand All @@ -199,8 +199,10 @@ protected override void AppendCustomParameters(OpenApiOperation operation)
return;
}

ReadRestrictionsType readRestrictions = Context.Model.GetRecord<ReadRestrictionsType>(annotatable, CapabilitiesConstants.ReadRestrictions);
ReadRestrictionsType readRestrictions = Context.Model.GetRecord<ReadRestrictionsType>(TargetPath, CapabilitiesConstants.ReadRestrictions)
?? Context.Model.GetRecord<ReadRestrictionsType>(annotatable, CapabilitiesConstants.ReadRestrictions);


if (readRestrictions == null)
{
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,31 +169,31 @@ protected override void SetParameters(OpenApiOperation operation)
{
// Need to verify that TopSupported or others should be applied to navigation source.
// So, how about for the navigation property.
OpenApiParameter parameter = Context.CreateTop(NavigationProperty);
OpenApiParameter parameter = Context.CreateTop(TargetPath) ?? Context.CreateTop(NavigationProperty);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}

parameter = Context.CreateSkip(NavigationProperty);
parameter = Context.CreateSkip(TargetPath) ?? Context.CreateSkip(NavigationProperty);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}

parameter = Context.CreateSearch(NavigationProperty);
parameter = Context.CreateSearch(TargetPath) ?? Context.CreateSearch(NavigationProperty);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}

parameter = Context.CreateFilter(NavigationProperty);
parameter = Context.CreateFilter(TargetPath) ?? Context.CreateFilter(NavigationProperty);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}

parameter = Context.CreateCount(NavigationProperty);
parameter = Context.CreateCount(TargetPath) ?? Context.CreateCount(NavigationProperty);
if (parameter != null)
{
operation.Parameters.Add(parameter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
// 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.Any;
using Microsoft.OpenApi.Models;
Expand All @@ -13,6 +11,8 @@
using Microsoft.OpenApi.OData.Vocabulary;
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
using Microsoft.OpenApi.OData.Vocabulary.Core;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.OpenApi.OData.Operation
{
Expand Down Expand Up @@ -113,16 +113,22 @@ internal string GetOperationId(string prefix = null)
/// <inheritdoc/>
protected override void SetExternalDocs(OpenApiOperation operation)
{
if (Context.Settings.ShowExternalDocs && Context.Model.GetLinkRecord(NavigationProperty, CustomLinkRel) is Link externalDocs)
if (Context.Settings.ShowExternalDocs)
{
operation.ExternalDocs = new OpenApiExternalDocs()
{
Description = CoreConstants.ExternalDocsDescription,
Url = externalDocs.Href
};
var externalDocs = Context.Model.GetLinkRecord(TargetPath, CustomLinkRel) ??
Context.Model.GetLinkRecord(NavigationProperty, CustomLinkRel);

if (externalDocs != null)
{
operation.ExternalDocs = new OpenApiExternalDocs()
{
Description = CoreConstants.ExternalDocsDescription,
Url = externalDocs.Href
};
}
}
}
}
/// <summary>
/// Retrieves the CRUD restrictions annotations for the navigation property
/// in context, given a capability annotation term.
Expand All @@ -134,12 +140,16 @@ protected IRecord GetRestrictionAnnotation(string annotationTerm)
return annotationTerm switch
{
CapabilitiesConstants.ReadRestrictions => Restriction?.ReadRestrictions ??
Context.Model.GetRecord<ReadRestrictionsType>(TargetPath, CapabilitiesConstants.ReadRestrictions) ??
Context.Model.GetRecord<ReadRestrictionsType>(NavigationProperty, CapabilitiesConstants.ReadRestrictions),
CapabilitiesConstants.UpdateRestrictions => Restriction?.UpdateRestrictions ??
Context.Model.GetRecord<UpdateRestrictionsType>(TargetPath, CapabilitiesConstants.UpdateRestrictions) ??
Context.Model.GetRecord<UpdateRestrictionsType>(NavigationProperty, CapabilitiesConstants.UpdateRestrictions),
CapabilitiesConstants.InsertRestrictions => Restriction?.InsertRestrictions ??
Context.Model.GetRecord<InsertRestrictionsType>(TargetPath, CapabilitiesConstants.InsertRestrictions) ??
Context.Model.GetRecord<InsertRestrictionsType>(NavigationProperty, CapabilitiesConstants.InsertRestrictions),
CapabilitiesConstants.DeleteRestrictions => Restriction?.DeleteRestrictions ??
Context.Model.GetRecord<DeleteRestrictionsType>(TargetPath, CapabilitiesConstants.DeleteRestrictions) ??
Context.Model.GetRecord<DeleteRestrictionsType>(NavigationProperty, CapabilitiesConstants.DeleteRestrictions),
_ => null,
};
Expand Down
Loading