modify the operation and parameter generator to use the capabilites

This commit is contained in:
Sam Xu 2018-01-18 18:20:02 -08:00
parent be25a389a6
commit a9a68fb662
16 changed files with 578 additions and 548 deletions

View file

@ -0,0 +1,97 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Vocabularies;
using Microsoft.OpenApi.OData.Common;
namespace Microsoft.OpenApi.OData.Capabilities
{
internal static class CapabilitiesHelper
{
/// <summary>
/// Gets description for term Org.OData.Core.V1.Description from a target annotatable
/// </summary>
/// <param name="model">The model referenced to.</param>
/// <param name="target">The target Annotatable to find annotation</param>
/// <returns>Description for term Org.OData.Core.V1.Description</returns>
public static IEdmVocabularyAnnotation GetCapabilitiesAnnotation(this IEdmModel model, IEdmVocabularyAnnotatable target, string qualifiedName)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));
Utils.CheckArgumentNull(qualifiedName, nameof(qualifiedName));
IEdmTerm term = model.FindTerm(qualifiedName);
if (term != null)
{
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
if (annotation != null)
{
return annotation;
}
}
return null;
}
/// <summary>
/// Gets description for term Org.OData.Core.V1.Description from a target annotatable
/// </summary>
/// <param name="model">The model referenced to.</param>
/// <param name="target">The target Annotatable to find annotation</param>
/// <returns>Description for term Org.OData.Core.V1.Description</returns>
public static IEdmVocabularyAnnotation GetCapabilitiesAnnotation(this IEdmModel model, IEdmVocabularyAnnotatable target, IEdmTerm term)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));
Utils.CheckArgumentNull(term, nameof(term));
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
if (annotation != null)
{
return annotation;
}
return null;
}
private static bool? GetBoolean(this IEdmModel model, IEdmVocabularyAnnotatable target, IEdmTerm term, string propertyName = null)
{
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
if (annotation != null)
{
switch (annotation.Value.ExpressionKind)
{
case EdmExpressionKind.Record:
IEdmRecordExpression recordExpression = (IEdmRecordExpression)annotation.Value;
if (recordExpression != null)
{
IEdmPropertyConstructor property = recordExpression.Properties.FirstOrDefault(e => e.Name == propertyName);
if (property != null)
{
IEdmBooleanConstantExpression value = property.Value as IEdmBooleanConstantExpression;
if (value != null)
{
return value.Value;
}
}
}
break;
case EdmExpressionKind.BooleanConstant:
IEdmBooleanConstantExpression boolConstant = (IEdmBooleanConstantExpression)annotation.Value;
if (boolConstant != null)
{
return boolConstant.Value;
}
break;
}
}
return null;
}
}
}

View file

@ -40,7 +40,7 @@ namespace Microsoft.OpenApi.OData.Capabilities
public CapabilitiesRestrictions(IEdmModel model, IEdmVocabularyAnnotatable target)
{
Model = model ?? throw Error.ArgumentNull(nameof(model));
Target = Target ?? throw Error.ArgumentNull(nameof(target));
Target = target ?? throw Error.ArgumentNull(nameof(target));
}
protected void Initialize()
@ -56,8 +56,11 @@ namespace Microsoft.OpenApi.OData.Capabilities
IEdmNavigationSource navigationSource = Target as IEdmNavigationSource;
// if not, search the entity type.
IEdmEntityType entityType = navigationSource.EntityType();
annotation = Model.GetCapabilitiesAnnotation(entityType, QualifiedName);
if (navigationSource != null)
{
IEdmEntityType entityType = navigationSource.EntityType();
annotation = Model.GetCapabilitiesAnnotation(entityType, QualifiedName);
}
}
Initialize(annotation);

View file

@ -16,8 +16,8 @@ namespace Microsoft.OpenApi.OData.Capabilities
internal class CountRestrictions : CapabilitiesRestrictions
{
private bool _countable = true;
private IList<string> _nonCountableProperties;
private IList<string> _nonCountableNavigationProperties;
private IList<string> _nonCountableProperties = new List<string>();
private IList<string> _nonCountableNavigationProperties = new List<string>();
/// <summary>
/// The Term type name.

View file

@ -16,7 +16,7 @@ namespace Microsoft.OpenApi.OData.Capabilities
internal class DeleteRestrictions : CapabilitiesRestrictions
{
private bool _deletable = true;
private IList<string> _nonDeletableNavigationProperties;
private IList<string> _nonDeletableNavigationProperties = new List<string>();
/// <summary>
/// The Term type name.

View file

@ -1,391 +0,0 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using System.Linq;
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Vocabularies;
using Microsoft.OpenApi.OData.Common;
namespace Microsoft.OpenApi.OData.Capabilities
{
internal static class EdmModelCapabilitiesHelper
{
public static IEnumerable<string> GetAscendingOnlyProperties(this IEdmModel model, IEdmVocabularyAnnotatable target)
{
IEdmRecordExpression record = model.GetSortRestrictionsAnnotation(target);
if (record != null)
{
IEdmPropertyConstructor property = record.Properties.FirstOrDefault(e => e.Name == "AscendingOnlyProperties");
if (property != null)
{
IEdmCollectionExpression value = property.Value as IEdmCollectionExpression;
if (value != null)
{
foreach(var a in value.Elements.Select(e => e as EdmPropertyPathExpression))
{
yield return a.Path;
}
}
}
}
yield return null;
}
public static IEnumerable<string> GetDescendingOnlyProperties(this IEdmModel model, IEdmVocabularyAnnotatable target)
{
IEdmRecordExpression record = model.GetSortRestrictionsAnnotation(target);
if (record != null)
{
IEdmPropertyConstructor property = record.Properties.FirstOrDefault(e => e.Name == "DescendingOnlyProperties");
if (property != null)
{
IEdmCollectionExpression value = property.Value as IEdmCollectionExpression;
if (value != null)
{
foreach (var a in value.Elements.Select(e => e as EdmPropertyPathExpression))
{
yield return a.Path;
}
}
}
}
yield return null;
}
public static IEnumerable<string> GetNonSortableProperties(this IEdmModel model, IEdmVocabularyAnnotatable target)
{
IEdmRecordExpression record = model.GetSortRestrictionsAnnotation(target);
if (record != null)
{
IEdmPropertyConstructor property = record.Properties.FirstOrDefault(e => e.Name == "NonSortablePropeties");
if (property != null)
{
IEdmCollectionExpression value = property.Value as IEdmCollectionExpression;
if (value != null)
{
foreach (var a in value.Elements.Select(e => e as EdmPropertyPathExpression))
{
yield return a.Path;
}
}
}
}
yield return null;
}
/// <summary>
/// Gets description for term Org.OData.Core.V1.Description from a target annotatable
/// </summary>
/// <param name="model">The model referenced to.</param>
/// <param name="target">The target Annotatable to find annotation</param>
/// <returns>Description for term Org.OData.Core.V1.Description</returns>
public static IEdmVocabularyAnnotation GetCapabilitiesAnnotation(this IEdmModel model, IEdmVocabularyAnnotatable target, string qualifiedName)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));
Utils.CheckArgumentNull(qualifiedName, nameof(qualifiedName));
IEdmTerm term = model.FindTerm(qualifiedName);
if (term != null)
{
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
if (annotation != null)
{
return annotation;
}
}
return null;
}
/// <summary>
/// Gets description for term Org.OData.Core.V1.Description from a target annotatable
/// </summary>
/// <param name="model">The model referenced to.</param>
/// <param name="target">The target Annotatable to find annotation</param>
/// <returns>Description for term Org.OData.Core.V1.Description</returns>
public static IEdmVocabularyAnnotation GetCapabilitiesAnnotation(this IEdmModel model, IEdmVocabularyAnnotatable target, IEdmTerm term)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));
Utils.CheckArgumentNull(term, nameof(term));
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
if (annotation != null)
{
return annotation;
}
return null;
}
/// <summary>
/// Gets description for term Org.OData.Core.V1.Description from a target annotatable
/// </summary>
/// <param name="model">The model referenced to.</param>
/// <param name="target">The target Annotatable to find annotation</param>
/// <returns>Description for term Org.OData.Core.V1.Description</returns>
public static IEdmRecordExpression GetSortRestrictionsAnnotation(this IEdmModel model, IEdmVocabularyAnnotatable target)
{
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
if (target == null)
{
throw Error.ArgumentNull(nameof(target));
}
IEdmTerm term = model.FindTerm(CapabilitiesConstants.SortRestrictions);
if (term == null)
{
return null;
}
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
if (annotation != null)
{
IEdmRecordExpression recordExpression = annotation.Value as IEdmRecordExpression;
if (recordExpression != null)
{
return recordExpression;
}
}
return null;
}
/// <summary>
/// Gets description for term Org.OData.Core.V1.Description from a target annotatable
/// </summary>
/// <param name="model">The model referenced to.</param>
/// <param name="target">The target Annotatable to find annotation</param>
/// <returns>Description for term Org.OData.Core.V1.Description</returns>
public static IEdmRecordExpression GetFilterRestrictionsAnnotation(this IEdmModel model, IEdmVocabularyAnnotatable target)
{
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
if (target == null)
{
throw Error.ArgumentNull(nameof(target));
}
var a = model.FindDeclaredVocabularyAnnotations(target);
IEdmTerm term = model.FindTerm("Org.OData.Capabilities.V1.FilterRestrictions");
if (term == null)
{
return null;
}
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
if (annotation != null)
{
IEdmRecordExpression recordExpression = annotation.Value as IEdmRecordExpression;
if (recordExpression != null)
{
return recordExpression;
}
}
return null;
}
/// <summary>
/// Gets bool value for term Org.OData.Core.V1.TopSupported from a target annotatable
/// </summary>
/// <param name="model">The model referenced to.</param>
/// <param name="target">The target Annotatable to find annotation</param>
/// <returns>Boolean value for term Org.OData.Core.V1.TopSupported</returns>
public static bool IsTopSupported(this IEdmModel model, IEdmVocabularyAnnotatable target)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));
IEdmTerm term = model.FindTerm(CapabilitiesConstants.TopSupported);
if (term != null)
{
bool? boolValue = model.GetBoolean(target, term);
if (boolValue != null)
{
return boolValue.Value;
}
}
// by default, it supports $top.
return true;
}
/// <summary>
/// Gets bool value for term Org.OData.Core.V1.SkipSupported from a target annotatable
/// </summary>
/// <param name="model">The model referenced to.</param>
/// <param name="target">The target Annotatable to find annotation</param>
/// <returns>Boolean value for term Org.OData.Core.V1.SkipSupported</returns>
public static bool IsSkipSupported(this IEdmModel model, IEdmVocabularyAnnotatable target)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));
IEdmTerm term = model.FindTerm(CapabilitiesConstants.SkipSupported);
if (term != null)
{
bool? boolValue = model.GetBoolean(target, term);
if (boolValue != null)
{
return boolValue.Value;
}
}
// by default, it supports $skip.
return true;
}
/// <summary>
/// Gets Sortable value for term Org.OData.Core.V1.SortRestrictions from a target annotatable
/// </summary>
/// <param name="model">The model referenced to.</param>
/// <param name="target">The target Annotatable to find annotation</param>
/// <returns>Boolean value for term Org.OData.Core.V1.SortRestrictions</returns>
public static bool IsSortable(this IEdmModel model, IEdmVocabularyAnnotatable target)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));
IEdmTerm term = model.FindTerm(CapabilitiesConstants.SortRestrictions);
if (term != null)
{
bool? boolValue = model.GetBoolean(target, term, "Sortable");
if (boolValue != null)
{
return boolValue.Value;
}
}
return true; // by default, it's sortable.
}
/// <summary>
/// Gets Searchable property value for term Org.OData.Core.V1.SearchRestritions from a target annotatable
/// </summary>
/// <param name="model">The model referenced to.</param>
/// <param name="target">The target Annotatable to find annotation</param>
/// <returns>Boolean value for term Org.OData.Core.V1.SearchRestritions</returns>
public static bool IsSearchable(this IEdmModel model, IEdmVocabularyAnnotatable target)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));
IEdmTerm term = model.FindTerm(CapabilitiesConstants.SearchRestrictions);
if (term != null)
{
bool? boolValue = model.GetBoolean(target, term, "Searchable");
if (boolValue != null)
{
return boolValue.Value;
}
}
// by default, it supports $search.
return true;
}
/// <summary>
/// Gets Countable property value for term Org.OData.Core.V1.CountRestrictions from a target annotatable
/// </summary>
/// <param name="model">The model referenced to.</param>
/// <param name="target">The target Annotatable to find annotation</param>
/// <returns>Boolean value for term Org.OData.Core.V1.CountRestrictions</returns>
public static bool IsCountable(this IEdmModel model, IEdmVocabularyAnnotatable target)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));
IEdmTerm term = model.FindTerm(CapabilitiesConstants.CountRestrictions);
if (term != null)
{
bool? boolValue = model.GetBoolean(target, term, "Countable");
if (boolValue != null)
{
return boolValue.Value;
}
}
// by default, it supports $count.
return true;
}
/// <summary>
/// Gets Filterable property value for term Org.OData.Core.V1.FilterRestrictions from a target annotatable
/// </summary>
/// <param name="model">The model referenced to.</param>
/// <param name="target">The target Annotatable to find annotation</param>
/// <returns>Boolean value for term Org.OData.Core.V1.FilterRestrictions</returns>
public static bool IsFilterable(this IEdmModel model, IEdmVocabularyAnnotatable target)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));
IEdmTerm term = model.FindTerm(CapabilitiesConstants.FilterRestrictions);
if (term != null)
{
bool? boolValue = model.GetBoolean(target, term, "Filterable");
if (boolValue != null)
{
return boolValue.Value;
}
}
// by default, it supports $filter.
return true;
}
private static bool? GetBoolean(this IEdmModel model, IEdmVocabularyAnnotatable target, IEdmTerm term, string propertyName = null)
{
IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(target, term).FirstOrDefault();
if (annotation != null)
{
switch (annotation.Value.ExpressionKind)
{
case EdmExpressionKind.Record:
IEdmRecordExpression recordExpression = (IEdmRecordExpression)annotation.Value;
if (recordExpression != null)
{
IEdmPropertyConstructor property = recordExpression.Properties.FirstOrDefault(e => e.Name == propertyName);
if (property != null)
{
IEdmBooleanConstantExpression value = property.Value as IEdmBooleanConstantExpression;
if (value != null)
{
return value.Value;
}
}
}
break;
case EdmExpressionKind.BooleanConstant:
IEdmBooleanConstantExpression boolConstant = (IEdmBooleanConstantExpression)annotation.Value;
if (boolConstant != null)
{
return boolConstant.Value;
}
break;
}
}
return null;
}
}
}

View file

@ -16,7 +16,7 @@ namespace Microsoft.OpenApi.OData.Capabilities
internal class ExpandRestrictions : CapabilitiesRestrictions
{
private bool _expandable = true;
private IList<string> _nonExpandableProperties;
private IList<string> _nonExpandableProperties = new List<string>();
/// <summary>
/// The Term type name.

View file

@ -17,8 +17,8 @@ namespace Microsoft.OpenApi.OData.Capabilities
{
private bool _filterable = true;
private bool? _requiresFilter;
private IList<string> _requiredProperties;
private IList<string> _nonFilterableProperties;
private IList<string> _requiredProperties = new List<string>();
private IList<string> _nonFilterableProperties = new List<string>();
/// <summary>
/// The Term type name.

View file

@ -16,7 +16,7 @@ namespace Microsoft.OpenApi.OData.Capabilities
internal class InsertRestrictions : CapabilitiesRestrictions
{
private bool _insertable = true;
private IList<string> _nonInsertableNavigationProperties;
private IList<string> _nonInsertableNavigationProperties = new List<string>();
/// <summary>
/// The Term type name.

View file

@ -53,7 +53,7 @@ namespace Microsoft.OpenApi.OData.Capabilities
}
private NavigationType _navigability = NavigationType.Single;
private IList<NavigationPropertyRestriction> _restrictedProperties;
private IList<NavigationPropertyRestriction> _restrictedProperties = new List<NavigationPropertyRestriction>();
/// <summary>
/// The Term type name.

View file

@ -1,11 +0,0 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
namespace Microsoft.OpenApi.OData.Reader.Capabilities.Tests
{
public class SkipSupportedTests
{
}
}

View file

@ -16,9 +16,9 @@ namespace Microsoft.OpenApi.OData.Capabilities
internal class SortRestrictions : CapabilitiesRestrictions
{
private bool _sortable = true;
private IList<string> _ascendingOnlyProperties;
private IList<string> _descendingOnlyProperties;
private IList<string> _nonSortableProperties;
private IList<string> _ascendingOnlyProperties = new List<string>();
private IList<string> _descendingOnlyProperties = new List<string>();
private IList<string> _nonSortableProperties = new List<string>();
/// <summary>
/// The Term type name.

View file

@ -16,7 +16,7 @@ namespace Microsoft.OpenApi.OData.Capabilities
internal class UpdateRestrictions : CapabilitiesRestrictions
{
private bool _updatable = true;
private IList<string> _nonUpdatableNavigationProperties;
private IList<string> _nonUpdatableNavigationProperties = new List<string>();
/// <summary>
/// The Term type name.

View file

@ -52,43 +52,70 @@ namespace Microsoft.OpenApi.OData.Generator
operation.OperationId = operation.Summary;
}
IEdmEntityType entityType = entitySet.EntityType();
// The parameters array contains Parameter Objects for system query options allowed for this entity set,
// and it does not list system query options not allowed for this entity set.
operation.Parameters = new List<OpenApiParameter>
// 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
operation.Parameters = new List<OpenApiParameter>();
// $top
OpenApiParameter parameter = context.CreateTop(entitySet);
if (parameter != null)
{
new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "top" }
},
new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "skip" }
},
new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "search" }
},
new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "filter" }
},
new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "count" }
},
operation.Parameters.Add(parameter);
}
// the syntax of the system query options $expand, $select, and $orderby is too flexible
// to be formally described with OpenAPI Specification means, yet the typical use cases
// of just providing a comma-separated list of properties can be expressed via an array-valued
// parameter with an enum constraint
entityType.CreateOrderByParameter(),
// $skip
parameter = context.CreateSkip(entitySet);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
entityType.CreateSelectParameter(),
// $search
parameter = context.CreateSearch(entitySet);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
entityType.CreateExpandParameter(),
};
// $filter
parameter = context.CreateFilter(entitySet);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
// $count
parameter = context.CreateCount(entitySet);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
// the syntax of the system query options $expand, $select, and $orderby is too flexible
// to be formally described with OpenAPI Specification means, yet the typical use cases
// of just providing a comma-separated list of properties can be expressed via an array-valued
// parameter with an enum constraint
// $order
parameter = context.CreateOrderBy(entitySet);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
// $select
parameter = context.CreateSelect(entitySet);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
// $expand
parameter = context.CreateExpand(entitySet);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
// The value of responses is a Responses Object.
// It contains a name/value pair for the success case (HTTP response code 200)
@ -273,9 +300,19 @@ namespace Microsoft.OpenApi.OData.Generator
operation.Parameters = context.CreateKeyParameters(entitySet.EntityType());
operation.Parameters.Add(entityType.CreateSelectParameter());
// $select
OpenApiParameter parameter = context.CreateSelect(entitySet);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
operation.Parameters.Add(entityType.CreateExpandParameter());
// $expand
parameter = context.CreateExpand(entitySet);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
operation.Responses = new OpenApiResponses
{
@ -482,8 +519,20 @@ namespace Microsoft.OpenApi.OData.Generator
operation.Parameters = new List<OpenApiParameter>();
IEdmEntityType entityType = singleton.EntityType();
operation.Parameters.Add(entityType.CreateSelectParameter());
operation.Parameters.Add(entityType.CreateExpandParameter());
// $select
OpenApiParameter parameter = context.CreateSelect(singleton);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
// $expand
parameter = context.CreateExpand(singleton);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
operation.Responses = new OpenApiResponses
{
@ -637,45 +686,69 @@ namespace Microsoft.OpenApi.OData.Generator
{
// The parameters array contains Parameter Objects for system query options allowed for this entity set,
// and it does not list system query options not allowed for this entity set.
operation.Parameters = new List<OpenApiParameter>
operation.Parameters = new List<OpenApiParameter>();
OpenApiParameter parameter = context.CreateTop(property);
if (parameter != null)
{
new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "top" }
},
new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "skip" }
},
new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "search" }
},
new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "filter" }
},
new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "count" }
},
operation.Parameters.Add(parameter);
}
// the syntax of the system query options $expand, $select, and $orderby is too flexible
// to be formally described with OpenAPI Specification means, yet the typical use cases
// of just providing a comma-separated list of properties can be expressed via an array-valued
// parameter with an enum constraint
navEntityType.CreateOrderByParameter(),
parameter = context.CreateSkip(property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
navEntityType.CreateSelectParameter(),
parameter = context.CreateSearch(property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
navEntityType.CreateExpandParameter(),
};
parameter = context.CreateFilter(property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
parameter = context.CreateCount(property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
parameter = context.CreateOrderBy(property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
parameter = context.CreateSelect(property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
parameter = context.CreateExpand(property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
}
else
{
operation.Parameters.Add(navEntityType.CreateSelectParameter());
OpenApiParameter parameter = context.CreateSelect(property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
operation.Parameters.Add(navEntityType.CreateExpandParameter());
parameter = context.CreateExpand(property);
if (parameter != null)
{
operation.Parameters.Add(parameter);
}
}
operation.Responses = new OpenApiResponses

View file

@ -8,6 +8,9 @@ using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Common;
using Microsoft.OpenApi.OData.Capabilities;
using Microsoft.OData.Edm.Vocabularies;
namespace Microsoft.OpenApi.OData.Generator
{
@ -156,16 +159,189 @@ namespace Microsoft.OpenApi.OData.Generator
return parameters;
}
/// <summary>
/// Create the $top parameter.
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="target">The Edm annotation target.</param>
/// <returns>The created <see cref="OpenApiParameter"/> or null.</returns>
public static OpenApiParameter CreateTop(this ODataContext context, IEdmVocabularyAnnotatable target)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(target, nameof(target));
TopSupported top = new TopSupported(context.Model, target);
if (top.Supported)
{
return new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "top" }
};
}
return null;
}
/// <summary>
/// Create the $skip parameter.
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="target">The Edm annotation target.</param>
/// <returns>The created <see cref="OpenApiParameter"/> or null.</returns>
public static OpenApiParameter CreateSkip(this ODataContext context, IEdmVocabularyAnnotatable target)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(target, nameof(target));
SkipSupported skip = new SkipSupported(context.Model, target);
if (skip.Supported)
{
return new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "skip" }
};
}
return null;
}
/// <summary>
/// Create the $search parameter.
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="target">The Edm annotation target.</param>
/// <returns>The created <see cref="OpenApiParameter"/> or null.</returns>
public static OpenApiParameter CreateSearch(this ODataContext context, IEdmVocabularyAnnotatable target)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(target, nameof(target));
SearchRestrictions search = new SearchRestrictions(context.Model, target);
if (search.Searchable)
{
return new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "search" }
};
}
return null;
}
/// <summary>
/// Create the $count parameter.
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="target">The Edm annotation target.</param>
/// <returns>The created <see cref="OpenApiParameter"/> or null.</returns>
public static OpenApiParameter CreateCount(this ODataContext context, IEdmVocabularyAnnotatable target)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(target, nameof(target));
CountRestrictions count = new CountRestrictions(context.Model, target);
if (count.Countable)
{
return new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "count" }
};
}
return null;
}
/// <summary>
/// Create the $filter parameter.
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="target">The Edm annotation target.</param>
/// <returns>The created <see cref="OpenApiParameter"/> or null.</returns>
public static OpenApiParameter CreateFilter(this ODataContext context, IEdmVocabularyAnnotatable target)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(target, nameof(target));
FilterRestrictions filter = new FilterRestrictions(context.Model, target);
if (filter.Filterable)
{
return new OpenApiParameter
{
Reference = new OpenApiReference { Type = ReferenceType.Parameter, Id = "filter" }
};
}
return null;
}
public static OpenApiParameter CreateOrderBy(this ODataContext context, IEdmEntitySet entitySet)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(entitySet, nameof(entitySet));
return context.CreateOrderBy(entitySet, entitySet.EntityType());
}
public static OpenApiParameter CreateOrderBy(this ODataContext context, IEdmSingleton singleton)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(singleton, nameof(singleton));
return context.CreateOrderBy(singleton, singleton.EntityType());
}
public static OpenApiParameter CreateOrderBy(this ODataContext context, IEdmNavigationProperty navigationProperty)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(navigationProperty, nameof(navigationProperty));
return context.CreateOrderBy(navigationProperty, navigationProperty.DeclaringEntityType());
}
/// <summary>
/// Create $orderby parameter for the <see cref="IEdmEntitySet"/>.
/// </summary>
/// <param name="entityType">The Edm entity type.</param>
/// <returns>The created <see cref="OpenApiParameter"/>.</returns>
public static OpenApiParameter CreateOrderByParameter(this IEdmEntityType entityType)
/// <param name="context">The OData context.</param>
/// <param name="target">The Edm annotation target.</param>
/// <returns>The created <see cref="OpenApiParameter"/> or null.</returns>
public static OpenApiParameter CreateOrderBy(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmEntityType entityType)
{
if (entityType == null)
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(target, nameof(target));
Utils.CheckArgumentNull(entityType, nameof(entityType));
SortRestrictions sort = new SortRestrictions(context.Model, target);
if (!sort.Sortable)
{
throw Error.ArgumentNull(nameof(entityType));
return null;
}
IList<IOpenApiAny> orderByItems = new List<IOpenApiAny>();
foreach (var property in entityType.StructuralProperties())
{
if (sort.IsNonsortableProperty(property))
{
continue;
}
bool isAscOnly = sort.IsAscendingOnlyProperty(property);
bool isDescOnly = sort.IsDescendingOnlyProperty(property);
if (isAscOnly || isDescOnly)
{
if (isAscOnly)
{
orderByItems.Add(new OpenApiString(property.Name));
}
else
{
orderByItems.Add(new OpenApiString(property.Name + " desc"));
}
}
else
{
orderByItems.Add(new OpenApiString(property.Name));
orderByItems.Add(new OpenApiString(property.Name + " desc"));
}
}
return new OpenApiParameter
@ -180,22 +356,69 @@ namespace Microsoft.OpenApi.OData.Generator
Items = new OpenApiSchema
{
Type = "string",
Enum = VisitOrderbyItems(entityType)
Enum = orderByItems
}
}
};
}
/// <summary>
public static OpenApiParameter CreateSelect(this ODataContext context, IEdmEntitySet entitySet)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(entitySet, nameof(entitySet));
return context.CreateSelect(entitySet, entitySet.EntityType());
}
public static OpenApiParameter CreateSelect(this ODataContext context, IEdmSingleton singleton)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(singleton, nameof(singleton));
return context.CreateSelect(singleton, singleton.EntityType());
}
public static OpenApiParameter CreateSelect(this ODataContext context, IEdmNavigationProperty navigationProperty)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(navigationProperty, nameof(navigationProperty));
return context.CreateSelect(navigationProperty, navigationProperty.DeclaringEntityType());
}
// <summary>
/// Create $select parameter for the <see cref="IEdmNavigationSource"/>.
/// </summary>
/// <param name="entityType">The Edm entity type.</param>
/// <returns>The created <see cref="OpenApiParameter"/>.</returns>
public static OpenApiParameter CreateSelectParameter(this IEdmEntityType entityType)
/// <param name="context">The OData context.</param>
/// <param name="navigationSource">The Edm navigation source.</param>
/// <returns>The created <see cref="OpenApiParameter"/> or null.</returns>
public static OpenApiParameter CreateSelect(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmEntityType entityType)
{
if (entityType == null)
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(target, nameof(target));
Utils.CheckArgumentNull(entityType, nameof(entityType));
NavigationRestrictions navigation = new NavigationRestrictions(context.Model, target);
if (navigation.Navigability == NavigationType.None)
{
throw Error.ArgumentNull(nameof(entityType));
return null;
}
IList<IOpenApiAny> selectItems = new List<IOpenApiAny>();
foreach (var property in entityType.StructuralProperties())
{
selectItems.Add(new OpenApiString(property.Name));
}
foreach (var property in entityType.NavigationProperties())
{
if (navigation.IsNonNavigationProperty(property))
{
continue;
}
selectItems.Add(new OpenApiString(property.Name));
}
return new OpenApiParameter
@ -210,22 +433,68 @@ namespace Microsoft.OpenApi.OData.Generator
Items = new OpenApiSchema
{
Type = "string",
Enum = VisitSelectItems(entityType)
Enum = selectItems
}
}
};
}
public static OpenApiParameter CreateExpand(this ODataContext context, IEdmEntitySet entitySet)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(entitySet, nameof(entitySet));
return context.CreateExpand(entitySet, entitySet.EntityType());
}
public static OpenApiParameter CreateExpand(this ODataContext context, IEdmSingleton singleton)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(singleton, nameof(singleton));
return context.CreateExpand(singleton, singleton.EntityType());
}
public static OpenApiParameter CreateExpand(this ODataContext context, IEdmNavigationProperty navigationProperty)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(navigationProperty, nameof(navigationProperty));
return context.CreateExpand(navigationProperty, navigationProperty.DeclaringEntityType());
}
/// <summary>
/// Create $expand parameter for the <see cref="IEdmNavigationSource"/>.
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="target">The edm entity path.</param>
/// <param name="entityType">The edm entity path.</param>
/// <returns>The created <see cref="OpenApiParameter"/>.</returns>
public static OpenApiParameter CreateExpandParameter(this IEdmEntityType entityType)
/// <returns>The created <see cref="OpenApiParameter"/> or null.</returns>
public static OpenApiParameter CreateExpand(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmEntityType entityType)
{
if (entityType == null)
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(target, nameof(target));
Utils.CheckArgumentNull(entityType, nameof(entityType));
ExpandRestrictions expand = new ExpandRestrictions(context.Model, target);
if (!expand.Expandable)
{
throw Error.ArgumentNull(nameof(entityType));
return null;
}
IList<IOpenApiAny> expandItems = new List<IOpenApiAny>
{
new OpenApiString("*")
};
foreach (var property in entityType.NavigationProperties())
{
if (expand.IsNonExpandableProperty(property.Name))
{
continue;
}
expandItems.Add(new OpenApiString(property.Name));
}
return new OpenApiParameter
@ -240,7 +509,7 @@ namespace Microsoft.OpenApi.OData.Generator
Items = new OpenApiSchema
{
Type = "string",
Enum = VisitExpandItems(entityType)
Enum = expandItems
}
}
};
@ -323,45 +592,5 @@ namespace Microsoft.OpenApi.OData.Generator
}
};
}
private static IList<IOpenApiAny> VisitOrderbyItems(IEdmEntityType entityType)
{
IList<IOpenApiAny> orderByItems = new List<IOpenApiAny>();
foreach (var property in entityType.StructuralProperties())
{
orderByItems.Add(new OpenApiString(property.Name));
orderByItems.Add(new OpenApiString(property.Name + " desc"));
}
return orderByItems;
}
private static IList<IOpenApiAny> VisitSelectItems(IEdmEntityType entityType)
{
IList<IOpenApiAny> selectItems = new List<IOpenApiAny>();
foreach (var property in entityType.StructuralProperties())
{
selectItems.Add(new OpenApiString(property.Name));
}
return selectItems;
}
private static IList<IOpenApiAny> VisitExpandItems(IEdmEntityType entityType)
{
IList<IOpenApiAny> expandItems = new List<IOpenApiAny>
{
new OpenApiString("*")
};
foreach (var property in entityType.NavigationProperties())
{
expandItems.Add(new OpenApiString(property.Name));
}
return expandItems;
}
}
}

View file

@ -88,7 +88,10 @@
"AddressInfo",
"HomeAddress",
"FavoriteFeature",
"Features"
"Features",
"Friends",
"BestFriend",
"Trips"
],
"type": "string"
}
@ -206,7 +209,10 @@
"AddressInfo",
"HomeAddress",
"FavoriteFeature",
"Features"
"Features",
"Friends",
"BestFriend",
"Trips"
],
"type": "string"
}
@ -1095,7 +1101,10 @@
"AddressInfo",
"HomeAddress",
"FavoriteFeature",
"Features"
"Features",
"Friends",
"BestFriend",
"Trips"
],
"type": "string"
}
@ -1213,7 +1222,10 @@
"AddressInfo",
"HomeAddress",
"FavoriteFeature",
"Features"
"Features",
"Friends",
"BestFriend",
"Trips"
],
"type": "string"
}
@ -1515,7 +1527,10 @@
"AddressInfo",
"HomeAddress",
"FavoriteFeature",
"Features"
"Features",
"Friends",
"BestFriend",
"Trips"
],
"type": "string"
}

View file

@ -67,6 +67,9 @@ paths:
- HomeAddress
- FavoriteFeature
- Features
- Friends
- BestFriend
- Trips
type: string
- name: $expand
in: query
@ -147,6 +150,9 @@ paths:
- HomeAddress
- FavoriteFeature
- Features
- Friends
- BestFriend
- Trips
type: string
- name: $expand
in: query
@ -717,6 +723,9 @@ paths:
- HomeAddress
- FavoriteFeature
- Features
- Friends
- BestFriend
- Trips
type: string
- name: $expand
in: query
@ -797,6 +806,9 @@ paths:
- HomeAddress
- FavoriteFeature
- Features
- Friends
- BestFriend
- Trips
type: string
- name: $expand
in: query
@ -991,6 +1003,9 @@ paths:
- HomeAddress
- FavoriteFeature
- Features
- Friends
- BestFriend
- Trips
type: string
- name: $expand
in: query