Skip to content

Commit e176372

Browse files
garethj-msftxuzhg
authored andcommitted
Fix paths when operation is bound to a type derived from the type of a
navigation property. (#88)
1 parent 1643ba5 commit e176372

File tree

10 files changed

+405
-28
lines changed

10 files changed

+405
-28
lines changed

.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[*.{cs,vb}]
2+
3+
# IDE0009: Member access should be qualified.
4+
dotnet_diagnostic.IDE0009.severity = none

Microsoft.OpenApi.OData.sln

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26730.3
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.30907.101
55
MinimumVisualStudioVersion = 10.0.40219.1
66
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}"
77
EndProject
@@ -11,6 +11,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OoasUtil", "src\OoasUtil\Oo
1111
EndProject
1212
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OoasGui", "src\OoasGui\OoasGui.csproj", "{79B190E8-EDB0-4C03-8FD8-EB48E4807CFB}"
1313
EndProject
14+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{99C5C9A7-63FD-4E78-96E8-69C402868C3E}"
15+
ProjectSection(SolutionItems) = preProject
16+
.editorconfig = .editorconfig
17+
EndProjectSection
18+
EndProject
1419
Global
1520
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1621
Debug|Any CPU = Debug|Any CPU

src/Microsoft.OpenApi.OData.Reader/Edm/IODataPathProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ public interface IODataPathProvider
2424
/// Generate the list of <see cref="ODataPath"/> based on the given <see cref="IEdmModel"/>.
2525
/// </summary>
2626
/// <param name="model">The Edm model.</param>
27+
/// <param name="settings">The conversion settings.</param>
2728
/// <returns>The collection of built <see cref="ODataPath"/>.</returns>
28-
IEnumerable<ODataPath> GetPaths(IEdmModel model);
29+
IEnumerable<ODataPath> GetPaths(IEdmModel model, OpenApiConvertSettings settings);
2930
}
3031
}

src/Microsoft.OpenApi.OData.Reader/Edm/ODataContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ public void AppendTag(OpenApiTag tagItem)
158158
/// <returns>All acceptable OData path.</returns>
159159
private IEnumerable<ODataPath> LoadAllODataPaths()
160160
{
161-
IEnumerable<ODataPath> allPaths = _pathProvider.GetPaths(Model);
161+
IEnumerable<ODataPath> allPaths = _pathProvider.GetPaths(Model, Settings);
162162
foreach (var path in allPaths)
163163
{
164164
if ((path.Kind == ODataPathKind.Operation && !Settings.EnableOperationPath) ||

src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
44
// ------------------------------------------------------------
55

6+
using System;
67
using System.Collections.Generic;
78
using System.Diagnostics;
89
using System.Linq;
910
using Microsoft.OData.Edm;
11+
using Microsoft.OData.Edm.Vocabularies;
1012

1113
namespace Microsoft.OpenApi.OData.Edm
1214
{
@@ -38,8 +40,9 @@ public class ODataPathProvider : IODataPathProvider
3840
/// Generate the list of <see cref="ODataPath"/> based on the given <see cref="IEdmModel"/>.
3941
/// </summary>
4042
/// <param name="model">The Edm model.</param>
43+
/// <param name="settings">The conversion settings.</param>
4144
/// <returns>The collection of built <see cref="ODataPath"/>.</returns>
42-
public virtual IEnumerable<ODataPath> GetPaths(IEdmModel model)
45+
public virtual IEnumerable<ODataPath> GetPaths(IEdmModel model, OpenApiConvertSettings settings)
4346
{
4447
if (model == null || model.EntityContainer == null)
4548
{
@@ -67,7 +70,7 @@ public virtual IEnumerable<ODataPath> GetPaths(IEdmModel model)
6770
}
6871

6972
// bound operations
70-
RetrieveBoundOperationPaths();
73+
RetrieveBoundOperationPaths(settings);
7174

7275
// unbound operations
7376
foreach (IEdmOperationImport import in _model.EntityContainer.OperationImports())
@@ -338,7 +341,7 @@ private bool ShouldExpandNavigationProperty(IEdmNavigationProperty navigationPro
338341
/// <summary>
339342
/// Retrieve all bounding <see cref="IEdmOperation"/>.
340343
/// </summary>
341-
private void RetrieveBoundOperationPaths()
344+
private void RetrieveBoundOperationPaths(OpenApiConvertSettings convertSettings)
342345
{
343346
foreach (var edmOperation in _model.GetAllElements().OfType<IEdmOperation>().Where(e => e.IsBound))
344347
{
@@ -396,10 +399,17 @@ private void RetrieveBoundOperationPaths()
396399
}
397400

398401
// 3. Search for derived
399-
if (AppendBoundOperationOnDerived(edmOperation, isCollection, bindingEntityType))
402+
if (AppendBoundOperationOnDerived(edmOperation, isCollection, bindingEntityType, convertSettings))
400403
{
401404
continue;
402405
}
406+
407+
// 4. Search for derived generated navigation property
408+
if (AppendBoundOperationOnDerivedNavigationPropertyPath(edmOperation, isCollection, bindingEntityType, convertSettings))
409+
{
410+
continue;
411+
}
412+
403413
}
404414
}
405415
}
@@ -477,7 +487,11 @@ private bool AppendBoundOperationOnNavigationPropertyPath(IEdmOperation edmOpera
477487
return found;
478488
}
479489

480-
private bool AppendBoundOperationOnDerived(IEdmOperation edmOperation, bool isCollection, IEdmEntityType bindingEntityType)
490+
private bool AppendBoundOperationOnDerived(
491+
IEdmOperation edmOperation,
492+
bool isCollection,
493+
IEdmEntityType bindingEntityType,
494+
OpenApiConvertSettings convertSettings)
481495
{
482496
bool found = false;
483497

@@ -488,6 +502,14 @@ private bool AppendBoundOperationOnDerived(IEdmOperation edmOperation, bool isCo
488502
{
489503
foreach (var ns in baseNavigationSource)
490504
{
505+
if (HasUnsatisfiedDerivedTypeConstraint(
506+
ns as IEdmVocabularyAnnotatable,
507+
baseType,
508+
convertSettings))
509+
{
510+
continue;
511+
}
512+
491513
if (isCollection)
492514
{
493515
if (ns is IEdmEntitySet)
@@ -523,5 +545,84 @@ private bool AppendBoundOperationOnDerived(IEdmOperation edmOperation, bool isCo
523545
return found;
524546
}
525547

548+
private bool HasUnsatisfiedDerivedTypeConstraint(
549+
IEdmVocabularyAnnotatable annotatable,
550+
IEdmEntityType baseType,
551+
OpenApiConvertSettings convertSettings)
552+
{
553+
return convertSettings.RequireDerivedTypesConstraintForBoundOperations &&
554+
!(_model.GetCollection(annotatable, "Org.OData.Validation.V1.DerivedTypeConstraint") ?? Enumerable.Empty<string>())
555+
.Any(c => c.Equals(baseType.FullName(), StringComparison.OrdinalIgnoreCase));
556+
}
557+
558+
private bool AppendBoundOperationOnDerivedNavigationPropertyPath(
559+
IEdmOperation edmOperation,
560+
bool isCollection,
561+
IEdmEntityType bindingEntityType,
562+
OpenApiConvertSettings convertSettings)
563+
{
564+
bool found = false;
565+
bool isEscapedFunction = _model.IsUrlEscapeFunction(edmOperation);
566+
567+
foreach (var baseType in bindingEntityType.FindAllBaseTypes())
568+
{
569+
if (_allNavigationPropertyPaths.TryGetValue(baseType, out IList<ODataPath> paths))
570+
{
571+
foreach (var path in paths)
572+
{
573+
if (path.Kind == ODataPathKind.Ref)
574+
{
575+
continue;
576+
}
577+
578+
var npSegment = path.Segments.Last(s => s is ODataNavigationPropertySegment)
579+
as ODataNavigationPropertySegment;
580+
if (npSegment == null)
581+
{
582+
continue;
583+
}
584+
585+
bool isLastKeySegment = path.LastSegment is ODataKeySegment;
586+
587+
if (isCollection)
588+
{
589+
if (isLastKeySegment)
590+
{
591+
continue;
592+
}
593+
594+
if (npSegment.NavigationProperty.TargetMultiplicity() != EdmMultiplicity.Many)
595+
{
596+
continue;
597+
}
598+
}
599+
else
600+
{
601+
if (!isLastKeySegment && npSegment.NavigationProperty.TargetMultiplicity() ==
602+
EdmMultiplicity.Many)
603+
{
604+
continue;
605+
}
606+
}
607+
608+
if (HasUnsatisfiedDerivedTypeConstraint(
609+
npSegment.NavigationProperty as IEdmVocabularyAnnotatable,
610+
baseType,
611+
convertSettings))
612+
{
613+
continue;
614+
}
615+
616+
ODataPath newPath = path.Clone();
617+
newPath.Push(new ODataTypeCastSegment(bindingEntityType));
618+
newPath.Push(new ODataOperationSegment(edmOperation, isEscapedFunction));
619+
AppendPath(newPath);
620+
found = true;
621+
}
622+
}
623+
}
624+
625+
return found;
626+
}
526627
}
527628
}

src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,13 @@ public string PathPrefix
161161
/// </summary>
162162
public bool ShowSchemaExamples { get; set; } = false;
163163

164+
/// <summary>
165+
/// Gets/Sets a value indicating whether or not to require the
166+
/// Validation.DerivedTypeConstraint to be applied to NavigationSources
167+
/// to bind operations of derived types to them.
168+
/// </summary>
169+
public bool RequireDerivedTypesConstraintForBoundOperations { get; set; } = false;
170+
164171
/// <summary>
165172
/// Gets/sets a value indicating whether or not to show the root path of the described API.
166173
/// </summary>
@@ -197,6 +204,7 @@ internal OpenApiConvertSettings Clone()
197204
EnableDerivedTypesReferencesForRequestBody = this.EnableDerivedTypesReferencesForRequestBody,
198205
RoutePathPrefixProvider = this.RoutePathPrefixProvider,
199206
ShowLinks = this.ShowLinks,
207+
RequireDerivedTypesConstraintForBoundOperations = this.RequireDerivedTypesConstraintForBoundOperations,
200208
ShowSchemaExamples = this.ShowSchemaExamples,
201209
ShowRootPath = this.ShowRootPath,
202210
PathProvider = this.PathProvider

src/OoasGui/OoasGui.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@
7171
<AutoGen>True</AutoGen>
7272
<DependentUpon>Resources.resx</DependentUpon>
7373
</Compile>
74+
<None Include="..\..\.editorconfig">
75+
<Link>.editorconfig</Link>
76+
</None>
7477
<None Include="packages.config" />
7578
<None Include="Properties\Settings.settings">
7679
<Generator>SettingsSingleFileGenerator</Generator>

src/OoasUtil/ComLineProcesser.cs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,22 +74,29 @@ public ComLineProcesser(string[] args)
7474
/// Set the output to expect all derived types in request bodies.
7575
/// </summary>
7676
public bool? DerivedTypesReferencesForRequestBody { get; private set; }
77-
77+
7878
/// <summary>
7979
/// Set the output to expose pagination for collections.
8080
/// </summary>
8181
public bool? EnablePagination { get; private set; }
8282

8383
/// <summary>
84-
/// tSet the output to use unqualified calls for bound operations.
84+
/// Set the output to use unqualified calls for bound operations.
8585
/// </summary>
8686
public bool? EnableUnqualifiedCall { get; private set; }
8787

8888
/// <summary>
89-
/// tDisable examples in the schema.
89+
/// Disable examples in the schema.
9090
/// </summary>
9191
public bool? DisableSchemaExamples { get; private set; }
9292

93+
/// <summary>
94+
/// Gets/Sets a value indicating whether or not to require the
95+
/// Validation.DerivedTypeConstraint to be applied to NavigationSources
96+
/// to bind operations of derived types to them.
97+
/// </summary>
98+
public bool? RequireDerivedTypesConstraint { get; private set; }
99+
93100
/// <summary>
94101
/// Process the arguments.
95102
/// </summary>
@@ -186,6 +193,14 @@ public bool Process()
186193
}
187194
break;
188195

196+
case "--requireDerivedTypesConstraint":
197+
case "-rdt":
198+
if (!ProcessRequireDerivedTypesConstraint(true))
199+
{
200+
return false;
201+
}
202+
break;
203+
189204
case "--enablepagination":
190205
case "-p":
191206
if (!ProcessEnablePagination(true))
@@ -248,6 +263,11 @@ public bool Process()
248263
DerivedTypesReferencesForRequestBody = false;
249264
}
250265

266+
if (RequireDerivedTypesConstraint == null)
267+
{
268+
RequireDerivedTypesConstraint = false;
269+
}
270+
251271
if (EnablePagination == null)
252272
{
253273
EnablePagination = false;
@@ -345,6 +365,19 @@ private bool ProcessDerivedTypesReferencesForRequestBody(bool derivedTypesRefere
345365
return true;
346366
}
347367

368+
private bool ProcessRequireDerivedTypesConstraint(bool requireDerivedTypesConstraint)
369+
{
370+
if (RequireDerivedTypesConstraint != null)
371+
{
372+
Console.WriteLine("[Error:] Multiple [--requireDerivedTypesConstraint|-rdt] are not allowed.\n");
373+
PrintUsage();
374+
return false;
375+
}
376+
377+
RequireDerivedTypesConstraint = requireDerivedTypesConstraint;
378+
return true;
379+
}
380+
348381
private bool ProcessEnablePagination(bool enablePagination)
349382
{
350383
if (EnablePagination != null)
@@ -445,6 +478,7 @@ public static void PrintUsage()
445478
sb.Append(" --keyassegment|-k\t\t\tSet the output to use key-as-segment style URLs.\n");
446479
sb.Append(" --derivedtypesreferencesforresponses|-drs\t\t\tSet the output to produce all derived types in responses.\n");
447480
sb.Append(" --derivedtypesreferencesforrequestbody|-drq\t\t\tSet the output to expect all derived types in request bodies.\n");
481+
sb.Append(" --requireDerivedTypesConstraint|-rdt\t\t\tSet the output to require derived type constraint to bind Operations.\n");
448482
sb.Append(" --enablepagination|-p\t\t\tSet the output to expose pagination for collections.\n");
449483
sb.Append(" --enableunqualifiedcall|-u\t\t\tSet the output to use unqualified calls for bound operations.\n");
450484
sb.Append(" --disableschemaexamples|-x\t\t\tDisable examples in the schema.\n");

src/OoasUtil/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ static int Main(string[] args)
3737
EnableKeyAsSegment = processer.KeyAsSegment,
3838
EnableDerivedTypesReferencesForResponses = processer.DerivedTypesReferencesForResponses.Value,
3939
EnableDerivedTypesReferencesForRequestBody = processer.DerivedTypesReferencesForRequestBody.Value,
40+
RequireDerivedTypesConstraintForBoundOperations = processer.RequireDerivedTypesConstraint.Value,
4041
EnablePagination = processer.EnablePagination.Value,
4142
EnableUnqualifiedCall = processer.EnableUnqualifiedCall.Value,
4243
ShowSchemaExamples = !processer.DisableSchemaExamples.Value,

0 commit comments

Comments
 (0)