Skip to content

Commit

Permalink
Fetch annotations for paths ending with navigation properties using t…
Browse files Browse the repository at this point in the history
…arget path (#509)

* Fetch annotations for paths ending with navigation properties using target path

* Allow fetching annotations using either path or navigation property as target

* Update tests

* Temporarily remove typecasts from TargetPath used for fetching annotations until we have a fix in the Edm library

* Update release notes

* Update Microsoft.OData.Edm library which now allows type casts in annotation target paths

* Update tests

* Merge annotations if some are defined out of line using TargetPath and other inline on the schema element

* Update release notes
  • Loading branch information
millicentachieng authored Apr 24, 2024
1 parent 8adfed4 commit f8cef27
Show file tree
Hide file tree
Showing 48 changed files with 12,609 additions and 1,369 deletions.
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

0 comments on commit f8cef27

Please sign in to comment.