Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
81be306
Update retrieving descriptions for stream properties
irvinesunday Feb 15, 2022
f7300aa
Use restrictions annotations to retrieve operations descriptions
irvinesunday Feb 15, 2022
15d5ef0
Update capturing of operation descriptions
irvinesunday Feb 16, 2022
12cda0f
Update tests; add further descriptions; remove invalid descriptions; …
irvinesunday Feb 16, 2022
3dbdbea
Update comment
irvinesunday Feb 16, 2022
8208d72
Factor out common code into helper classes
irvinesunday Feb 17, 2022
7a19e47
Get navigation restrictions from nav. sources and in-lined nav. prope…
irvinesunday Feb 17, 2022
edff362
Minor formatting changes
irvinesunday Feb 18, 2022
a04f9e1
Remove description
irvinesunday Feb 18, 2022
36758dd
Update src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs
irvinesunday Feb 21, 2022
f3f225c
Update src/Microsoft.OpenApi.OData.Reader/Operation/SingletonPatchOpe…
irvinesunday Feb 21, 2022
66f5771
Update src/Microsoft.OpenApi.OData.Reader/Operation/SingletonGetOpera…
irvinesunday Feb 21, 2022
10a5ddc
Update src/Microsoft.OpenApi.OData.Reader/Operation/EntityUpdateOpera…
irvinesunday Feb 21, 2022
f2efebb
Update src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetGetOpera…
irvinesunday Feb 21, 2022
efa8d0d
Update src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOper…
irvinesunday Feb 21, 2022
83adb6b
Update src/Microsoft.OpenApi.OData.Reader/Operation/RefGetOperationHa…
irvinesunday Feb 21, 2022
10686a8
Minor refactoring
irvinesunday Feb 21, 2022
caa5516
Get descriptions for complex properties
irvinesunday Feb 22, 2022
01a84f9
Update tests to validate retrieving descriptions for complex properties
irvinesunday Feb 22, 2022
363df8c
Merge branch 'master' into feature/is/restrictions-descriptions
irvinesunday Feb 23, 2022
fb7beaf
Update src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs
irvinesunday Feb 23, 2022
c6483d4
Update src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
irvinesunday Feb 23, 2022
6dd41ef
Update src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
irvinesunday Feb 23, 2022
6f40299
Update src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
irvinesunday Feb 23, 2022
1a936ae
Update src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs
irvinesunday Feb 23, 2022
10dfc4b
Apply PR review suggestions
irvinesunday Feb 23, 2022
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
23 changes: 23 additions & 0 deletions src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;

namespace Microsoft.OpenApi.OData.Common
{
Expand Down Expand Up @@ -61,6 +62,28 @@ internal static OpenApiSchema GetDerivedTypesReferenceSchema(IEdmStructuredType
};

return schema;
}

/// <summary>
/// Verifies whether the provided navigation restrictions allow for navigability of a navigation property.
/// </summary>
/// <param name="restrictionType">The <see cref="NavigationRestrictionsType"/>.</param>
/// <param name="restrictionProperty">The <see cref="NavigationPropertyRestriction"/>.</param>
/// <returns></returns>
internal static bool NavigationRestrictionsAllowsNavigability(
NavigationRestrictionsType restrictionType,
NavigationPropertyRestriction restrictionProperty)
{
// Verify using individual navigation restriction first
if (restrictionProperty?.Navigability != null && restrictionProperty.Navigability.Value == NavigationType.None)
{
return false;
}

// if the individual has no navigability setting, use the global navigability setting
// Default navigability for all navigation properties of the annotation target.
// Individual navigation properties can override this value via `RestrictedProperties/Navigability`.
return restrictionProperty?.Navigability != null || restrictionType == null || restrictionType.IsNavigable;
}
}
}
15 changes: 15 additions & 0 deletions src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.OpenApi.OData.Edm;
using Microsoft.OpenApi.OData.Vocabulary;

namespace Microsoft.OpenApi.OData.Common
Expand Down Expand Up @@ -115,5 +116,19 @@ internal static string CheckArgumentNullOrEmpty(string value, string parameterNa
/// <returns>The changed string.</returns>
internal static string ToFirstCharacterLowerCase(this string input)
=> string.IsNullOrEmpty(input) ? input : $"{char.ToLowerInvariant(input.FirstOrDefault())}{input.Substring(1)}";

/// <summary>
/// Gets the navigation path.
/// </summary>
/// <param name="path">The <see cref="ODataPath"/>.</param>
/// <param name="navigationPropertyName">Optional: The navigation property name.</param>
internal static string NavigationPropertyPath(this ODataPath path, string navigationPropertyName = null)
{
string value = string.Join("/",
path.Segments.Where(s => !(s is ODataKeySegment || s is ODataNavigationSourceSegment
|| s is ODataStreamContentSegment || s is ODataStreamPropertySegment)).Select(e => e.Identifier));

return navigationPropertyName == null ? value : $"{value}/{navigationPropertyName}";
}
}
}
39 changes: 28 additions & 11 deletions src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,36 +360,52 @@ private void RetrieveNavigationPropertyPaths(

if (visitedNavigationProperties == null)
{
visitedNavigationProperties = new ();
visitedNavigationProperties = new();
}

var navPropFullyQualifiedName = $"{navigationProperty.DeclaringType.FullTypeName()}/{navigationProperty.Name}";

// Check whether the navigation property has already been navigated in the path
string navPropFullyQualifiedName = $"{navigationProperty.DeclaringType.FullTypeName()}/{navigationProperty.Name}";

// Check whether the navigation property has already been navigated in the path.
if (visitedNavigationProperties.Contains(navPropFullyQualifiedName))
{
return;
}

// Get the annotatable navigation source for this navigation property.
IEdmVocabularyAnnotatable annotatableNavigationSource = (currentPath.FirstSegment as ODataNavigationSourceSegment)?.NavigationSource as IEdmVocabularyAnnotatable;
NavigationRestrictionsType navSourceRestrictionType = null;
NavigationRestrictionsType navPropRestrictionType = null;

// Get the NavigationRestrictions referenced by this navigation property: Can be defined in the navigation source or in-lined in the navigation property.
if (annotatableNavigationSource != null)
{
navSourceRestrictionType = _model.GetRecord<NavigationRestrictionsType>(annotatableNavigationSource, CapabilitiesConstants.NavigationRestrictions);
navPropRestrictionType = _model.GetRecord<NavigationRestrictionsType>(navigationProperty, CapabilitiesConstants.NavigationRestrictions);
}

NavigationPropertyRestriction restriction = navSourceRestrictionType?.RestrictedProperties?
.FirstOrDefault(r => r.NavigationProperty == currentPath.NavigationPropertyPath(navigationProperty.Name))
?? navPropRestrictionType?.RestrictedProperties?.FirstOrDefault();

// Check whether the navigation property should be part of the path
NavigationRestrictionsType navigation = _model.GetRecord<NavigationRestrictionsType>(navigationProperty, CapabilitiesConstants.NavigationRestrictions);
if (navigation != null && !navigation.IsNavigable)
if (!EdmModelHelper.NavigationRestrictionsAllowsNavigability(navSourceRestrictionType, restriction) ||
!EdmModelHelper.NavigationRestrictionsAllowsNavigability(navPropRestrictionType, restriction))
{
return;
}

// Whether to expand the navigation property
// Whether to expand the navigation property.
bool shouldExpand = navigationProperty.ContainsTarget;

// append a navigation property.
// Append a navigation property.
currentPath.Push(new ODataNavigationPropertySegment(navigationProperty));
AppendPath(currentPath.Clone());
visitedNavigationProperties.Push(navPropFullyQualifiedName);

// Check whether a collection-valued navigation property should be indexed by key value(s).
NavigationPropertyRestriction restriction = navigation?.RestrictedProperties?.FirstOrDefault();
bool indexableByKey = restriction?.IndexableByKey ?? true;

if (restriction == null || restriction.IndexableByKey == true)
if (indexableByKey)
{
IEdmEntityType navEntityType = navigationProperty.ToEntityType();
var targetsMany = navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many;
Expand All @@ -409,7 +425,8 @@ private void RetrieveNavigationPropertyPaths(
// ~/entityset/{key}/single-valued-Nav/subtype
CreateTypeCastPaths(currentPath, convertSettings, navigationProperty.DeclaringType, navigationProperty, targetsMany);

if (navigation?.Referenceable == true)
if ((navSourceRestrictionType?.Referenceable ?? false) ||
(navPropRestrictionType?.Referenceable ?? false))
{
// Referenceable navigation properties
// Single-Valued: ~/entityset/{key}/single-valued-Nav/$ref
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ internal class ComplexPropertyGetOperationHandler : ComplexPropertyBaseOperation
{
/// <inheritdoc />
public override OperationType OperationType => OperationType.Get;

/// <summary>
/// Gets/Sets the <see cref="ReadRestrictionsType"/>
/// </summary>
private ReadRestrictionsType ReadRestrictions { get; set; }

protected override void Initialize(ODataContext context, ODataPath path)
{
base.Initialize(context, path);

ReadRestrictions = Context.Model.GetRecord<ReadRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.ReadRestrictions);
}

/// <inheritdoc/>
protected override void SetBasicInfo(OpenApiOperation operation)
{
Expand All @@ -33,6 +46,9 @@ protected override void SetBasicInfo(OpenApiOperation operation)
operation.OperationId = ComplexPropertySegment.Property.Name + "." + typeName + listOrGet + Utils.UpperFirstChar(typeName);
}

// Description
operation.Description = ReadRestrictions?.Description ?? Context.Model.GetDescriptionAnnotation(ComplexPropertySegment.Property);

base.SetBasicInfo(operation);
}
/// <inheritdoc/>
Expand Down Expand Up @@ -192,31 +208,29 @@ private void SetSingleResponse(OpenApiOperation operation)
}
protected override void SetSecurity(OpenApiOperation operation)
{
ReadRestrictionsType read = Context.Model.GetRecord<ReadRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.ReadRestrictions);
if (read == null || read.Permissions == null)
if (ReadRestrictions?.Permissions == null)
{
return;
}

operation.Security = Context.CreateSecurityRequirements(read.Permissions).ToList();
operation.Security = Context.CreateSecurityRequirements(ReadRestrictions.Permissions).ToList();
}

protected override void AppendCustomParameters(OpenApiOperation operation)
{
ReadRestrictionsType read = Context.Model.GetRecord<ReadRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.ReadRestrictions);
if (read == null)
{
if (ReadRestrictions == null)
{
return;
}

if (read.CustomHeaders != null)
if (ReadRestrictions.CustomHeaders != null)
{
AppendCustomParameters(operation, read.CustomHeaders, ParameterLocation.Header);
AppendCustomParameters(operation, ReadRestrictions.CustomHeaders, ParameterLocation.Header);
}

if (read.CustomQueryOptions != null)
if (ReadRestrictions.CustomQueryOptions != null)
{
AppendCustomParameters(operation, read.CustomQueryOptions, ParameterLocation.Query);
AppendCustomParameters(operation, ReadRestrictions.CustomQueryOptions, ParameterLocation.Query);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,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);
}
/// <inheritdoc />
public override OperationType OperationType => OperationType.Post;

/// <summary>
/// Gets/Sets the <see cref="InsertRestrictionsType"/>
/// </summary>
private InsertRestrictionsType InsertRestrictions { get; set; }

/// <inheritdoc/>
protected override void SetBasicInfo(OpenApiOperation operation)
{
Expand All @@ -42,6 +49,9 @@ protected override void SetBasicInfo(OpenApiOperation operation)
operation.OperationId = ComplexPropertySegment.Property.Name + "." + typeName + ".Set" + Utils.UpperFirstChar(typeName);
}

// Description
operation.Description = InsertRestrictions?.Description;

base.SetBasicInfo(operation);
}

Expand Down Expand Up @@ -103,31 +113,29 @@ protected override void SetResponses(OpenApiOperation operation)

protected override void SetSecurity(OpenApiOperation operation)
{
InsertRestrictionsType insert = Context.Model.GetRecord<InsertRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.InsertRestrictions);
if (insert == null || insert.Permissions == null)
if (InsertRestrictions?.Permissions == null)
{
return;
}

operation.Security = Context.CreateSecurityRequirements(insert.Permissions).ToList();
operation.Security = Context.CreateSecurityRequirements(InsertRestrictions.Permissions).ToList();
}

protected override void AppendCustomParameters(OpenApiOperation operation)
{
InsertRestrictionsType insert = Context.Model.GetRecord<InsertRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.InsertRestrictions);
if (insert == null)
if (InsertRestrictions == null)
{
return;
}

if (insert.CustomQueryOptions != null)
if (InsertRestrictions.CustomQueryOptions != null)
{
AppendCustomParameters(operation, insert.CustomQueryOptions, ParameterLocation.Query);
AppendCustomParameters(operation, InsertRestrictions.CustomQueryOptions, ParameterLocation.Query);
}

if (insert.CustomHeaders != null)
if (InsertRestrictions.CustomHeaders != null)
{
AppendCustomParameters(operation, insert.CustomHeaders, ParameterLocation.Header);
AppendCustomParameters(operation, InsertRestrictions.CustomHeaders, ParameterLocation.Header);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,26 @@ namespace Microsoft.OpenApi.OData.Operation;

internal abstract class ComplexPropertyUpdateOperationHandler : ComplexPropertyBaseOperationHandler
{
/// <summary>
/// Gets/Sets the <see cref="UpdateRestrictionsType"/>
/// </summary>
private UpdateRestrictionsType UpdateRestrictions { get; set; }

protected override void Initialize(ODataContext context, ODataPath path)
{
base.Initialize(context, path);

UpdateRestrictions = Context.Model.GetRecord<UpdateRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.UpdateRestrictions);
}

/// <inheritdoc/>
protected override void SetBasicInfo(OpenApiOperation operation)
{
// Summary
operation.Summary = $"Update property {ComplexPropertySegment.Property.Name} value.";

// Description
operation.Description = Context.Model.GetDescriptionAnnotation(ComplexPropertySegment.Property);
operation.Description = UpdateRestrictions?.Description;

// OperationId
if (Context.Settings.EnableOperationId)
Expand Down Expand Up @@ -87,31 +99,29 @@ protected override void SetResponses(OpenApiOperation operation)
}
protected override void SetSecurity(OpenApiOperation operation)
{
UpdateRestrictionsType update = Context.Model.GetRecord<UpdateRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.UpdateRestrictions);
if (update == null || update.Permissions == null)
if (UpdateRestrictions?.Permissions == null)
{
return;
}

operation.Security = Context.CreateSecurityRequirements(update.Permissions).ToList();
operation.Security = Context.CreateSecurityRequirements(UpdateRestrictions.Permissions).ToList();
}

protected override void AppendCustomParameters(OpenApiOperation operation)
{
UpdateRestrictionsType update = Context.Model.GetRecord<UpdateRestrictionsType>(ComplexPropertySegment.Property, CapabilitiesConstants.UpdateRestrictions);
if (update == null)
if (UpdateRestrictions == null)
{
return;
}

if (update.CustomHeaders != null)
if (UpdateRestrictions.CustomHeaders != null)
{
AppendCustomParameters(operation, update.CustomHeaders, ParameterLocation.Header);
AppendCustomParameters(operation, UpdateRestrictions.CustomHeaders, ParameterLocation.Header);
}

if (update.CustomQueryOptions != null)
if (UpdateRestrictions.CustomQueryOptions != null)
{
AppendCustomParameters(operation, update.CustomQueryOptions, ParameterLocation.Query);
AppendCustomParameters(operation, UpdateRestrictions.CustomQueryOptions, ParameterLocation.Query);
}
}
}
Loading