Skip to content

Commit 2814714

Browse files
authored
Merge pull request #141 from microsoft/bugifx/count-segments
fixes a bug where OData Count path items would be missing from the description
2 parents 387d070 + 00b605f commit 2814714

36 files changed

+2456
-270
lines changed

.vscode/launch.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
{
22
"version": "0.2.0",
33
"configurations": [
4+
{
5+
"name": "Launch Update Docs",
6+
"type": "coreclr",
7+
"request": "launch",
8+
"preLaunchTask": "build",
9+
"program": "${workspaceFolder}/tool/UpdateDocs/bin/Debug/net6.0/UpdateDocs.dll",
10+
"cwd": "${workspaceFolder}/tool/UpdateDocs/bin/Debug/net6.0",
11+
"console": "internalConsole",
12+
"stopAtEntry": false
13+
},
414
{
515
"name": ".NET Core Attach",
616
"type": "coreclr",

Microsoft.OpenApi.OData.sln

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
1616
.editorconfig = .editorconfig
1717
EndProjectSection
1818
EndProject
19+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tool", "tool", "{DE8F8E75-A119-4CF3-AFDD-4132B55DAE76}"
20+
EndProject
21+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateDocs", "tool\UpdateDocs\UpdateDocs.csproj", "{AAC31ECB-05F9-444A-9B86-42ECD50AA468}"
22+
EndProject
1923
Global
2024
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2125
Debug|Any CPU = Debug|Any CPU
@@ -38,11 +42,18 @@ Global
3842
{79B190E8-EDB0-4C03-8FD8-EB48E4807CFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
3943
{79B190E8-EDB0-4C03-8FD8-EB48E4807CFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
4044
{79B190E8-EDB0-4C03-8FD8-EB48E4807CFB}.Release|Any CPU.Build.0 = Release|Any CPU
45+
{AAC31ECB-05F9-444A-9B86-42ECD50AA468}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46+
{AAC31ECB-05F9-444A-9B86-42ECD50AA468}.Debug|Any CPU.Build.0 = Debug|Any CPU
47+
{AAC31ECB-05F9-444A-9B86-42ECD50AA468}.Release|Any CPU.ActiveCfg = Release|Any CPU
48+
{AAC31ECB-05F9-444A-9B86-42ECD50AA468}.Release|Any CPU.Build.0 = Release|Any CPU
4149
EndGlobalSection
4250
GlobalSection(SolutionProperties) = preSolution
4351
HideSolutionNode = FALSE
4452
EndGlobalSection
4553
GlobalSection(ExtensibilityGlobals) = postSolution
4654
SolutionGuid = {9AE22713-F94E-45CA-81F4-0806CA195B69}
4755
EndGlobalSection
56+
GlobalSection(NestedProjects) = preSolution
57+
{AAC31ECB-05F9-444A-9B86-42ECD50AA468} = {DE8F8E75-A119-4CF3-AFDD-4132B55DAE76}
58+
EndGlobalSection
4859
EndGlobal

src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,10 @@ internal static class Constants
7979
/// extension for discriminator value support
8080
/// </summary>
8181
public static string xMsDiscriminatorValue = "x-ms-discriminator-value";
82+
83+
/// <summary>
84+
/// Name used for the OpenAPI referenced schema for OData Count operations responses.
85+
/// </summary>
86+
public static string DollarCountSchemaName = "ODataCountResponse";
8287
}
8388
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ namespace Microsoft.OpenApi.OData.Edm
1212
/// </summary>
1313
public class ODataDollarCountSegment : ODataSegment
1414
{
15+
/// <summary>
16+
/// Get the static instance of $count segment.
17+
/// </summary>
18+
internal static ODataDollarCountSegment Instance = new();
19+
1520
/// <inheritdoc />
1621
public override ODataSegmentKind Kind => ODataSegmentKind.DollarCount;
1722

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ public string GetPathItemName(OpenApiConvertSettings settings)
146146

147147
// From Open API spec, parameter name is case sensitive, so don't use the IgnoreCase HashSet.
148148
// HashSet<string> parameters = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
149-
HashSet<string> parameters = new HashSet<string>();
150-
StringBuilder sb = new StringBuilder();
149+
HashSet<string> parameters = new();
150+
StringBuilder sb = new();
151151

152152
if (!string.IsNullOrWhiteSpace(settings.PathPrefix))
153153
{

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

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public virtual IEnumerable<ODataPath> GetPaths(IEdmModel model, OpenApiConvertSe
5757
{
5858
if (CanFilter(entitySet))
5959
{
60-
RetrieveNavigationSourcePaths(entitySet);
60+
RetrieveNavigationSourcePaths(entitySet, settings);
6161
}
6262
}
6363

@@ -66,7 +66,7 @@ public virtual IEnumerable<ODataPath> GetPaths(IEdmModel model, OpenApiConvertSe
6666
{
6767
if (CanFilter(singleton))
6868
{
69-
RetrieveNavigationSourcePaths(singleton);
69+
RetrieveNavigationSourcePaths(singleton, settings);
7070
}
7171
}
7272

@@ -102,7 +102,7 @@ protected virtual void Initialize(IEdmModel model)
102102

103103
private IEnumerable<ODataPath> MergePaths()
104104
{
105-
List<ODataPath> allODataPaths = new List<ODataPath>();
105+
List<ODataPath> allODataPaths = new();
106106
foreach (var item in _allNavigationSourcePaths.Values)
107107
{
108108
allODataPaths.AddRange(item);
@@ -127,6 +127,7 @@ private void AppendPath(ODataPath path)
127127
ODataPathKind kind = path.Kind;
128128
switch(kind)
129129
{
130+
case ODataPathKind.DollarCount:
130131
case ODataPathKind.Entity:
131132
case ODataPathKind.EntitySet:
132133
case ODataPathKind.Singleton:
@@ -143,8 +144,7 @@ private void AppendPath(ODataPath path)
143144

144145
case ODataPathKind.NavigationProperty:
145146
case ODataPathKind.Ref:
146-
ODataNavigationPropertySegment navigationPropertySegment = path.Last(p => p is ODataNavigationPropertySegment)
147-
as ODataNavigationPropertySegment;
147+
ODataNavigationPropertySegment navigationPropertySegment = path.OfType<ODataNavigationPropertySegment>().Last();
148148

149149
if (!_allNavigationPropertyPaths.TryGetValue(navigationPropertySegment.EntityType, out IList<ODataPath> npList))
150150
{
@@ -169,20 +169,26 @@ private void AppendPath(ODataPath path)
169169
/// Retrieve the paths for <see cref="IEdmNavigationSource"/>.
170170
/// </summary>
171171
/// <param name="navigationSource">The navigation source.</param>
172-
private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource)
172+
/// <param name="convertSettings">The settings for the current conversion.</param>
173+
private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource, OpenApiConvertSettings convertSettings)
173174
{
174175
Debug.Assert(navigationSource != null);
175176

176177
// navigation source itself
177-
ODataPath path = new ODataPath(new ODataNavigationSourceSegment(navigationSource));
178+
ODataPath path = new(new ODataNavigationSourceSegment(navigationSource));
178179
AppendPath(path.Clone());
179180

180181
IEdmEntitySet entitySet = navigationSource as IEdmEntitySet;
181182
IEdmEntityType entityType = navigationSource.EntityType();
183+
CountRestrictionsType count = null;
182184

183-
// for entity set, create a path with key
185+
// for entity set, create a path with key and a $count path
184186
if (entitySet != null)
185187
{
188+
count = _model.GetRecord<CountRestrictionsType>(entitySet, CapabilitiesConstants.CountRestrictions);
189+
if(count?.Countable ?? true)
190+
CreateCountPath(path, convertSettings);
191+
186192
path.Push(new ODataKeySegment(entityType));
187193
AppendPath(path.Clone());
188194
}
@@ -195,7 +201,7 @@ private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource
195201
{
196202
if (CanFilter(np))
197203
{
198-
RetrieveNavigationPropertyPaths(np, path);
204+
RetrieveNavigationPropertyPaths(np, count, path, convertSettings);
199205
}
200206
}
201207

@@ -249,8 +255,10 @@ private void RetrieveMediaEntityStreamPaths(IEdmEntityType entityType, ODataPath
249255
/// Retrieve the path for <see cref="IEdmNavigationProperty"/>.
250256
/// </summary>
251257
/// <param name="navigationProperty">The navigation property.</param>
258+
/// <param name="count">The count restrictions.</param>
252259
/// <param name="currentPath">The current OData path.</param>
253-
private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationProperty, ODataPath currentPath)
260+
/// <param name="convertSettings">The settings for the current conversion.</param>
261+
private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationProperty, CountRestrictionsType count, ODataPath currentPath, OpenApiConvertSettings convertSettings)
254262
{
255263
Debug.Assert(navigationProperty != null);
256264
Debug.Assert(currentPath != null);
@@ -275,6 +283,15 @@ private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationPr
275283
if (restriction == null || restriction.IndexableByKey == true)
276284
{
277285
IEdmEntityType navEntityType = navigationProperty.ToEntityType();
286+
var targetsMany = navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many;
287+
var propertyPath = navigationProperty.GetPartnerPath()?.Path;
288+
289+
if (targetsMany && (string.IsNullOrEmpty(propertyPath) ||
290+
(count?.IsNonCountableNavigationProperty(propertyPath) ?? true)))
291+
{
292+
// ~/entityset/{key}/collection-valued-Nav/$count
293+
CreateCountPath(currentPath, convertSettings);
294+
}
278295

279296
if (!navigationProperty.ContainsTarget)
280297
{
@@ -283,7 +300,7 @@ private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationPr
283300
// Collection-valued: ~/entityset/{key}/collection-valued-Nav/$ref?$id ={navKey}
284301
CreateRefPath(currentPath);
285302

286-
if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many)
303+
if (targetsMany)
287304
{
288305
// Collection-valued: DELETE ~/entityset/{key}/collection-valued-Nav/{key}/$ref
289306
currentPath.Push(new ODataKeySegment(navEntityType));
@@ -296,7 +313,7 @@ private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationPr
296313
else
297314
{
298315
// append a navigation property key.
299-
if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many)
316+
if (targetsMany)
300317
{
301318
currentPath.Push(new ODataKeySegment(navEntityType));
302319
AppendPath(currentPath.Clone());
@@ -312,13 +329,13 @@ private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationPr
312329
{
313330
if (CanFilter(subNavProperty))
314331
{
315-
RetrieveNavigationPropertyPaths(subNavProperty, currentPath);
332+
RetrieveNavigationPropertyPaths(subNavProperty, count, currentPath, convertSettings);
316333
}
317334
}
318335
}
319336
}
320337

321-
if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many)
338+
if (targetsMany)
322339
{
323340
currentPath.Pop();
324341
}
@@ -361,6 +378,21 @@ private void CreateRefPath(ODataPath currentPath)
361378
AppendPath(newPath);
362379
}
363380

381+
/// <summary>
382+
/// Create $count paths.
383+
/// </summary>
384+
/// <param name="currentPath">The current OData path.</param>
385+
/// <param name="convertSettings">The settings for the current conversion.</param>
386+
private void CreateCountPath(ODataPath currentPath, OpenApiConvertSettings convertSettings)
387+
{
388+
if(currentPath == null) throw new ArgumentNullException(nameof(currentPath));
389+
if(convertSettings == null) throw new ArgumentNullException(nameof(convertSettings));
390+
if(!convertSettings.EnableDollarCountPath) return;
391+
var countPath = currentPath.Clone();
392+
countPath.Push(ODataDollarCountSegment.Instance);
393+
AppendPath(countPath);
394+
}
395+
364396
/// <summary>
365397
/// Retrieve all bounding <see cref="IEdmOperation"/>.
366398
/// </summary>
@@ -436,7 +468,11 @@ private void RetrieveBoundOperationPaths(OpenApiConvertSettings convertSettings)
436468
}
437469
}
438470
}
439-
471+
private static readonly HashSet<ODataPathKind> _oDataPathKindsToSkipForOperations = new HashSet<ODataPathKind>() {
472+
ODataPathKind.EntitySet,
473+
ODataPathKind.MediaEntity,
474+
ODataPathKind.DollarCount
475+
};
440476
private bool AppendBoundOperationOnNavigationSourcePath(IEdmOperation edmOperation, bool isCollection, IEdmEntityType bindingEntityType)
441477
{
442478
bool found = false;
@@ -448,8 +484,7 @@ private bool AppendBoundOperationOnNavigationSourcePath(IEdmOperation edmOperati
448484
foreach (var subPath in value)
449485
{
450486
if ((isCollection && subPath.Kind == ODataPathKind.EntitySet) ||
451-
(!isCollection && subPath.Kind != ODataPathKind.EntitySet &&
452-
subPath.Kind != ODataPathKind.MediaEntity))
487+
(!isCollection && !_oDataPathKindsToSkipForOperations.Contains(subPath.Kind)))
453488
{
454489
ODataPath newPath = subPath.Clone();
455490
newPath.Push(new ODataOperationSegment(edmOperation, isEscapedFunction));
@@ -461,21 +496,18 @@ private bool AppendBoundOperationOnNavigationSourcePath(IEdmOperation edmOperati
461496

462497
return found;
463498
}
464-
499+
private static readonly HashSet<ODataPathKind> _pathKindToSkipForNavigationProperties = new () {
500+
ODataPathKind.Ref,
501+
};
465502
private bool AppendBoundOperationOnNavigationPropertyPath(IEdmOperation edmOperation, bool isCollection, IEdmEntityType bindingEntityType)
466503
{
467504
bool found = false;
468505
bool isEscapedFunction = _model.IsUrlEscapeFunction(edmOperation);
469506

470507
if (_allNavigationPropertyPaths.TryGetValue(bindingEntityType, out IList<ODataPath> value))
471508
{
472-
foreach (var path in value)
509+
foreach (var path in value.Where(x => !_pathKindToSkipForNavigationProperties.Contains(x.Kind)))
473510
{
474-
if (path.Kind == ODataPathKind.Ref)
475-
{
476-
continue;
477-
}
478-
479511
ODataNavigationPropertySegment npSegment = path.Segments.Last(s => s is ODataNavigationPropertySegment) as ODataNavigationPropertySegment;
480512

481513
if (!npSegment.NavigationProperty.ContainsTarget)
@@ -596,15 +628,9 @@ private bool AppendBoundOperationOnDerivedNavigationPropertyPath(
596628
{
597629
if (_allNavigationPropertyPaths.TryGetValue(baseType, out IList<ODataPath> paths))
598630
{
599-
foreach (var path in paths)
631+
foreach (var path in paths.Where(x => !_pathKindToSkipForNavigationProperties.Contains(x.Kind)))
600632
{
601-
if (path.Kind == ODataPathKind.Ref)
602-
{
603-
continue;
604-
}
605-
606-
var npSegment = path.Segments.Last(s => s is ODataNavigationPropertySegment)
607-
as ODataNavigationPropertySegment;
633+
var npSegment = path.Segments.OfType<ODataNavigationPropertySegment>().LastOrDefault();
608634
if (npSegment == null)
609635
{
610636
continue;

0 commit comments

Comments
 (0)