Skip to content
Merged
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
5 changes: 5 additions & 0 deletions src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,10 @@ internal static class Constants
/// entity name
/// </summary>
public static string EntityName = "entity";

/// <summary>
/// count segment identifier
/// </summary>
public const string CountSegmentIdentifier = "count";
}
}
58 changes: 58 additions & 0 deletions src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public class ODataPathProvider : IODataPathProvider

private IEdmModel _model;

private readonly IDictionary<IEdmEntityType, IList<ODataPath>> _dollarCountPaths =
new Dictionary<IEdmEntityType, IList<ODataPath>>();

/// <summary>
/// Can filter the <see cref="IEdmElement"/> or not.
/// </summary>
Expand Down Expand Up @@ -99,6 +102,7 @@ protected virtual void Initialize(IEdmModel model)
_allNavigationSourcePaths.Clear();
_allNavigationPropertyPaths.Clear();
_allOperationPaths.Clear();
_dollarCountPaths.Clear();
}

private IEnumerable<ODataPath> MergePaths()
Expand Down Expand Up @@ -142,6 +146,25 @@ private void AppendPath(ODataPath path)
nsList = new List<ODataPath>();
_allNavigationSourcePaths[navigationSourceSegment.EntityType] = nsList;
}

if (kind == ODataPathKind.DollarCount)
{
if (_allOperationPaths.FirstOrDefault(p => DollarCountAndOperationPathsSimilar(p, path)) is not null)
{
// Don't add a path for $count if a similar count() function path already exists.
return;
}
else
{
if (!_dollarCountPaths.TryGetValue(navigationSourceSegment.EntityType, out IList<ODataPath> dollarPathList))
{
dollarPathList = new List<ODataPath>();
_dollarCountPaths[navigationSourceSegment.EntityType] = dollarPathList;
}
dollarPathList.Add(path);
}
}

nsList.Add(path);
}
break;
Expand All @@ -161,12 +184,47 @@ private void AppendPath(ODataPath path)

case ODataPathKind.Operation:
case ODataPathKind.OperationImport:
if (kind == ODataPathKind.Operation)
{
foreach (var kvp in _dollarCountPaths)
{
if (kvp.Value.FirstOrDefault(p => DollarCountAndOperationPathsSimilar(p, path)) is ODataPath dollarCountPath &&
_allNavigationSourcePaths.TryGetValue(kvp.Key, out IList<ODataPath> dollarPathList))
{
dollarPathList.Remove(dollarCountPath);
break;
}
}
}

_allOperationPaths.Add(path);
break;

default:
return;
}

bool DollarCountAndOperationPathsSimilar(ODataPath path1, ODataPath path2)
{
if ((path1.Kind == ODataPathKind.DollarCount &&
path2.Kind == ODataPathKind.Operation && path2.LastSegment.Identifier.Equals(Constants.CountSegmentIdentifier, StringComparison.OrdinalIgnoreCase)) ||
(path2.Kind == ODataPathKind.DollarCount &&
path1.Kind == ODataPathKind.Operation && path1.LastSegment.Identifier.Equals(Constants.CountSegmentIdentifier, StringComparison.OrdinalIgnoreCase)))
{
return GetModifiedPathItemName(path1)?.Equals(GetModifiedPathItemName(path2), StringComparison.OrdinalIgnoreCase) ?? false;
}

return false;
}

string GetModifiedPathItemName(ODataPath path)
{
if (!path.Any()) return null;

IEnumerable<ODataSegment> modifiedSegments = path.Take(path.Count - 1);
ODataPath modifiedPath = new(modifiedSegments);
return modifiedPath.GetPathItemName();
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<PackageId>Microsoft.OpenApi.OData</PackageId>
<SignAssembly>true</SignAssembly>
<Version>1.3.0-preview1</Version>
<Version>1.3.0-preview2</Version>
<Description>This package contains the codes you need to convert OData CSDL to Open API Document of Model.</Description>
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<PackageTags>Microsoft OpenApi OData EDM</PackageTags>
<RepositoryUrl>https://github.com/Microsoft/OpenAPI.NET.OData</RepositoryUrl>
<PackageReleaseNotes>
- Update key path parameter descriptions #309
- Skips adding a $count path if a similar count() function path exists #347
</PackageReleaseNotes>
<AssemblyName>Microsoft.OpenApi.OData.Reader</AssemblyName>
<AssemblyOriginatorKeyFile>..\..\tool\Microsoft.OpenApi.OData.snk</AssemblyOriginatorKeyFile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ public void FindOperationsReturnsCorrectCollectionOrOperations()
var operations = provider.FindOperations(entitySet.EntityType(), false);

// Assert
Assert.Equal(29, operations.Count());
Assert.Equal(30, operations.Count());

// Act
entitySet = model.EntityContainer.FindEntitySet("directoryObjects");

operations = provider.FindOperations(entitySet.EntityType(), false);

// Assert
Assert.Equal(57, operations.Count());
Assert.Equal(58, operations.Count());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,18 @@ public void GetPathsForGraphBetaModelReturnsAllPaths()
ODataPathProvider provider = new();
OpenApiConvertSettings settings = new()
{
AddAlternateKeyPaths = true
AddAlternateKeyPaths = true,
PrefixEntityTypeNameBeforeKey = true
};

// Act
var paths = provider.GetPaths(model, settings);

// Assert
Assert.NotNull(paths);
Assert.Equal(18317, paths.Count());
Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/drives({id})/items({id1})/workbook/tables/$count")));
Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/drives({id})/items({id1})/workbook/tables/microsoft.graph.count()")));
Assert.Equal(18024, paths.Count());
}

[Fact]
Expand All @@ -72,7 +75,7 @@ public void GetPathsForGraphBetaModelWithDerivedTypesConstraintReturnsAllPaths()

// Assert
Assert.NotNull(paths);
Assert.Equal(19773, paths.Count());
Assert.Equal(18675, paths.Count());
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,29 @@ public void CreateLinksForSingletons()

// Assert
Assert.NotNull(links);
Assert.Equal(2, links.Count);
Assert.Equal(5, links.Count);
Assert.Collection(links,
item =>
{
Assert.Equal("edge", item.Key);
Assert.Equal("admin.GetEdge", item.Value.OperationId);
},
item =>
{
Assert.Equal("sharepoint", item.Key);
Assert.Equal("admin.GetSharepoint", item.Value.OperationId);
},
item =>
{
Assert.Equal("serviceAnnouncement", item.Key);
Assert.Equal("admin.GetServiceAnnouncement", item.Value.OperationId);
},
item =>
{
Assert.Equal("reportSettings", item.Key);
Assert.Equal("admin.GetReportSettings", item.Value.OperationId);
},
item =>
{
Assert.Equal("windows", item.Key);
Assert.Equal("admin.GetWindows", item.Value.OperationId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ public void CreateStructuredTypeSchemaForEntityTypeWithDiscriminatorValueEnabled
""deletedDateTime"": {
""pattern"": ""^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$"",
""type"": ""string"",
""description"": ""Date and time when this object was deleted. Always null when the object hasn't been deleted."",
""format"": ""date-time"",
""nullable"": true
},
Expand All @@ -218,10 +219,11 @@ public void CreateStructuredTypeSchemaForEntityTypeWithDiscriminatorValueEnabled
""propertyName"": ""@odata.type"",
""mapping"": {
""#microsoft.graph.user"": ""#/components/schemas/microsoft.graph.user"",
""#microsoft.graph.servicePrincipal"": ""#/components/schemas/microsoft.graph.servicePrincipal"",
""#microsoft.graph.group"": ""#/components/schemas/microsoft.graph.group"",
""#microsoft.graph.device"": ""#/components/schemas/microsoft.graph.device"",
""#microsoft.graph.administrativeUnit"": ""#/components/schemas/microsoft.graph.administrativeUnit"",
""#microsoft.graph.application"": ""#/components/schemas/microsoft.graph.application"",
""#microsoft.graph.servicePrincipal"": ""#/components/schemas/microsoft.graph.servicePrincipal"",
""#microsoft.graph.policyBase"": ""#/components/schemas/microsoft.graph.policyBase"",
""#microsoft.graph.appManagementPolicy"": ""#/components/schemas/microsoft.graph.appManagementPolicy"",
""#microsoft.graph.stsPolicy"": ""#/components/schemas/microsoft.graph.stsPolicy"",
Expand All @@ -231,14 +233,16 @@ public void CreateStructuredTypeSchemaForEntityTypeWithDiscriminatorValueEnabled
""#microsoft.graph.claimsMappingPolicy"": ""#/components/schemas/microsoft.graph.claimsMappingPolicy"",
""#microsoft.graph.activityBasedTimeoutPolicy"": ""#/components/schemas/microsoft.graph.activityBasedTimeoutPolicy"",
""#microsoft.graph.authorizationPolicy"": ""#/components/schemas/microsoft.graph.authorizationPolicy"",
""#microsoft.graph.tenantRelationshipAccessPolicyBase"": ""#/components/schemas/microsoft.graph.tenantRelationshipAccessPolicyBase"",
""#microsoft.graph.crossTenantAccessPolicy"": ""#/components/schemas/microsoft.graph.crossTenantAccessPolicy"",
""#microsoft.graph.tenantAppManagementPolicy"": ""#/components/schemas/microsoft.graph.tenantAppManagementPolicy"",
""#microsoft.graph.externalIdentitiesPolicy"": ""#/components/schemas/microsoft.graph.externalIdentitiesPolicy"",
""#microsoft.graph.permissionGrantPolicy"": ""#/components/schemas/microsoft.graph.permissionGrantPolicy"",
""#microsoft.graph.servicePrincipalCreationPolicy"": ""#/components/schemas/microsoft.graph.servicePrincipalCreationPolicy"",
""#microsoft.graph.identitySecurityDefaultsEnforcementPolicy"": ""#/components/schemas/microsoft.graph.identitySecurityDefaultsEnforcementPolicy"",
""#microsoft.graph.extensionProperty"": ""#/components/schemas/microsoft.graph.extensionProperty"",
""#microsoft.graph.endpoint"": ""#/components/schemas/microsoft.graph.endpoint"",
""#microsoft.graph.resourceSpecificPermissionGrant"": ""#/components/schemas/microsoft.graph.resourceSpecificPermissionGrant"",
""#microsoft.graph.administrativeUnit"": ""#/components/schemas/microsoft.graph.administrativeUnit"",
""#microsoft.graph.contract"": ""#/components/schemas/microsoft.graph.contract"",
""#microsoft.graph.directoryObjectPartnerReference"": ""#/components/schemas/microsoft.graph.directoryObjectPartnerReference"",
""#microsoft.graph.directoryRole"": ""#/components/schemas/microsoft.graph.directoryRole"",
Expand Down Expand Up @@ -285,6 +289,7 @@ public void CreateStructuredTypeSchemaForComplexTypeWithDiscriminatorValueEnable
""properties"": {
""isBackup"": {
""type"": ""boolean"",
""description"": ""For a user in an approval stage, this property indicates whether the user is a backup fallback approver."",
""nullable"": true
},
""@odata.type"": {
Expand Down Expand Up @@ -313,6 +318,7 @@ public void CreateStructuredTypeSchemaForComplexTypeWithDiscriminatorValueEnable
""properties"": {
""isBackup"": {
""type"": ""boolean"",
""description"": ""For a user in an approval stage, this property indicates whether the user is a backup fallback approver."",
""nullable"": true
},
""@odata.type"": {
Expand Down Expand Up @@ -363,11 +369,11 @@ public void CreateStructuredTypePropertiesSchemaWithCustomAttributeReturnsCorrec
""properties"": {
""contributionToContentDiscoveryAsOrganizationDisabled"": {
""type"": ""boolean"",
""x-ms-isHidden"": ""true""
""description"": ""Reflects the Office Delve organization level setting. When set to true, the organization doesn't have access to Office Delve. This setting is read-only and can only be changed by administrators in the SharePoint admin center.""
},
""contributionToContentDiscoveryDisabled"": {
""type"": ""boolean"",
""x-ms-isHidden"": ""true""
""description"": ""When set to true, documents in the user's Office Delve are disabled. Users can control this setting in Office Delve.""
},
""itemInsights"": {
""anyOf"": [
Expand All @@ -379,7 +385,20 @@ public void CreateStructuredTypePropertiesSchemaWithCustomAttributeReturnsCorrec
""nullable"": true
}
],
""x-ms-isHidden"": ""true"",
""description"": ""The user's settings for the visibility of meeting hour insights, and insights derived between a user and other items in Microsoft 365, such as documents or sites. Get userInsightsSettings through this navigation property."",
""x-ms-navigationProperty"": true
},
""contactMergeSuggestions"": {
""anyOf"": [
{
""$ref"": ""#/components/schemas/microsoft.graph.contactMergeSuggestions""
},
{
""type"": ""object"",
""nullable"": true
}
],
""description"": ""The user's settings for the visibility of merge suggestion for the duplicate contacts in the user's contact list."",
""x-ms-navigationProperty"": true
},
""regionalAndLanguageSettings"": {
Expand All @@ -392,6 +411,7 @@ public void CreateStructuredTypePropertiesSchemaWithCustomAttributeReturnsCorrec
""nullable"": true
}
],
""description"": ""The user's preferences for languages, regional locale and date/time formatting."",
""x-ms-navigationProperty"": true
},
""shiftPreferences"": {
Expand All @@ -404,6 +424,7 @@ public void CreateStructuredTypePropertiesSchemaWithCustomAttributeReturnsCorrec
""nullable"": true
}
],
""description"": ""The shift preferences for the user."",
""x-ms-navigationProperty"": true
}
}
Expand Down Expand Up @@ -931,6 +952,7 @@ public void CreatePropertySchemaWithComputedAnnotationReturnsCorrectSchema(OpenA
{
Assert.Equal(@"{
""format"": ""duration"",
""description"": ""The length of the appointment, denoted in ISO8601 format."",
""pattern"": ""^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$"",
""type"": ""string"",
""readOnly"": true
Expand All @@ -941,6 +963,7 @@ public void CreatePropertySchemaWithComputedAnnotationReturnsCorrectSchema(OpenA
Assert.Equal(@"{
""pattern"": ""^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$"",
""type"": ""string"",
""description"": ""The length of the appointment, denoted in ISO8601 format."",
""format"": ""duration"",
""readOnly"": true
}".ChangeLineBreaks(), json);
Expand Down
Loading