Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[*.{cs,vb}]

# IDE0009: Member access should be qualified.
dotnet_diagnostic.IDE0009.severity = none
9 changes: 7 additions & 2 deletions Microsoft.OpenApi.OData.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.3
# Visual Studio Version 16
VisualStudioVersion = 16.0.30907.101
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.OData.Reader", "src\Microsoft.OpenApi.OData.Reader\Microsoft.OpenApi.OData.Reader.csproj", "{FF3ACD93-19E0-486C-9C0F-FA1C2E7FC8C2}"
EndProject
Expand All @@ -11,6 +11,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OoasUtil", "src\OoasUtil\Oo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OoasGui", "src\OoasGui\OoasGui.csproj", "{79B190E8-EDB0-4C03-8FD8-EB48E4807CFB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{99C5C9A7-63FD-4E78-96E8-69C402868C3E}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.OpenApi.OData.Reader/Edm/IODataPathProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public interface IODataPathProvider
/// Generate the list of <see cref="ODataPath"/> based on the given <see cref="IEdmModel"/>.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="settings">The conversion settings.</param>
/// <returns>The collection of built <see cref="ODataPath"/>.</returns>
IEnumerable<ODataPath> GetPaths(IEdmModel model);
IEnumerable<ODataPath> GetPaths(IEdmModel model, OpenApiConvertSettings settings);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change. But, I am ok to add this.

}
}
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi.OData.Reader/Edm/ODataContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public void AppendTag(OpenApiTag tagItem)
/// <returns>All acceptable OData path.</returns>
private IEnumerable<ODataPath> LoadAllODataPaths()
{
IEnumerable<ODataPath> allPaths = _pathProvider.GetPaths(Model);
IEnumerable<ODataPath> allPaths = _pathProvider.GetPaths(Model, Settings);
foreach (var path in allPaths)
{
if ((path.Kind == ODataPathKind.Operation && !Settings.EnableOperationPath) ||
Expand Down
111 changes: 106 additions & 5 deletions src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Vocabularies;

namespace Microsoft.OpenApi.OData.Edm
{
Expand Down Expand Up @@ -38,8 +40,9 @@ public class ODataPathProvider : IODataPathProvider
/// Generate the list of <see cref="ODataPath"/> based on the given <see cref="IEdmModel"/>.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="settings">The conversion settings.</param>
/// <returns>The collection of built <see cref="ODataPath"/>.</returns>
public virtual IEnumerable<ODataPath> GetPaths(IEdmModel model)
public virtual IEnumerable<ODataPath> GetPaths(IEdmModel model, OpenApiConvertSettings settings)
{
if (model == null || model.EntityContainer == null)
{
Expand Down Expand Up @@ -67,7 +70,7 @@ public virtual IEnumerable<ODataPath> GetPaths(IEdmModel model)
}

// bound operations
RetrieveBoundOperationPaths();
RetrieveBoundOperationPaths(settings);

// unbound operations
foreach (IEdmOperationImport import in _model.EntityContainer.OperationImports())
Expand Down Expand Up @@ -338,7 +341,7 @@ private bool ShouldExpandNavigationProperty(IEdmNavigationProperty navigationPro
/// <summary>
/// Retrieve all bounding <see cref="IEdmOperation"/>.
/// </summary>
private void RetrieveBoundOperationPaths()
private void RetrieveBoundOperationPaths(OpenApiConvertSettings convertSettings)
{
foreach (var edmOperation in _model.GetAllElements().OfType<IEdmOperation>().Where(e => e.IsBound))
{
Expand Down Expand Up @@ -396,10 +399,17 @@ private void RetrieveBoundOperationPaths()
}

// 3. Search for derived
if (AppendBoundOperationOnDerived(edmOperation, isCollection, bindingEntityType))
if (AppendBoundOperationOnDerived(edmOperation, isCollection, bindingEntityType, convertSettings))
{
continue;
}

// 4. Search for derived generated navigation property
if (AppendBoundOperationOnDerivedNavigationPropertyPath(edmOperation, isCollection, bindingEntityType, convertSettings))
{
continue;
}

}
}
}
Expand Down Expand Up @@ -477,7 +487,11 @@ private bool AppendBoundOperationOnNavigationPropertyPath(IEdmOperation edmOpera
return found;
}

private bool AppendBoundOperationOnDerived(IEdmOperation edmOperation, bool isCollection, IEdmEntityType bindingEntityType)
private bool AppendBoundOperationOnDerived(
IEdmOperation edmOperation,
bool isCollection,
IEdmEntityType bindingEntityType,
OpenApiConvertSettings convertSettings)
{
bool found = false;

Expand All @@ -488,6 +502,14 @@ private bool AppendBoundOperationOnDerived(IEdmOperation edmOperation, bool isCo
{
foreach (var ns in baseNavigationSource)
{
if (HasUnsatisfiedDerivedTypeConstraint(
ns as IEdmVocabularyAnnotatable,
baseType,
convertSettings))
{
continue;
}

if (isCollection)
{
if (ns is IEdmEntitySet)
Expand Down Expand Up @@ -523,5 +545,84 @@ private bool AppendBoundOperationOnDerived(IEdmOperation edmOperation, bool isCo
return found;
}

private bool HasUnsatisfiedDerivedTypeConstraint(
IEdmVocabularyAnnotatable annotatable,
IEdmEntityType baseType,
OpenApiConvertSettings convertSettings)
{
return convertSettings.RequireDerivedTypesConstraintForBoundOperations &&
!(_model.GetCollection(annotatable, "Org.OData.Validation.V1.DerivedTypeConstraint") ?? Enumerable.Empty<string>())
.Any(c => c.Equals(baseType.FullName(), StringComparison.OrdinalIgnoreCase));
}

private bool AppendBoundOperationOnDerivedNavigationPropertyPath(
IEdmOperation edmOperation,
bool isCollection,
IEdmEntityType bindingEntityType,
OpenApiConvertSettings convertSettings)
{
bool found = false;
bool isEscapedFunction = _model.IsUrlEscapeFunction(edmOperation);

foreach (var baseType in bindingEntityType.FindAllBaseTypes())
{
if (_allNavigationPropertyPaths.TryGetValue(baseType, out IList<ODataPath> paths))
{
foreach (var path in paths)
{
if (path.Kind == ODataPathKind.Ref)
{
continue;
}

var npSegment = path.Segments.Last(s => s is ODataNavigationPropertySegment)
as ODataNavigationPropertySegment;
if (npSegment == null)
{
continue;
}

bool isLastKeySegment = path.LastSegment is ODataKeySegment;

if (isCollection)
{
if (isLastKeySegment)
{
continue;
}

if (npSegment.NavigationProperty.TargetMultiplicity() != EdmMultiplicity.Many)
{
continue;
}
}
else
{
if (!isLastKeySegment && npSegment.NavigationProperty.TargetMultiplicity() ==
EdmMultiplicity.Many)
{
continue;
}
}

if (HasUnsatisfiedDerivedTypeConstraint(
npSegment.NavigationProperty as IEdmVocabularyAnnotatable,
baseType,
convertSettings))
{
continue;
}

ODataPath newPath = path.Clone();
newPath.Push(new ODataTypeCastSegment(bindingEntityType));
newPath.Push(new ODataOperationSegment(edmOperation, isEscapedFunction));
AppendPath(newPath);
found = true;
}
}
}

return found;
}
}
}
8 changes: 8 additions & 0 deletions src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ public string PathPrefix
/// </summary>
public bool ShowSchemaExamples { get; set; } = false;

/// <summary>
/// Gets/Sets a value indicating whether or not to require the
/// Validation.DerivedTypeConstraint to be applied to NavigationSources
/// to bind operations of derived types to them.
/// </summary>
public bool RequireDerivedTypesConstraintForBoundOperations { get; set; } = false;

/// <summary>
/// Gets/sets a value indicating whether or not to show the root path of the described API.
/// </summary>
Expand Down Expand Up @@ -197,6 +204,7 @@ internal OpenApiConvertSettings Clone()
EnableDerivedTypesReferencesForRequestBody = this.EnableDerivedTypesReferencesForRequestBody,
RoutePathPrefixProvider = this.RoutePathPrefixProvider,
ShowLinks = this.ShowLinks,
RequireDerivedTypesConstraintForBoundOperations = this.RequireDerivedTypesConstraintForBoundOperations,
ShowSchemaExamples = this.ShowSchemaExamples,
ShowRootPath = this.ShowRootPath,
PathProvider = this.PathProvider
Expand Down
3 changes: 3 additions & 0 deletions src/OoasGui/OoasGui.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="..\..\.editorconfig">
<Link>.editorconfig</Link>
</None>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
Expand Down
40 changes: 37 additions & 3 deletions src/OoasUtil/ComLineProcesser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,29 @@ public ComLineProcesser(string[] args)
/// Set the output to expect all derived types in request bodies.
/// </summary>
public bool? DerivedTypesReferencesForRequestBody { get; private set; }

/// <summary>
/// Set the output to expose pagination for collections.
/// </summary>
public bool? EnablePagination { get; private set; }

/// <summary>
/// tSet the output to use unqualified calls for bound operations.
/// Set the output to use unqualified calls for bound operations.
/// </summary>
public bool? EnableUnqualifiedCall { get; private set; }

/// <summary>
/// tDisable examples in the schema.
/// Disable examples in the schema.
/// </summary>
public bool? DisableSchemaExamples { get; private set; }

/// <summary>
/// Gets/Sets a value indicating whether or not to require the
/// Validation.DerivedTypeConstraint to be applied to NavigationSources
/// to bind operations of derived types to them.
/// </summary>
public bool? RequireDerivedTypesConstraint { get; private set; }

/// <summary>
/// Process the arguments.
/// </summary>
Expand Down Expand Up @@ -186,6 +193,14 @@ public bool Process()
}
break;

case "--requireDerivedTypesConstraint":
case "-rdt":
if (!ProcessRequireDerivedTypesConstraint(true))
{
return false;
}
break;

case "--enablepagination":
case "-p":
if (!ProcessEnablePagination(true))
Expand Down Expand Up @@ -248,6 +263,11 @@ public bool Process()
DerivedTypesReferencesForRequestBody = false;
}

if (RequireDerivedTypesConstraint == null)
{
RequireDerivedTypesConstraint = false;
}

if (EnablePagination == null)
{
EnablePagination = false;
Expand Down Expand Up @@ -345,6 +365,19 @@ private bool ProcessDerivedTypesReferencesForRequestBody(bool derivedTypesRefere
return true;
}

private bool ProcessRequireDerivedTypesConstraint(bool requireDerivedTypesConstraint)
{
if (RequireDerivedTypesConstraint != null)
{
Console.WriteLine("[Error:] Multiple [--requireDerivedTypesConstraint|-rdt] are not allowed.\n");
PrintUsage();
return false;
}

RequireDerivedTypesConstraint = requireDerivedTypesConstraint;
return true;
}

private bool ProcessEnablePagination(bool enablePagination)
{
if (EnablePagination != null)
Expand Down Expand Up @@ -445,6 +478,7 @@ public static void PrintUsage()
sb.Append(" --keyassegment|-k\t\t\tSet the output to use key-as-segment style URLs.\n");
sb.Append(" --derivedtypesreferencesforresponses|-drs\t\t\tSet the output to produce all derived types in responses.\n");
sb.Append(" --derivedtypesreferencesforrequestbody|-drq\t\t\tSet the output to expect all derived types in request bodies.\n");
sb.Append(" --requireDerivedTypesConstraint|-rdt\t\t\tSet the output to require derived type constraint to bind Operations.\n");
sb.Append(" --enablepagination|-p\t\t\tSet the output to expose pagination for collections.\n");
sb.Append(" --enableunqualifiedcall|-u\t\t\tSet the output to use unqualified calls for bound operations.\n");
sb.Append(" --disableschemaexamples|-x\t\t\tDisable examples in the schema.\n");
Expand Down
1 change: 1 addition & 0 deletions src/OoasUtil/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ static int Main(string[] args)
EnableKeyAsSegment = processer.KeyAsSegment,
EnableDerivedTypesReferencesForResponses = processer.DerivedTypesReferencesForResponses.Value,
EnableDerivedTypesReferencesForRequestBody = processer.DerivedTypesReferencesForRequestBody.Value,
RequireDerivedTypesConstraintForBoundOperations = processer.RequireDerivedTypesConstraint.Value,
EnablePagination = processer.EnablePagination.Value,
EnableUnqualifiedCall = processer.EnableUnqualifiedCall.Value,
ShowSchemaExamples = !processer.DisableSchemaExamples.Value,
Expand Down
Loading