diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index 125618185..f4136466d 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -68,16 +68,6 @@ extends: Write-Host "##vso[task.setvariable variable=ODataVersion]$version" displayName: 'Validate OData component tag and project version' - - task: UseDotNet@2 - displayName: 'Use .NET 6' # needed for ESRP signing - inputs: - version: 6.x - - - task: UseDotNet@2 - displayName: 'Use .NET 8' - inputs: - version: 8.x - - task: UseDotNet@2 displayName: 'Use .NET 10' inputs: diff --git a/.azure-pipelines/hidi-release.yml b/.azure-pipelines/hidi-release.yml index 9b67b8cb0..1083db386 100644 --- a/.azure-pipelines/hidi-release.yml +++ b/.azure-pipelines/hidi-release.yml @@ -59,14 +59,6 @@ extends: } Write-Host "##vso[task.setvariable variable=HidiVersion]$version" displayName: Validate Hidi component tag and project version - - task: UseDotNet@2 - displayName: Use .NET 6 for ESRP - inputs: - version: 6.x - - task: UseDotNet@2 - displayName: Use .NET 8 - inputs: - version: 8.x - task: UseDotNet@2 displayName: Use .NET 10 inputs: @@ -142,7 +134,7 @@ extends: & .\artifacts\hidi\win-x64\Microsoft.OpenApi.Hidi.exe --help if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } New-Item -ItemType Directory -Force artifacts\hidi\signing | Out-Null - Copy-Item src\Microsoft.OpenApi.Hidi\bin\$(buildConfiguration)\net8.0\Microsoft.OpenApi.Hidi.dll artifacts\hidi\signing + Copy-Item src\Microsoft.OpenApi.Hidi\bin\$(buildConfiguration)\net10.0\Microsoft.OpenApi.Hidi.dll artifacts\hidi\signing Copy-Item artifacts\hidi\win-x64\Microsoft.OpenApi.Hidi.exe artifacts\hidi\signing displayName: Build, test and stage hidi binaries - task: EsrpCodeSigning@6 diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index f19603532..85e8c663c 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -19,9 +19,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@0f4a354dd5547a28c70b7853dbd95d5b67886e73 # v6.1.0 with: - dotnet-version: | - 8.0.x - 10.0.x + dotnet-version: 10.0.x - name: Data gatherer id: data_gatherer @@ -172,9 +170,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-dotnet@0f4a354dd5547a28c70b7853dbd95d5b67886e73 # v6.1.0 with: - dotnet-version: | - 8.0.x - 10.0.x + dotnet-version: 10.0.x - name: Publish and smoke-test Windows executable shell: pwsh run: | diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 2c6ba1e12..5361ec360 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -44,9 +44,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@0f4a354dd5547a28c70b7853dbd95d5b67886e73 # v6.1.0 with: - dotnet-version: | - 8.0.x - 10.0.x + dotnet-version: 10.0.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index baf883013..87d227912 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -43,9 +43,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@0f4a354dd5547a28c70b7853dbd95d5b67886e73 # v6.1.0 with: - dotnet-version: | - 8.0.x - 10.0.x + dotnet-version: 10.0.x - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis @@ -126,7 +124,7 @@ jobs: CoverletOutputFormat: 'opencover' # https://github.com/microsoft/vstest/issues/4014#issuecomment-1307913682 shell: pwsh run: | - dotnet tool run dotnet-sonarscanner begin /k:"microsoft_OpenAPI.NET.OData" /o:"microsoft" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="test/**/coverage.net8.0.opencover.xml,artifacts/hidi/coverage/*opencover*.xml" /d:sonar.coverageReportPaths="artifacts/hidi/powershell-coverage/sonar-coverage.xml" + dotnet tool run dotnet-sonarscanner begin /k:"microsoft_OpenAPI.NET.OData" /o:"microsoft" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="test/**/coverage.opencover.xml,artifacts/hidi/coverage/*opencover*.xml" /d:sonar.coverageReportPaths="artifacts/hidi/powershell-coverage/sonar-coverage.xml" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } dotnet workload restore if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/Dockerfile b/Dockerfile index 4a66f5985..29138b747 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ WORKDIR /app/hidi RUN --mount=type=secret,id=nuget_config,target=/app/hidi/NuGet.Config \ dotnet publish ./src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj -c Release -o /app/publish -p:GeneratePackageOnBuild=false -p:HidiPublicSignBuild=true -FROM mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled AS runtime +FROM mcr.microsoft.com/dotnet/runtime:10.0-noble-chiseled AS runtime WORKDIR /app COPY --from=build-env /app/publish ./ diff --git a/README.md b/README.md index 5169836ef..88f9a4c4d 100644 --- a/README.md +++ b/README.md @@ -86,13 +86,16 @@ The `GetEdmModel()` method can load a model in 3 ways: The OpenAPI.OData.reader nuget package is at: [https://www.nuget.org/packages/Microsoft.OpenApi.OData/](https://www.nuget.org/packages/Microsoft.OpenApi.OData) +The package targets .NET 10 and depends on Microsoft.OData.Edm 9.x. + ## Hidi command line tool [Hidi](src/Microsoft.OpenApi.Hidi/readme.md) validates, transforms and slices OpenAPI documents, converts OData CSDL, and generates API diagrams. The NuGet package remains `Microsoft.OpenApi.Hidi` and the command remains `hidi`. Hidi 3.x is maintained on `main`; Hidi 2.x is maintained on `support/v2`. -Both tracks use independent versions and require the .NET 8 runtime. +Both tracks use independent versions. Hidi 3.x requires the .NET 10 runtime; +Hidi 2.x requires the .NET 8 runtime. Building this repository, including the Hidi tests, requires the .NET 10 SDK. The main Hidi pipeline sets `hidiPublishingEnabled: 'true'` for stable `hidi-v3.*` tag releases; `hidiPreviewPublishingEnabled: 'false'` keeps main diff --git a/install-tool.ps1 b/install-tool.ps1 index 4d1a74c07..1f4c92ace 100644 --- a/install-tool.ps1 +++ b/install-tool.ps1 @@ -19,7 +19,7 @@ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } $installedDlls = @(Get-ChildItem $toolDirectory -Force -Recurse -Filter Microsoft.OpenApi.Hidi.dll) if ($installedDlls.Count -ne 1) { throw 'Expected exactly one installed hidi DLL.' } -$builtDll = Join-Path $PSScriptRoot 'src\Microsoft.OpenApi.Hidi\bin\Release\net8.0\Microsoft.OpenApi.Hidi.dll' +$builtDll = Join-Path $PSScriptRoot 'src\Microsoft.OpenApi.Hidi\bin\Release\net10.0\Microsoft.OpenApi.Hidi.dll' if ((Get-FileHash $installedDlls[0].FullName).Hash -ne (Get-FileHash $builtDll).Hash) { throw 'Installed hidi does not match the locally built package.' } \ No newline at end of file diff --git a/scripts/verify-hidi-package-assembly.ps1 b/scripts/verify-hidi-package-assembly.ps1 index 53365fe78..a562f8f5e 100644 --- a/scripts/verify-hidi-package-assembly.ps1 +++ b/scripts/verify-hidi-package-assembly.ps1 @@ -31,8 +31,8 @@ $archive = $null try { $archive = [IO.Compression.ZipFile]::OpenRead((Resolve-Path -LiteralPath $PackagePath).Path) $assemblies = @($archive.Entries | Where-Object { $_.Name -ieq 'Microsoft.OpenApi.Hidi.dll' }) - if ($assemblies.Count -ne 1 -or $assemblies[0].FullName -cne 'tools/net8.0/any/Microsoft.OpenApi.Hidi.dll') { - throw 'Expected exactly one Hidi DLL at tools/net8.0/any/Microsoft.OpenApi.Hidi.dll.' + if ($assemblies.Count -ne 1 -or $assemblies[0].FullName -cne 'tools/net10.0/any/Microsoft.OpenApi.Hidi.dll') { + throw 'Expected exactly one Hidi DLL at tools/net10.0/any/Microsoft.OpenApi.Hidi.dll.' } [IO.Compression.ZipFileExtensions]::ExtractToFile($assemblies[0], $packagedAssembly) diff --git a/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs b/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs index 6d2282b32..03c141a76 100644 --- a/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs +++ b/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs @@ -67,7 +67,7 @@ public override void Visit(OpenApiOperation operation) var operationId = operation.OperationId; var operationTypeExtension = operation.Extensions?.GetExtension("x-ms-docs-operation-type"); - if (operationTypeExtension.IsEquals("function") && operation.Parameters is { Count :> 0}) + if (operationTypeExtension.IsEquals("function") && operation.Parameters is { Count: > 0 }) ResolveFunctionParameters(operation.Parameters); // Order matters. Resolve operationId. @@ -106,7 +106,7 @@ private static string ResolveByRefOperationId(string operationId) // Update $ref path operationId name // Ref key word is enclosed between lower-cased and upper-cased letters // Ex.: applications_GetRefCreatedOnBehalfOf to applications_GetCreatedOnBehalfOfByRef - return s_oDataRefRegex.Match(operationId).Success ? $"{s_oDataRefRegex.Replace(operationId, string.Empty)}ByRef" : operationId; + return s_oDataRefRegex.IsMatch(operationId) ? $"{s_oDataRefRegex.Replace(operationId, string.Empty)}ByRef" : operationId; } private static string ResolveODataCastOperationId(string operationId) @@ -175,8 +175,8 @@ private static void ResolveFunctionParameters(IList parameter private void AddAdditionalPropertiesToSchema(IOpenApiSchema schema) { - if (schema is OpenApiSchema openApiSchema - && !_schemaLoop.Contains(schema) + if (schema is OpenApiSchema openApiSchema + && !_schemaLoop.Contains(schema) && schema.Type.Equals(JsonSchemaType.Object)) { openApiSchema.AdditionalProperties = new OpenApiSchema() { Type = JsonSchemaType.Object }; diff --git a/src/Microsoft.OpenApi.Hidi/Logger.cs b/src/Microsoft.OpenApi.Hidi/Logger.cs index 2dd1a4ee3..26b4e8e91 100644 --- a/src/Microsoft.OpenApi.Hidi/Logger.cs +++ b/src/Microsoft.OpenApi.Hidi/Logger.cs @@ -5,7 +5,7 @@ namespace Microsoft.OpenApi.Hidi { - public static class Logger + internal static class Logger { public static ILoggerFactory ConfigureLogger(LogLevel logLevel) { diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index ac6c43e66..741a6d99f 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 latest true true @@ -44,7 +44,6 @@ all - diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index a494b4f2e..f6edbc5fc 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -51,7 +51,7 @@ public static async Task TransformOpenApiDocumentAsync(HidiOptions options, ILog { #pragma warning disable CA1308 // Normalize strings to uppercase var extension = options.OpenApiFormat?.ToLowerInvariant(); - var inputExtension = !string.IsNullOrEmpty(extension) ? string.Concat(".", extension) + var inputExtension = !string.IsNullOrEmpty(extension) ? string.Concat(".", extension) : GetInputPathExtension(options.OpenApi, options.Csdl); #pragma warning restore CA1308 // Normalize strings to uppercase @@ -178,7 +178,10 @@ private static OpenApiDocument ApplyFilters(HidiOptions options, ILogger logger, stopwatch.Start(); document = OpenApiFilterService.CreateFilteredDocument(document, predicate); stopwatch.Stop(); - logger.LogTrace("{Timestamp}ms: Creating filtered OpenApi document with {Paths} paths.", stopwatch.ElapsedMilliseconds, document.Paths.Count); + if (logger.IsEnabled(LogLevel.Trace)) + { + logger.LogTrace("{Timestamp}ms: Creating filtered OpenApi document with {Paths} paths.", stopwatch.ElapsedMilliseconds, document.Paths.Count); + } } return document; @@ -215,7 +218,10 @@ private static async Task WriteOpenApiAsync(HidiOptions options, string openApiF await document.SerializeAsync(writer, openApiVersion, cancellationToken).ConfigureAwait(false); stopwatch.Stop(); - logger.LogTrace("Finished serializing in {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds); + if (logger.IsEnabled(LogLevel.Trace)) + { + logger.LogTrace("Finished serializing in {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds); + } await textWriter.FlushAsync(cancellationToken).ConfigureAwait(false); } } @@ -244,7 +250,10 @@ private static async Task WriteOpenApiAsync(HidiOptions options, string openApiF document = await ConvertCsdlToOpenApiAsync(filteredStream ?? stream, format, metadataVersion, options.SettingsConfig, cancellationToken).ConfigureAwait(false); stopwatch.Stop(); - logger.LogTrace("{Timestamp}ms: Generated OpenAPI with {Paths} paths.", stopwatch.ElapsedMilliseconds, document?.Paths.Count); + if (logger.IsEnabled(LogLevel.Trace)) + { + logger.LogTrace("{Timestamp}ms: Generated OpenAPI with {Paths} paths.", stopwatch.ElapsedMilliseconds, document?.Paths.Count); + } } } else if (!string.IsNullOrEmpty(options.OpenApi)) @@ -356,9 +365,12 @@ private static MemoryStream ApplyFilterToCsdl(Stream csdlStream, string entitySe walker.Walk(result.Document); logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report.."); - #pragma warning disable CA2254 - logger.LogInformation(statsVisitor.GetStatisticsReport()); - #pragma warning restore CA2254 + if (logger.IsEnabled(LogLevel.Information)) + { +#pragma warning disable CA2254 + logger.LogInformation(statsVisitor.GetStatisticsReport()); +#pragma warning restore CA2254 + } } } catch (TaskCanceledException) @@ -395,7 +407,10 @@ private static async Task ParseOpenApiAsync(string openApiFile, bool result = await OpenApiDocument.LoadAsync(stream, settings: settings, cancellationToken: cancellationToken).ConfigureAwait(false); - logger.LogTrace("{Timestamp}ms: Completed parsing.", stopwatch.ElapsedMilliseconds); + if (logger.IsEnabled(LogLevel.Trace)) + { + logger.LogTrace("{Timestamp}ms: Completed parsing.", stopwatch.ElapsedMilliseconds); + } LogErrors(logger, result); LogWarnings(logger, result); @@ -482,7 +497,7 @@ private static Dictionary> EnumerateJsonDocument(JsonElemen } else { - paths.Add(path, new() {method}); + paths.Add(path, new() { method }); } } else @@ -547,7 +562,10 @@ SecurityException or } } stopwatch.Stop(); - logger.LogTrace("{Timestamp}ms: Read file {Input}", stopwatch.ElapsedMilliseconds, input); + if (logger.IsEnabled(LogLevel.Trace)) + { + logger.LogTrace("{Timestamp}ms: Read file {Input}", stopwatch.ElapsedMilliseconds, input); + } } return stream; } @@ -637,7 +655,7 @@ private static string GetInputPathExtension(string? openapi = null, string? csdl return options.Output.FullName; } } - } + } } catch (TaskCanceledException) { @@ -766,7 +784,7 @@ internal static async Task PluginManifestAsync(HidiOptions options, ILogger logg options.OpenApi = apiDependency.ApiDescriptionUrl; } - var openApiFormat = options.OpenApiFormat ?? (!string.IsNullOrEmpty(options.OpenApi) + var openApiFormat = options.OpenApiFormat ?? (!string.IsNullOrEmpty(options.OpenApi) ? GetOpenApiFormat(options.OpenApi, logger) : OpenApiConstants.Yaml); // Load OpenAPI document diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs b/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs index b8bff3195..e6afc32d1 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs @@ -5,7 +5,7 @@ namespace Microsoft.OpenApi.Hidi { - public static class OpenApiSpecVersionHelper + internal static class OpenApiSpecVersionHelper { public static OpenApiSpecVersion TryParseOpenApiSpecVersion(string value) { diff --git a/src/Microsoft.OpenApi.Hidi/readme.md b/src/Microsoft.OpenApi.Hidi/readme.md index b42640a0d..34c074941 100644 --- a/src/Microsoft.OpenApi.Hidi/readme.md +++ b/src/Microsoft.OpenApi.Hidi/readme.md @@ -15,7 +15,7 @@ Hidi has these key capabilities that enable you to build different scenarios off Install [Microsoft.OpenApi.Hidi](https://www.nuget.org/packages/Microsoft.OpenApi.Hidi) from NuGet. Hidi 3.x is maintained on this repository's `main` branch and requires -.NET 8. Hidi 2.x is maintained separately on `support/v2`. +.NET 10. Hidi 2.x is maintained separately on `support/v2`. ### .NET CLI(Global) @@ -32,7 +32,7 @@ dotnet tool install --local Microsoft.OpenApi.Hidi --version 3.10.2 ### Build and install from this repository -Run these commands from the repository root with the .NET 10 SDK and .NET 8 runtime installed: +Run these commands from the repository root with the .NET 10 SDK installed: ```powershell dotnet pack src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj -c Release -o artifacts\hidi\nuget diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs index e2bb4d781..34390c31d 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs @@ -50,15 +50,15 @@ public class ODataPathProvider : IODataPathProvider /// The collection of built . public virtual IEnumerable GetPaths(IEdmModel model, OpenApiConvertSettings settings) { - if (model == null || model.EntityContainer == null) - { - return []; - } + if (model == null || model.EntityContainer == null) + { + return []; + } - Initialize(model); + Initialize(model); - if (_model is not null) - { + if (_model is not null) + { // entity set foreach (var entitySet in _model.EntityContainer.EntitySets()) { @@ -76,13 +76,13 @@ public virtual IEnumerable GetPaths(IEdmModel model, OpenApiConvertSe RetrieveNavigationSourcePaths(singleton, settings); } } - } + } - // bound operations - RetrieveBoundOperationPaths(settings); + // bound operations + RetrieveBoundOperationPaths(settings); - if (_model is not null) - { + if (_model is not null) + { // unbound operations foreach (IEdmOperationImport import in _model.EntityContainer.OperationImports()) { @@ -91,9 +91,9 @@ public virtual IEnumerable GetPaths(IEdmModel model, OpenApiConvertSe AppendPath(new ODataPath(new ODataOperationImportSegment(import))); } } - } + } - return MergePaths(); + return MergePaths(); } /// @@ -112,32 +112,32 @@ protected virtual void Initialize(IEdmModel model) _dollarCountPaths.Clear(); } - private IEnumerable MergePaths() - { - List allODataPaths = new(); - foreach (var item in _allNavigationSourcePaths.Values) - { - allODataPaths.AddRange(item); - } + private IEnumerable MergePaths() + { + List allODataPaths = new(); + foreach (var item in _allNavigationSourcePaths.Values) + { + allODataPaths.AddRange(item); + } - foreach (var item in _allNavigationPropertyPaths.Values) - { - allODataPaths.AddRange(item); - } + foreach (var item in _allNavigationPropertyPaths.Values) + { + allODataPaths.AddRange(item); + } - allODataPaths.AddRange(_allOperationPaths); + allODataPaths.AddRange(_allOperationPaths); - allODataPaths.Sort(); + allODataPaths.Sort(); - return allODataPaths; - } + return allODataPaths; + } private void AppendPath(ODataPath path) { Utils.CheckArgumentNull(path, nameof(path)); ODataPathKind kind = path.Kind; - switch(kind) + switch (kind) { case ODataPathKind.ComplexProperty: case ODataPathKind.TypeCast: @@ -148,14 +148,14 @@ private void AppendPath(ODataPath path) case ODataPathKind.MediaEntity: if (path.FirstSegment is ODataNavigationSourceSegment navigationSourceSegment) { - if(!_allNavigationSourcePaths.TryGetValue(navigationSourceSegment.EntityType, out var nsList)) + if (!_allNavigationSourcePaths.TryGetValue(navigationSourceSegment.EntityType, out var nsList)) { nsList = []; _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. @@ -171,7 +171,7 @@ private void AppendPath(ODataPath path) dollarPathList.Add(path); } } - + nsList.Add(path); } break; @@ -213,7 +213,7 @@ private void AppendPath(ODataPath path) bool DollarCountAndOperationPathsSimilar(ODataPath path1, ODataPath path2) { - if ((path1.Kind == ODataPathKind.DollarCount && + if ((path1.Kind == ODataPathKind.DollarCount && path2.Kind == ODataPathKind.Operation && Constants.CountSegmentIdentifier.Equals(path2.LastSegment?.Identifier, StringComparison.OrdinalIgnoreCase)) || (path2.Kind == ODataPathKind.DollarCount && path1.Kind == ODataPathKind.Operation && Constants.CountSegmentIdentifier.Equals(path1.LastSegment?.Identifier, StringComparison.OrdinalIgnoreCase))) @@ -221,7 +221,7 @@ bool DollarCountAndOperationPathsSimilar(ODataPath path1, ODataPath path2) return GetModifiedPathItemName(path1)?.Equals(GetModifiedPathItemName(path2), StringComparison.OrdinalIgnoreCase) ?? false; } - return false; + return false; } string? GetModifiedPathItemName(ODataPath path) @@ -260,7 +260,7 @@ private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource string targetPath = path.GetTargetPath(_model); count = _model.GetRecord(targetPath, CapabilitiesConstants.CountRestrictions) ?? _model.GetRecord(entitySet, CapabilitiesConstants.CountRestrictions); - if(count?.Countable ?? true) // ~/entitySet/$count + if (count?.Countable ?? true) // ~/entitySet/$count CreateCountPath(path, convertSettings); CreateTypeCastPaths(path, convertSettings, entityType, entitySet, true); // ~/entitySet/subType @@ -435,20 +435,20 @@ private void TraverseComplexProperty(IEdmStructuralProperty structuralProperty, /// true or false. private bool ShouldCreateComplexPropertyPaths(IEdmStructuralProperty complexProperty, string targetPath, OpenApiConvertSettings convertSettings) { - Utils.CheckArgumentNull(complexProperty, nameof(complexProperty)); + Utils.CheckArgumentNull(complexProperty, nameof(complexProperty)); Utils.CheckArgumentNull(convertSettings, nameof(convertSettings)); if (!convertSettings.RequireRestrictionAnnotationsToGenerateComplexPropertyPaths) return true; - bool isReadable = _model?.GetRecord(targetPath, CapabilitiesConstants.ReadRestrictions)?.Readable - ?? _model?.GetRecord(complexProperty, CapabilitiesConstants.ReadRestrictions)?.Readable + bool isReadable = _model?.GetRecord(targetPath, CapabilitiesConstants.ReadRestrictions)?.Readable + ?? _model?.GetRecord(complexProperty, CapabilitiesConstants.ReadRestrictions)?.Readable ?? false; - bool isUpdatable = _model?.GetRecord(targetPath, CapabilitiesConstants.UpdateRestrictions)?.Updatable - ??_model?.GetRecord(complexProperty, CapabilitiesConstants.UpdateRestrictions)?.Updatable + bool isUpdatable = _model?.GetRecord(targetPath, CapabilitiesConstants.UpdateRestrictions)?.Updatable + ?? _model?.GetRecord(complexProperty, CapabilitiesConstants.UpdateRestrictions)?.Updatable ?? false; bool isInsertable = _model?.GetRecord(targetPath, CapabilitiesConstants.InsertRestrictions)?.Insertable - ?? _model?.GetRecord(complexProperty, CapabilitiesConstants.InsertRestrictions)?.Insertable + ?? _model?.GetRecord(complexProperty, CapabilitiesConstants.InsertRestrictions)?.Insertable ?? false; return isReadable || isUpdatable || isInsertable; @@ -533,8 +533,8 @@ private void RetrieveNavigationPropertyPaths( { navSourceRestrictionType = _model.GetRecord(annotatableNavigationSource, CapabilitiesConstants.NavigationRestrictions); navPropRestrictionType = _model.GetRecord(navigationProperty, CapabilitiesConstants.NavigationRestrictions); - } - + } + NavigationPropertyRestriction? restriction = navSourceRestrictionType?.RestrictedProperties? .FirstOrDefault(r => r.NavigationProperty == currentPath.NavigationPropertyPath(navigationProperty.Name)) ?? navPropRestrictionType?.RestrictedProperties?.FirstOrDefault(); @@ -567,7 +567,7 @@ private void RetrieveNavigationPropertyPaths( { IEdmEntityType navEntityType = navigationProperty.ToEntityType(); var targetsMany = navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many; - + if (targetsMany) { bool? createCountPath = null; @@ -652,11 +652,11 @@ private void RetrieveNavigationPropertyPaths( if (targetsMany) { currentPath.Pop(); - } + } } currentPath.Pop(); visitedNavigationProperties.Pop(); - } + } /// /// Create $ref paths. @@ -681,7 +681,7 @@ private void CreateCountPath(ODataPath currentPath, OpenApiConvertSettings conve Utils.CheckArgumentNull(currentPath, nameof(currentPath)); Utils.CheckArgumentNull(convertSettings, nameof(convertSettings)); - if(!convertSettings.EnableDollarCountPath) + if (!convertSettings.EnableDollarCountPath) return; var countPath = currentPath.Clone(); @@ -729,12 +729,12 @@ private void CreateTypeCastPaths(ODataPath currentPath, OpenApiConvertSettings c Utils.CheckArgumentNull(structuredType, nameof(structuredType)); Utils.CheckArgumentNull(annotable, nameof(annotable)); - if(!convertSettings.EnableODataTypeCast) + if (!convertSettings.EnableODataTypeCast) return; var annotedTypeNames = GetDerivedTypeConstraintTypeNames(annotable); - - if(!annotedTypeNames.Any() && convertSettings.RequireDerivedTypesConstraintForODataTypeCastSegments) + + if (!annotedTypeNames.Any() && convertSettings.RequireDerivedTypesConstraintForODataTypeCastSegments) return; // we don't want to generate any downcast path item if there is no type cast annotation. bool filter(IEdmStructuredType x) => @@ -750,7 +750,7 @@ bool filter(IEdmStructuredType x) => .OfType() .ToArray(); - if (targetTypes is not { Length: > 0} || _model is null) return; + if (targetTypes is not { Length: > 0 } || _model is null) return; foreach (var targetType in targetTypes) { @@ -784,11 +784,11 @@ bool filter(IEdmStructuredType x) => { RetrieveNavigationPropertyPaths(declaredNavigationProperty, null, castPath, convertSettings); } - + if (targetType is IEdmEntityType entityType) { RetrieveComplexPropertyPaths(entityType, castPath, convertSettings); - } + } } } } @@ -872,15 +872,15 @@ private void RetrieveBoundOperationPaths(OpenApiConvertSettings convertSettings) var functionPaths = _allOperationPaths.Where(x => x.LastSegment is ODataOperationSegment operationSegment && operationSegment.Operation is IEdmFunction edmFunction && edmFunction.IsComposable - && edmFunction.GetReturn()?.Type is { } retType + && edmFunction.ReturnType is { } retType && retType.Definition is IEdmEntityType); - foreach( var functionPath in functionPaths) + foreach (var functionPath in functionPaths) { if (functionPath.LastSegment is not ODataOperationSegment operationSegment || operationSegment.Operation is not IEdmFunction edmFunction || !edmFunction.IsComposable - || edmFunction.GetReturn()?.Type is not { } retType + || edmFunction.ReturnType is not { } retType || retType.Definition is not IEdmEntityType returnBindingEntityType) { continue; @@ -902,7 +902,7 @@ functionPath.LastSegment is ODataOperationSegment && int composableFuncSegIndex = functionPath .Segments .OfType() - .FirstOrDefault(x => x.Operation is IEdmFunction {IsComposable: true}) is {} firstOperationSegment ? + .FirstOrDefault(x => x.Operation is IEdmFunction { IsComposable: true }) is { } firstOperationSegment ? functionPath.Segments.IndexOf(firstOperationSegment) : -1; int currentDepth = functionPath.Count - composableFuncSegIndex - 1; @@ -911,7 +911,7 @@ functionPath.LastSegment is ODataOperationSegment && ODataPath newNavigationPath = functionPath.Clone(); newNavigationPath.Push(new ODataNavigationPropertySegment(navProperty)); AppendPath(newNavigationPath); - } + } } } } @@ -969,7 +969,7 @@ secondLastPathSegment is not ODataKeySegment && var annotatable = (lastPathSegment as ODataNavigationSourceSegment)?.NavigationSource as IEdmVocabularyAnnotatable; annotatable ??= (lastPathSegment as ODataKeySegment)?.EntityType; - + if (annotatable != null && _model is not null && !EdmModelHelper.IsOperationAllowed(_model, edmOperation, annotatable)) { // Check whether the navigation source is allowed to have an operation on the entity type @@ -977,7 +977,7 @@ secondLastPathSegment is not ODataKeySegment && if (annotatable != null && !EdmModelHelper.IsOperationAllowed(_model, edmOperation, annotatable)) { continue; - } + } } if (_model is not null) @@ -990,7 +990,7 @@ secondLastPathSegment is not ODataKeySegment && } } } - private static readonly HashSet _pathKindToSkipForNavigationProperties = new () { + private static readonly HashSet _pathKindToSkipForNavigationProperties = new() { ODataPathKind.Ref, }; private void AppendBoundOperationOnNavigationPropertyPath(IEdmOperation edmOperation, bool isCollection, IEdmEntityType bindingEntityType) @@ -1002,7 +1002,7 @@ private void AppendBoundOperationOnNavigationPropertyPath(IEdmOperation edmOpera foreach (var path in value.Where(x => !_pathKindToSkipForNavigationProperties.Contains(x.Kind))) { ODataNavigationPropertySegment npSegment = path.Segments.OfType().Last(); - + if (_model is not null && !EdmModelHelper.IsOperationAllowed(_model, edmOperation, npSegment.NavigationProperty, npSegment.NavigationProperty.ContainsTarget)) { continue; @@ -1089,7 +1089,7 @@ private void AppendBoundOperationOnDerived( else { ODataPath newPath = new ODataPath(new ODataNavigationSourceSegment(ns), new ODataKeySegment(ns.EntityType), - new ODataTypeCastSegment(bindingEntityType , _model), + new ODataTypeCastSegment(bindingEntityType, _model), new ODataOperationSegment(edmOperation, isEscapedFunction, _model)); AppendPath(newPath); } @@ -1191,10 +1191,10 @@ private void AppendBoundOperationOnOperationPath(IEdmOperation edmOperation, boo foreach (var path in paths) { - if (path.LastSegment is not ODataOperationSegment operationSegment + if (path.LastSegment is not ODataOperationSegment operationSegment || (path.Segments.Count > 1 && path.Segments[path.Segments.Count - 2] is ODataOperationSegment) || operationSegment.Operation is not IEdmFunction edmFunction || !edmFunction.IsComposable - || edmFunction.GetReturn()?.Type is not { } retType || !retType.Definition.Equals(bindingEntityType) + || edmFunction.ReturnType is not { } retType || !retType.Definition.Equals(bindingEntityType) || isCollection || _model is not null && !EdmModelHelper.IsOperationAllowed(_model, edmOperation, operationSegment.Operation, true)) { diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/RecordExpressionExtensions.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/RecordExpressionExtensions.cs index 3010a06c9..862acf890 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Edm/RecordExpressionExtensions.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/RecordExpressionExtensions.cs @@ -82,7 +82,7 @@ internal static class RecordExpressionExtensions return (record.Properties?.FirstOrDefault(e => propertyName.Equals(e.Name, StringComparison.Ordinal)) is IEdmPropertyConstructor property && property.Value is IEdmDateConstantExpression value) ? - value.Value : + value.Value.ToDateTime(TimeOnly.MinValue) : null; } @@ -175,7 +175,7 @@ property.Value is IEdmEnumMemberExpression value && if (record.Properties?.FirstOrDefault(e => propertyName.Equals(e.Name, StringComparison.Ordinal)) is IEdmPropertyConstructor property && property.Value is IEdmCollectionExpression value && value.Elements != null) { - IList properties = + IList properties = value.Elements .OfType() .Select(x => x.Path) diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/EdmSpatialTypeVisitor.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/EdmSpatialTypeVisitor.cs index ce2065eae..545e65e48 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/EdmSpatialTypeVisitor.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/EdmSpatialTypeVisitor.cs @@ -49,9 +49,9 @@ public void Visit(IEdmModel model) private void VisitOperation(IEdmOperation operation) { - if (operation.GetReturn() is {} operationReturn) + if (operation.ReturnType is { } returnType) { - this.VisitTypeReference(operationReturn.Type); + this.VisitTypeReference(returnType); } foreach (var parameter in operation.Parameters) diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs index 879f4727a..4cb73c692 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs @@ -73,7 +73,7 @@ public static IList CreateParameters(this ODataContext contex Utils.CheckArgumentNull(function, nameof(function)); Utils.CheckArgumentNull(document, nameof(document)); - var parameters = new List(); + var parameters = new List(); int skip = function.IsBound ? 1 : 0; foreach (IEdmOperationParameter edmParameter in function.Parameters.Skip(skip)) { @@ -161,7 +161,7 @@ public static IList CreateKeyParameters(this ODataContext cont Utils.CheckArgumentNull(context, nameof(context)); Utils.CheckArgumentNull(keySegment, nameof(keySegment)); Utils.CheckArgumentNull(document, nameof(document)); - + if (keySegment.IsAlternateKey) return CreateAlternateKeyParameters(context, keySegment, document); @@ -234,11 +234,11 @@ public static IList CreateKeyParameters(this ODataContext cont private static List CreateAlternateKeyParameters(ODataContext context, ODataKeySegment keySegment, OpenApiDocument document) { Debug.Assert(keySegment.Kind == ODataSegmentKind.Key); - + var parameters = new List(); var entityType = keySegment.EntityType; - var alternateKeys = context.Model.GetAlternateKeysAnnotation(entityType); - + var alternateKeys = context.Model.GetAlternateKeysAnnotation(entityType); + foreach (var alternateKey in alternateKeys) { if (alternateKey.Count() == 1) @@ -255,7 +255,7 @@ private static List CreateAlternateKeyParameters(ODataContext Required = true } ); - } + } } else { @@ -273,7 +273,7 @@ private static List CreateAlternateKeyParameters(ODataContext Required = true } ); - } + } } } } @@ -317,7 +317,7 @@ public static List CreatePathParameters(this ODataPath path, { IDictionary mappings = parameterMappings[operationSegment]; var parameters = context.CreateParameters(function, document, mappings); - pathParameters.AddRange(parameters); + pathParameters.AddRange(parameters); } } @@ -454,7 +454,7 @@ public static void AppendParameter(this IList parameters, IOp Utils.CheckArgumentNull(target, nameof(target)); Utils.CheckArgumentNull(document, nameof(document)); - if (context.Model.GetRecord(target, CapabilitiesConstants.SearchRestrictions) is not {} search || search.IsSearchable) + if (context.Model.GetRecord(target, CapabilitiesConstants.SearchRestrictions) is not { } search || search.IsSearchable) { return new OpenApiParameterReference("search", document); } @@ -494,7 +494,7 @@ public static void AppendParameter(this IList parameters, IOp Utils.CheckArgumentNull(target, nameof(target)); Utils.CheckArgumentNull(document, nameof(document)); - if (context.Model.GetRecord(target, CapabilitiesConstants.CountRestrictions) is not {} count || count.IsCountable) + if (context.Model.GetRecord(target, CapabilitiesConstants.CountRestrictions) is not { } count || count.IsCountable) { return new OpenApiParameterReference("count", document); } @@ -535,7 +535,7 @@ public static void AppendParameter(this IList parameters, IOp Utils.CheckArgumentNull(target, nameof(target)); Utils.CheckArgumentNull(document, nameof(document)); - if (context.Model.GetRecord(target, CapabilitiesConstants.FilterRestrictions) is not {} filter || filter.IsFilterable) + if (context.Model.GetRecord(target, CapabilitiesConstants.FilterRestrictions) is not { } filter || filter.IsFilterable) { return new OpenApiParameterReference("filter", document); } @@ -598,18 +598,6 @@ public static void AppendParameter(this IList parameters, IOp return context.CreateOrderBy(navigationProperty, navigationProperty.ToEntityType()); } - /// - /// Create $orderby parameter for the . - /// - /// The OData context. - /// The Edm annotation target. - /// The Edm Entity type. - /// The created or null. - public static OpenApiParameter? CreateOrderBy(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmEntityType entityType) - {// patchwork to avoid breaking changes - return context.CreateOrderBy(target, entityType as IEdmStructuredType); - } - public static OpenApiParameter? CreateOrderBy(this ODataContext context, string targetPath, IEdmStructuredType structuredType) { IEdmTargetPath target = context.Model.GetTargetPath(targetPath); @@ -619,6 +607,13 @@ public static void AppendParameter(this IList parameters, IOp return context.CreateOrderBy(target, structuredType); } + /// + /// Create $orderby parameter for the . + /// + /// The OData context. + /// The Edm annotation target. + /// The Edm structured type. + /// The created or null. public static OpenApiParameter? CreateOrderBy(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmStructuredType structuredType) { Utils.CheckArgumentNull(context, nameof(context)); @@ -684,7 +679,7 @@ public static void AppendParameter(this IList parameters, IOp Utils.CheckArgumentNull(context, nameof(context)); Utils.CheckArgumentNull(targetPath, nameof(targetPath)); - if (context.Model.GetTargetPath(targetPath) is not {} target) + if (context.Model.GetTargetPath(targetPath) is not { } target) return null; return context.CreateSelect(target, entityType); @@ -714,18 +709,6 @@ public static void AppendParameter(this IList parameters, IOp return context.CreateSelect(navigationProperty, navigationProperty.ToEntityType()); } - /// - /// Create $select parameter for the . - /// - /// The OData context. - /// The Edm target. - /// The Edm entity type. - /// The created or null. - public static OpenApiParameter? CreateSelect(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmEntityType entityType) - { // patchwork to avoid breaking changes - return context.CreateSelect(target, entityType as IEdmStructuredType); - } - public static OpenApiParameter? CreateSelect(this ODataContext context, string targetPath, IEdmStructuredType structuredType) { IEdmTargetPath target = context.Model.GetTargetPath(targetPath); @@ -735,6 +718,13 @@ public static void AppendParameter(this IList parameters, IOp return context.CreateSelect(target, structuredType); } + /// + /// Create $select parameter for the . + /// + /// The OData context. + /// The Edm target. + /// The Edm structured type. + /// The created or null. public static OpenApiParameter? CreateSelect(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmStructuredType structuredType) { Utils.CheckArgumentNull(context, nameof(context)); @@ -820,18 +810,6 @@ public static void AppendParameter(this IList parameters, IOp return context.CreateExpand(navigationProperty, navigationProperty.ToEntityType()); } - /// - /// Create $expand parameter for the . - /// - /// The OData context. - /// The edm entity path. - /// The edm entity path. - /// The created or null. - public static OpenApiParameter? CreateExpand(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmEntityType entityType) - { // patchwork to avoid breaking changes - return context.CreateExpand(target, entityType as IEdmStructuredType); - } - public static OpenApiParameter? CreateExpand(this ODataContext context, string targetPath, IEdmStructuredType structuredType) { IEdmTargetPath target = context.Model.GetTargetPath(targetPath); @@ -841,6 +819,13 @@ public static void AppendParameter(this IList parameters, IOp return context.CreateExpand(target, structuredType); } + /// + /// Create $expand parameter for the . + /// + /// The OData context. + /// The Edm annotation target. + /// The Edm structured type. + /// The created or null. public static OpenApiParameter? CreateExpand(this ODataContext context, IEdmVocabularyAnnotatable target, IEdmStructuredType structuredType) { Utils.CheckArgumentNull(context, nameof(context)); @@ -853,7 +838,7 @@ public static void AppendParameter(this IList parameters, IOp return null; } - List expandItems = [ "*" ]; + List expandItems = ["*"]; foreach (var property in structuredType.NavigationProperties()) { diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs index fdbf03338..f4dff87e5 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs @@ -25,11 +25,12 @@ internal static class OpenApiResponseGenerator /// The created . public static IOpenApiResponse? GetResponse(this string statusCode, OpenApiDocument document) { - return statusCode switch { + return statusCode switch + { Constants.StatusCodeDefault => new OpenApiResponseReference(Constants.Error, document), - Constants.StatusCode204 => new OpenApiResponse { Description = Constants.Success}, - Constants.StatusCode201 => new OpenApiResponse { Description = Constants.Created}, - Constants.StatusCodeClass2XX => new OpenApiResponse { Description = Constants.Success}, + Constants.StatusCode204 => new OpenApiResponse { Description = Constants.Success }, + Constants.StatusCode201 => new OpenApiResponse { Description = Constants.Created }, + Constants.StatusCodeClass2XX => new OpenApiResponse { Description = Constants.Success }, Constants.StatusCodeClass4XX => new OpenApiResponseReference(Constants.Error, document), Constants.StatusCodeClass5XX => new OpenApiResponseReference(Constants.Error, document), _ => null, @@ -49,12 +50,12 @@ public static void AddResponsesToDocument(this ODataContext context, OpenApiDocu Utils.CheckArgumentNull(context, nameof(context)); Utils.CheckArgumentNull(document, nameof(document)); - var responses = new Dictionary + var responses = new Dictionary { { "error", context.CreateErrorResponse(document) } }; - if(context.Settings.EnableDollarCountPath) + if (context.Settings.EnableDollarCountPath) { responses[Constants.DollarCountSchemaName] = CreateCountResponse(document); } @@ -71,13 +72,13 @@ public static void AddResponsesToDocument(this ODataContext context, OpenApiDocu .Where(x => !responses.ContainsKey(x.Key))) .ToDictionary(x => x.Key, x => x.Value); - if(context.HasAnyNonContainedCollections()) + if (context.HasAnyNonContainedCollections()) responses[$"String{Constants.CollectionSchemaSuffix}"] = CreateCollectionResponse("String", document); foreach (IEdmOperation operation in context.Model.SchemaElements.OfType() .Where(op => context.Model.OperationTargetsMultiplePaths(op))) { - if (context.CreateOperationResponse(operation, document) is {} response) + if (context.CreateOperationResponse(operation, document) is { } response) responses[$"{operation.Name}Response"] = response; } @@ -116,8 +117,8 @@ public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOp Utils.CheckArgumentNull(operation, nameof(operation)); OpenApiResponses responses = new(); - - if (operation.IsAction() && operation.GetReturn()?.Type == null && Constants.StatusCode204.GetResponse(document) is {} x204Response) + + if (operation.IsAction() && operation.ReturnType == null && Constants.StatusCode204.GetResponse(document) is { } x204Response) { responses.Add(Constants.StatusCode204, x204Response); } @@ -128,20 +129,20 @@ public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOp new OpenApiResponseReference($"{operation.Name}Response", document) ); } - else if (context.CreateOperationResponse(operation, document) is {} successResponse) + else if (context.CreateOperationResponse(operation, document) is { } successResponse) { responses.Add(context.Settings.UseSuccessStatusCodeRange ? Constants.StatusCodeClass2XX : Constants.StatusCode200, successResponse); } - if (context.Settings.ErrorResponsesAsDefault && Constants.StatusCodeDefault.GetResponse(document) is {} defaultResponse) + if (context.Settings.ErrorResponsesAsDefault && Constants.StatusCodeDefault.GetResponse(document) is { } defaultResponse) { responses.Add(Constants.StatusCodeDefault, defaultResponse); } else { - if (Constants.StatusCodeClass4XX.GetResponse(document) is {} x4Response) + if (Constants.StatusCodeClass4XX.GetResponse(document) is { } x4Response) responses.Add(Constants.StatusCodeClass4XX, x4Response); - if (Constants.StatusCodeClass5XX.GetResponse(document) is {} x5Response) + if (Constants.StatusCodeClass5XX.GetResponse(document) is { } x5Response) responses.Add(Constants.StatusCodeClass5XX, x5Response); } @@ -150,7 +151,7 @@ public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOp public static OpenApiResponse? CreateOperationResponse(this ODataContext context, IEdmOperation operation, OpenApiDocument document) { - if (operation.GetReturn()?.Type is not {} returnType) + if (operation.ReturnType is not { } returnType) return null; IOpenApiSchema schema; @@ -167,7 +168,7 @@ public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOp } }; - if (context.Settings.EnableODataAnnotationReferencesForResponses && + if (context.Settings.EnableODataAnnotationReferencesForResponses && (operation.IsDeltaFunction() || context.Settings.EnablePagination || context.Settings.EnableCount)) { schema = new OpenApiSchema @@ -274,7 +275,7 @@ private static OpenApiResponse CreateCollectionResponse(string typeName, OpenApi Constants.ApplicationJsonMediaType, new OpenApiMediaType { - Schema = new OpenApiSchemaReference($"{typeName}{Constants.CollectionSchemaSuffix}", document) + Schema = new OpenApiSchemaReference($"{typeName}{Constants.CollectionSchemaSuffix}", document) } } } diff --git a/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj b/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj index 40a461703..7809123aa 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj +++ b/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj @@ -6,7 +6,7 @@ true Microsoft Open API OData Reader Microsoft.OpenApi.OData - net8.0 + net10.0 Microsoft.OpenApi.OData true This package contains the codes you need to convert OData CSDL to Open API Document of Model. @@ -31,7 +31,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Microsoft.OpenApi.OData.Reader/OData/ODataConstants.cs b/src/Microsoft.OpenApi.OData.Reader/OData/ODataConstants.cs index 090f55f40..b8e46136f 100644 --- a/src/Microsoft.OpenApi.OData.Reader/OData/ODataConstants.cs +++ b/src/Microsoft.OpenApi.OData.Reader/OData/ODataConstants.cs @@ -25,12 +25,6 @@ internal static class ODataConstants /// public readonly static KeyValuePair OdataNextLink = new("@odata.nextLink", new OpenApiSchema { Type = JsonSchemaType.String | JsonSchemaType.Null }); - /// - /// @odata.count KeyValue pair - /// - [Obsolete("Use CreateOdataCount instead to specify the format of the count value.")] - public readonly static KeyValuePair OdataCount = new("@odata.count", new OpenApiSchema { Type = JsonSchemaType.Number | JsonSchemaType.Null, Format = "int64"}); - /// /// Creates an @odata.count KeyValue pair with the specified format. /// diff --git a/src/Microsoft.OpenApi.OData.Reader/OData/ODataPrimitiveValue.cs b/src/Microsoft.OpenApi.OData.Reader/OData/ODataPrimitiveValue.cs index 24a616723..f6ba94eaa 100644 --- a/src/Microsoft.OpenApi.OData.Reader/OData/ODataPrimitiveValue.cs +++ b/src/Microsoft.OpenApi.OData.Reader/OData/ODataPrimitiveValue.cs @@ -3,6 +3,9 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ +using System; +using System.Globalization; + namespace Microsoft.OpenApi.OData { /// @@ -22,7 +25,13 @@ public ODataPrimitiveValue(object value) public override string? ToString() { - return Value.ToString(); + return Value switch + { + // Match the OData literal formats of Microsoft.OData.Edm 8 Date and TimeOfDay, which Edm 9 replaced. + DateOnly date => date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), + TimeOnly time => time.ToString("HH:mm:ss.fffffff", CultureInfo.InvariantCulture), + _ => Value.ToString(), + }; } } } diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs index 3725e282d..37b69a9b6 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs @@ -27,7 +27,7 @@ internal abstract class EdmOperationOperationHandler : OperationHandler /// The document to use to lookup references. protected EdmOperationOperationHandler(OpenApiDocument document) : base(document) { - + } private OperationRestrictionsType? _operationRestriction; @@ -85,10 +85,10 @@ protected override void SetBasicInfo(OpenApiOperation operation) operation.Summary = "Invoke " + (EdmOperation?.IsAction() ?? false ? "action " : "function ") + EdmOperation?.Name; // Description - operation.Description = (string.IsNullOrEmpty(TargetPath) ? null :Context?.Model.GetDescriptionAnnotation(TargetPath)) ?? Context?.Model.GetDescriptionAnnotation(EdmOperation); + operation.Description = (string.IsNullOrEmpty(TargetPath) ? null : Context?.Model.GetDescriptionAnnotation(TargetPath)) ?? Context?.Model.GetDescriptionAnnotation(EdmOperation); // OperationId - if (Context is {Settings.EnableOperationId: true} && Path is not null) + if (Context is { Settings.EnableOperationId: true } && Path is not null) { // When the key segment is available, // its EntityType name will be used @@ -101,7 +101,7 @@ protected override void SetBasicInfo(OpenApiOperation operation) { if (segment is ODataKeySegment keySegment) { - if (!keySegment.IsAlternateKey && segment is {EntityType: not null}) + if (!keySegment.IsAlternateKey && segment is { EntityType: not null }) { identifiers.Add(segment.EntityType.Name); continue; @@ -117,7 +117,7 @@ protected override void SetBasicInfo(OpenApiOperation operation) identifiers.Add(keySegment.Identifier); } } - else if (segment is ODataOperationSegment {Identifier: not null} opSegment) + else if (segment is ODataOperationSegment { Identifier: not null } opSegment) { if (opSegment.Operation is IEdmFunction function && Context.Model.IsOperationOverload(function)) { @@ -215,7 +215,7 @@ protected override void SetParameters(OpenApiOperation operation) } /// - protected override void SetResponses(OpenApiOperation operation) + protected override void SetResponses(OpenApiOperation operation) { if (EdmOperation is not null && Context is not null) operation.Responses = Context.CreateResponses(EdmOperation, _document); @@ -254,35 +254,35 @@ protected override void AppendCustomParameters(OpenApiOperation operation) private void AppendSystemQueryOptions(IEdmFunction function, OpenApiOperation operation) { - if (function.GetReturn()?.Type is {} functionReturnType && functionReturnType.IsCollection() && Context is not null) + if (function.ReturnType is { } functionReturnType && functionReturnType.IsCollection() && Context is not null) { operation.Parameters ??= []; // $top - if (Context.CreateTop(function, _document) is {} topParameter) + if (Context.CreateTop(function, _document) is { } topParameter) { operation.Parameters.AppendParameter(topParameter); } // $skip - if (Context.CreateSkip(function, _document) is {} skipParameter) + if (Context.CreateSkip(function, _document) is { } skipParameter) { operation.Parameters.AppendParameter(skipParameter); } // $search - if (Context.CreateSearch(function, _document) is {} searchParameter) + if (Context.CreateSearch(function, _document) is { } searchParameter) { operation.Parameters.AppendParameter(searchParameter); } // $filter - if (Context.CreateFilter(function, _document) is {} filterParameter) + if (Context.CreateFilter(function, _document) is { } filterParameter) { operation.Parameters.AppendParameter(filterParameter); } // $count - if (Context.CreateCount(function, _document) is {} countParameter) + if (Context.CreateCount(function, _document) is { } countParameter) { operation.Parameters.AppendParameter(countParameter); } @@ -290,19 +290,19 @@ private void AppendSystemQueryOptions(IEdmFunction function, OpenApiOperation op if (functionReturnType?.Definition?.AsElementType() is IEdmEntityType entityType) { // $select - if (Context.CreateSelect(function, entityType) is {} selectParameter) + if (Context.CreateSelect(function, entityType) is { } selectParameter) { operation.Parameters.AppendParameter(selectParameter); } // $orderby - if (Context.CreateOrderBy(function, entityType) is {} orderbyParameter) + if (Context.CreateOrderBy(function, entityType) is { } orderbyParameter) { operation.Parameters.AppendParameter(orderbyParameter); } // $expand - if (Context.CreateExpand(function, entityType) is {} expandParameter) + if (Context.CreateExpand(function, entityType) is { } expandParameter) { operation.Parameters.AppendParameter(expandParameter); } @@ -313,20 +313,20 @@ private void AppendSystemQueryOptions(IEdmFunction function, OpenApiOperation op /// protected override void SetCustomLinkRelType() { - if (Context is {Settings.CustomHttpMethodLinkRelMapping: not null} && EdmOperation != null) + if (Context is { Settings.CustomHttpMethodLinkRelMapping: not null } && EdmOperation != null) { LinkRelKey key = EdmOperation.IsAction() ? LinkRelKey.Action : LinkRelKey.Function; Context.Settings.CustomHttpMethodLinkRelMapping.TryGetValue(key, out var linkRelValue); - CustomLinkRel = linkRelValue; + CustomLinkRel = linkRelValue; } } - + /// protected override void SetExternalDocs(OpenApiOperation operation) { if (Context is { Settings.ShowExternalDocs: true }) { - var externalDocs = (string.IsNullOrEmpty(TargetPath), string.IsNullOrEmpty(CustomLinkRel)) switch + var externalDocs = (string.IsNullOrEmpty(TargetPath), string.IsNullOrEmpty(CustomLinkRel)) switch { (false, false) => Context.Model.GetLinkRecord(TargetPath!, CustomLinkRel!), (true, false) when EdmOperation is not null => Context.Model.GetLinkRecord(EdmOperation, CustomLinkRel!), @@ -347,14 +347,14 @@ protected override void SetExternalDocs(OpenApiOperation operation) // protected override void SetExtensions(OpenApiOperation operation) { - if (Context is { Settings.EnablePagination: true } && EdmOperation?.GetReturn()?.Type?.TypeKind() == EdmTypeKind.Collection) + if (Context is { Settings.EnablePagination: true } && EdmOperation?.ReturnType?.TypeKind() == EdmTypeKind.Collection) { var extension = new JsonObject { { "nextLinkName", "@odata.nextLink"}, { "operationName", Context.Settings.PageableOperationName} }; - + operation.Extensions ??= new Dictionary(); operation.Extensions.Add(Constants.xMsPageable, new JsonNodeExtension(extension)); } diff --git a/src/OoasGui/OoasGui.csproj b/src/OoasGui/OoasGui.csproj index e483343d3..ff41f3f2a 100644 --- a/src/OoasGui/OoasGui.csproj +++ b/src/OoasGui/OoasGui.csproj @@ -1,6 +1,6 @@  - net8.0-windows + net10.0-windows WinExe false true @@ -16,7 +16,7 @@ - + diff --git a/src/OoasUtil/OoasUtil.csproj b/src/OoasUtil/OoasUtil.csproj index ee59d187d..db64c1ab8 100644 --- a/src/OoasUtil/OoasUtil.csproj +++ b/src/OoasUtil/OoasUtil.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Common/EdmModelHelper.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Common/EdmModelHelper.cs index a7d5841c3..42957ff8a 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Common/EdmModelHelper.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Common/EdmModelHelper.cs @@ -401,7 +401,7 @@ public static string GetCsdl(IEdmModel model) }; using XmlWriter xw = XmlWriter.Create(sw, settings); - CsdlWriter.TryWriteCsdl(model, xw, CsdlTarget.OData, out var _); + CsdlWriter.TryWriteCsdl(model, xw, out var _); xw.Flush(); string edmx = sw.ToString(); diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/EdmVocabularyAnnotationExtensionsTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/EdmVocabularyAnnotationExtensionsTests.cs index fa1d83dca..217255767 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/EdmVocabularyAnnotationExtensionsTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/EdmVocabularyAnnotationExtensionsTests.cs @@ -12,7 +12,6 @@ using Microsoft.OData.Edm.Validation; using Microsoft.OData.Edm.Vocabularies; using Microsoft.OpenApi.OData.Vocabulary.Capabilities; -using Moq; using Xunit; namespace Microsoft.OpenApi.OData.Edm.Tests @@ -36,12 +35,12 @@ public void AnnotationCacheHandlesMixedTargetHashCollisions(bool pathFirst) IEdmVocabularyAnnotatable first = pathFirst ? path : other; IEdmVocabularyAnnotatable second = pathFirst ? other : path; - Assert.Equal(pathFirst ? "path" : "other", model.Object.GetString(first, CachedStringTerm.FullName())); - Assert.Equal(pathFirst ? "other" : "path", model.Object.GetString(second, CachedStringTerm.FullName())); + Assert.Equal(pathFirst ? "path" : "other", model.GetString(first, CachedStringTerm.FullName())); + Assert.Equal(pathFirst ? "other" : "path", model.GetString(second, CachedStringTerm.FullName())); // Revisit both keys so a stored path is compared with a nonpath in either insertion order. - Assert.Equal("other", model.Object.GetString(other, CachedStringTerm.FullName())); - Assert.Equal("path", model.Object.GetString(path, CachedStringTerm.FullName())); - model.Verify(m => m.FindDeclaredVocabularyAnnotations(It.IsAny()), Times.Exactly(2)); + Assert.Equal("other", model.GetString(other, CachedStringTerm.FullName())); + Assert.Equal("path", model.GetString(path, CachedStringTerm.FullName())); + Assert.Equal(2, model.FindDeclaredVocabularyAnnotationsCallCount); } [Fact] @@ -56,10 +55,10 @@ public void AnnotationCacheSharesEquivalentPathsUsingOrdinalEquality() new EdmVocabularyAnnotation(path, CachedStringTerm, new EdmStringConstant("original")), new EdmVocabularyAnnotation(differentPath, CachedStringTerm, new EdmStringConstant("different"))); - Assert.Equal("original", model.Object.GetString(path, CachedStringTerm.FullName())); - Assert.Equal("original", model.Object.GetString(equivalentPath, CachedStringTerm.FullName())); - Assert.Equal("different", model.Object.GetString(differentPath, CachedStringTerm.FullName())); - model.Verify(m => m.FindDeclaredVocabularyAnnotations(It.IsAny()), Times.Exactly(2)); + Assert.Equal("original", model.GetString(path, CachedStringTerm.FullName())); + Assert.Equal("original", model.GetString(equivalentPath, CachedStringTerm.FullName())); + Assert.Equal("different", model.GetString(differentPath, CachedStringTerm.FullName())); + Assert.Equal(2, model.FindDeclaredVocabularyAnnotationsCallCount); } [Fact] @@ -72,10 +71,10 @@ public void AnnotationCachePreservesNonpathValueEqualityAndHashing() new EdmVocabularyAnnotation(target, CachedStringTerm, new EdmStringConstant("original")), new EdmVocabularyAnnotation(differentTarget, CachedStringTerm, new EdmStringConstant("different"))); - Assert.Equal("original", model.Object.GetString(target, CachedStringTerm.FullName())); - Assert.Equal("original", model.Object.GetString(equivalentTarget, CachedStringTerm.FullName())); - Assert.Equal("different", model.Object.GetString(differentTarget, CachedStringTerm.FullName())); - model.Verify(m => m.FindDeclaredVocabularyAnnotations(It.IsAny()), Times.Exactly(2)); + Assert.Equal("original", model.GetString(target, CachedStringTerm.FullName())); + Assert.Equal("original", model.GetString(equivalentTarget, CachedStringTerm.FullName())); + Assert.Equal("different", model.GetString(differentTarget, CachedStringTerm.FullName())); + Assert.Equal(2, model.FindDeclaredVocabularyAnnotationsCallCount); } [Fact] @@ -87,10 +86,10 @@ public void AnnotationCachePreservesNonpathReferenceEquality() new EdmVocabularyAnnotation(target, CachedStringTerm, new EdmStringConstant("original")), new EdmVocabularyAnnotation(differentTarget, CachedStringTerm, new EdmStringConstant("different"))); - Assert.Equal("original", model.Object.GetString(target, CachedStringTerm.FullName())); - Assert.Equal("different", model.Object.GetString(differentTarget, CachedStringTerm.FullName())); - Assert.Equal("original", model.Object.GetString(target, CachedStringTerm.FullName())); - model.Verify(m => m.FindDeclaredVocabularyAnnotations(It.IsAny()), Times.Exactly(2)); + Assert.Equal("original", model.GetString(target, CachedStringTerm.FullName())); + Assert.Equal("different", model.GetString(differentTarget, CachedStringTerm.FullName())); + Assert.Equal("original", model.GetString(target, CachedStringTerm.FullName())); + Assert.Equal(2, model.FindDeclaredVocabularyAnnotationsCallCount); } [Theory] @@ -104,24 +103,33 @@ public void AnnotationCacheInvalidatesBothTargetKindsWhenModelChanges(bool pathT var firstModel = GetCacheModel(new EdmVocabularyAnnotation(target, CachedStringTerm, new EdmStringConstant("first"))); var secondModel = GetCacheModel(new EdmVocabularyAnnotation(target, CachedStringTerm, new EdmStringConstant("second"))); - Assert.Equal("first", firstModel.Object.GetString(target, CachedStringTerm.FullName())); - Assert.Equal("second", secondModel.Object.GetString(target, CachedStringTerm.FullName())); - Assert.Equal("first", firstModel.Object.GetString(target, CachedStringTerm.FullName())); - firstModel.Verify(m => m.FindDeclaredVocabularyAnnotations(It.IsAny()), Times.Exactly(2)); - secondModel.Verify(m => m.FindDeclaredVocabularyAnnotations(It.IsAny()), Times.Once()); + Assert.Equal("first", firstModel.GetString(target, CachedStringTerm.FullName())); + Assert.Equal("second", secondModel.GetString(target, CachedStringTerm.FullName())); + Assert.Equal("first", firstModel.GetString(target, CachedStringTerm.FullName())); + Assert.Equal(2, firstModel.FindDeclaredVocabularyAnnotationsCallCount); + Assert.Equal(1, secondModel.FindDeclaredVocabularyAnnotationsCallCount); } - private static Mock GetCacheModel(params IEdmVocabularyAnnotation[] annotations) + private static CountingAnnotationModel GetCacheModel(params IEdmVocabularyAnnotation[] annotations) { - var model = new Mock(); - model.Setup(m => m.DirectValueAnnotationsManager).Returns(new EdmModel().DirectValueAnnotationsManager); - model.Setup(m => m.FindDeclaredTerm(CachedStringTerm.FullName())).Returns(CachedStringTerm); - // Keep the model lookup reference-based to isolate the annotation cache's key comparison. - model.Setup(m => m.FindDeclaredVocabularyAnnotations(It.IsAny())) - .Returns((IEdmVocabularyAnnotatable target) => annotations.Where(a => ReferenceEquals(a.Target, target))); + var model = new CountingAnnotationModel(annotations); + model.AddElement(CachedStringTerm); return model; } + // Moq cannot proxy IEdmModel in Microsoft.OData.Edm 9 because it has ReadOnlySpan members. + private sealed class CountingAnnotationModel(IEdmVocabularyAnnotation[] annotations) : EdmModel + { + public int FindDeclaredVocabularyAnnotationsCallCount { get; private set; } + + // Keep the model lookup reference-based to isolate the annotation cache's key comparison. + public override IEnumerable FindDeclaredVocabularyAnnotations(IEdmVocabularyAnnotatable element) + { + FindDeclaredVocabularyAnnotationsCallCount++; + return annotations.Where(a => ReferenceEquals(a.Target, element)); + } + } + private static EdmTargetPath CreateTargetPath(string entitySetName = "Items") { var container = new EdmEntityContainer("NS", "Default"); diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs index 630af975a..7d4250be5 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs @@ -122,7 +122,7 @@ public void GetPathsForGraphBetaModelWithDerivedTypesConstraintReturnsAllPaths() Assert.NotNull(paths); Assert.Equal(15861, paths.Count()); } - + [Theory] [InlineData(true)] [InlineData(false)] @@ -182,11 +182,11 @@ public void UseCountRestrictionsAnnotationsToAppendDollarCountSegmentsToNavigati if (countable) { Assert.NotNull(testPath); - } + } else { Assert.Null(testPath); - } + } } else { @@ -222,7 +222,8 @@ public void GetPathsDoesntReturnPathsForCountWhenDisabled() // Arrange IEdmModel model = GetInheritanceModel(string.Empty); ODataPathProvider provider = new ODataPathProvider(); - var settings = new OpenApiConvertSettings { + var settings = new OpenApiConvertSettings + { EnableDollarCountPath = false, AppendBoundOperationsOnDerivedTypeCastSegments = true }; @@ -281,8 +282,8 @@ public void GetOperationPathsForModelWithDerivedTypesConstraint( Assert.NotNull(paths); Assert.Equal(expectedCount, paths.Count()); var dollarCountPathsWithCastSegment = paths.Where(x => x.Kind == ODataPathKind.DollarCount && x.Any(y => y.Kind == ODataSegmentKind.TypeCast)); - if(addAnnotation && !getNavPropModel) - Assert.Single(dollarCountPathsWithCastSegment); + if (addAnnotation && !getNavPropModel) + Assert.Single(dollarCountPathsWithCastSegment); } [Theory] [InlineData(false, false, true, true, 4)] @@ -324,10 +325,10 @@ public void GetTypeCastPathsForModelWithDerivedTypesConstraint( Assert.NotNull(paths); Assert.Equal(expectedCount, paths.Count()); var dollarCountPathsWithCastSegment = paths.Where(x => x.Kind == ODataPathKind.DollarCount && x.Any(y => y.Kind == ODataSegmentKind.TypeCast)); - if(addAnnotation || !requireConstraint) - Assert.Single(dollarCountPathsWithCastSegment); + if (addAnnotation || !requireConstraint) + Assert.Single(dollarCountPathsWithCastSegment); else - Assert.Empty(dollarCountPathsWithCastSegment); + Assert.Empty(dollarCountPathsWithCastSegment); } #if DEBUG // Super useful for debugging tests. @@ -529,6 +530,7 @@ public void GetPathsWithFalseNavigabilityInNavigationRestrictionsAnnotationWorks + @@ -568,6 +570,7 @@ public void GetPathsWithFalseIndexabilityByKeyInNavigationRestrictionsAnnotation + @@ -610,6 +613,7 @@ public void GetPathsWithReferenceableNavigationPropertyWorks() + @@ -656,6 +660,7 @@ public void GetPathsWithNonReferenceableNavigationPropertyWorks() + "; @@ -690,6 +695,7 @@ public void GetPathsWithContainedNavigationPropertyWorks() + "; @@ -787,7 +793,7 @@ public void GetPathsWithAlternateKeyParametersWorks() OpenApiConvertSettings settings = new() { EnableKeyAsSegment = true, - AddAlternateKeyPaths= true + AddAlternateKeyPaths = true }; // Act diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Microsoft.OpenAPI.OData.Reader.Tests.csproj b/test/Microsoft.OpenAPI.OData.Reader.Tests/Microsoft.OpenAPI.OData.Reader.Tests.csproj index 62eacb1ca..c66d93e72 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Microsoft.OpenAPI.OData.Reader.Tests.csproj +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Microsoft.OpenAPI.OData.Reader.Tests.csproj @@ -3,7 +3,7 @@ Microsoft.OpenApi.OData.Reader.Tests Microsoft.OpenApi.OData.Reader.Tests - net8.0 + net10.0 latest true ..\..\tool\Microsoft.OpenApi.OData.snk diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmActionOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmActionOperationHandlerTests.cs index 569b62257..00d0b0990 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmActionOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmActionOperationHandlerTests.cs @@ -20,14 +20,16 @@ public class EdmActionOperationHandlerTests { public EdmActionOperationHandlerTests() { - _openApiDocument.AddComponent("Delegated (work or school account)", new OpenApiSecurityScheme { - Type = SecuritySchemeType.OAuth2, - }); - _openApiDocument.AddComponent("Application", new OpenApiSecurityScheme { - Type = SecuritySchemeType.OAuth2, - }); - _openApiDocument.Tags ??= new HashSet(); - _openApiDocument.Tags.Add(new OpenApiTag { Name = "me.user.Actions" }); + _openApiDocument.AddComponent("Delegated (work or school account)", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.OAuth2, + }); + _openApiDocument.AddComponent("Application", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.OAuth2, + }); + _openApiDocument.Tags ??= new HashSet(); + _openApiDocument.Tags.Add(new OpenApiTag { Name = "me.user.Actions" }); } private readonly OpenApiDocument _openApiDocument = new(); private EdmActionOperationHandler _operationHandler => new(_openApiDocument); @@ -286,7 +288,7 @@ public async Task OperationRestrictionsTermWorksToCreateOperationForEdmAction(bo if (enableAnnotation) { - Assert.NotNull(operation.Security); + Assert.NotNull(operation.Security); Assert.Equal(2, operation.Security.Count); string json = await operation.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); @@ -342,13 +344,13 @@ public void CreateOperationForEdmActionWithCollectionReturnTypeContainsXMsPageab var operation = _operationHandler.CreateOperation(context, path); // Assert - if (enablePagination && action.GetReturn()?.Type?.IsCollection() == true) + if (enablePagination && action.ReturnType?.IsCollection() == true) { - Assert.True(operation.Extensions.ContainsKey(Common.Constants.xMsPageable)); + Assert.True(operation.Extensions.ContainsKey(Common.Constants.xMsPageable)); } else { - Assert.DoesNotContain(Common.Constants.xMsPageable, operation.Extensions?.Keys.Select(static x => x) ?? []); + Assert.DoesNotContain(Common.Constants.xMsPageable, operation.Extensions?.Keys.Select(static x => x) ?? []); } } @@ -390,8 +392,8 @@ public void CreateOperationForEdmActionWithCollectionReturnTypeHasResponseWithNe } else if (enablePagination) - { - Assert.True(responseProperties.ContainsKey("@odata.nextLink")); + { + Assert.True(responseProperties.ContainsKey("@odata.nextLink")); } else { diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs index 636108ffa..778d190ae 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs @@ -20,14 +20,16 @@ public class EdmFunctionOperationHandlerTests { public EdmFunctionOperationHandlerTests() { - _openApiDocument.AddComponent("Delegated (work or school account)", new OpenApiSecurityScheme { - Type = SecuritySchemeType.OAuth2, - }); - _openApiDocument.AddComponent("Application", new OpenApiSecurityScheme { - Type = SecuritySchemeType.OAuth2, - }); - _openApiDocument.Tags ??= new HashSet(); - _openApiDocument.Tags.Add(new OpenApiTag { Name = "me.user.Functions" }); + _openApiDocument.AddComponent("Delegated (work or school account)", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.OAuth2, + }); + _openApiDocument.AddComponent("Application", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.OAuth2, + }); + _openApiDocument.Tags ??= new HashSet(); + _openApiDocument.Tags.Add(new OpenApiTag { Name = "me.user.Functions" }); } private readonly OpenApiDocument _openApiDocument = new(); @@ -36,51 +38,51 @@ public EdmFunctionOperationHandlerTests() [Fact] public void SetsDeprecationInformation() { - // Arrange - IEdmModel model = EdmModelHelper.TripServiceModel; - ODataContext context = new(model); - IEdmEntitySet people = model.EntityContainer.FindEntitySet("People"); - Assert.NotNull(people); - - IEdmFunction function = model.SchemaElements.OfType().First(f => f.Name == "GetFriendsTrips"); - Assert.NotNull(function); - - ODataPath path = new(new ODataNavigationSourceSegment(people), new ODataKeySegment(people.EntityType), new ODataOperationSegment(function)); - - // Act - var operation = _operationHandler.CreateOperation(context, path); - - // Assert - Assert.NotNull(operation); - Assert.True(operation.Deprecated); - Assert.NotEmpty(operation.Extensions); - var extension = operation.Extensions["x-ms-deprecation"]; - Assert.NotNull(extension); + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new(model); + IEdmEntitySet people = model.EntityContainer.FindEntitySet("People"); + Assert.NotNull(people); + + IEdmFunction function = model.SchemaElements.OfType().First(f => f.Name == "GetFriendsTrips"); + Assert.NotNull(function); + + ODataPath path = new(new ODataNavigationSourceSegment(people), new ODataKeySegment(people.EntityType), new ODataOperationSegment(function)); + + // Act + var operation = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(operation); + Assert.True(operation.Deprecated); + Assert.NotEmpty(operation.Extensions); + var extension = operation.Extensions["x-ms-deprecation"]; + Assert.NotNull(extension); } [Fact] public void DoesntSetDeprecationInformation() { - // Arrange - IEdmModel model = EdmModelHelper.TripServiceModel; - ODataContext context = new(model); - IEdmEntitySet people = model.EntityContainer.FindEntitySet("People"); - Assert.NotNull(people); - - IEdmFunction function = model.SchemaElements.OfType().First(f => f.Name == "GetFavoriteAirline"); - Assert.NotNull(function); - - ODataPath path = new(new ODataNavigationSourceSegment(people), new ODataKeySegment(people.EntityType), new ODataOperationSegment(function)); - - // Act - var operation = _operationHandler.CreateOperation(context, path); - - // Assert - Assert.NotNull(operation); - Assert.True(operation.Deprecated); - Assert.NotEmpty(operation.Extensions); - var extension = operation.Extensions["x-ms-deprecation"]; - Assert.NotNull(extension); + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + ODataContext context = new(model); + IEdmEntitySet people = model.EntityContainer.FindEntitySet("People"); + Assert.NotNull(people); + + IEdmFunction function = model.SchemaElements.OfType().First(f => f.Name == "GetFavoriteAirline"); + Assert.NotNull(function); + + ODataPath path = new(new ODataNavigationSourceSegment(people), new ODataKeySegment(people.EntityType), new ODataOperationSegment(function)); + + // Act + var operation = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(operation); + Assert.True(operation.Deprecated); + Assert.NotEmpty(operation.Extensions); + var extension = operation.Extensions["x-ms-deprecation"]; + Assert.NotNull(extension); } #endregion @@ -245,7 +247,7 @@ public void CreateOperationForEdmFunctionWithTypeCastReturnsCorrectOperationId(b new ODataKeySegment(customer), new ODataTypeCastSegment(vipCustomer, model), new ODataOperationSegment(function)); - + // Act var operation = _operationHandler.CreateOperation(context, path); @@ -492,7 +494,7 @@ public async Task OperationRestrictionsTermWorksToCreateOperationForEdmFunction( if (enableAnnotation) { - Assert.NotNull(operation.Security); + Assert.NotNull(operation.Security); Assert.Equal(2, operation.Security.Count); string json = await operation.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); @@ -546,13 +548,13 @@ public void CreateOperationForEdmFunctionWithCollectionReturnTypeContainsXMsPage var operation = _operationHandler.CreateOperation(context, path); // Assert - if (enablePagination && function.GetReturn()?.Type?.IsCollection() == true) + if (enablePagination && function.ReturnType?.IsCollection() == true) { - Assert.True(operation.Extensions.ContainsKey(Common.Constants.xMsPageable)); + Assert.True(operation.Extensions.ContainsKey(Common.Constants.xMsPageable)); } else { - Assert.DoesNotContain(Common.Constants.xMsPageable, operation.Extensions?.Keys.Select(static x => x) ?? []); + Assert.DoesNotContain(Common.Constants.xMsPageable, operation.Extensions?.Keys.Select(static x => x) ?? []); } } @@ -580,7 +582,7 @@ public void CreateOperationForFunctionWithDateTimeParametersReturnsCorrectPathIt // Act OpenApiOperation operation = _operationHandler.CreateOperation(context, path); string pathItemName = path.GetPathItemName(); - + // Assert Assert.NotNull(operation); Assert.Equal("/reports/microsoft.graph.getPrinterUsageSummary(printerId={printerId},periodStart={periodStart},periodEnd={periodEnd})", pathItemName); diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Vocabulary/Capabilities/CustomParameterTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Vocabulary/Capabilities/CustomParameterTests.cs index 4e0b12e8a..4e7d1f1f1 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Vocabulary/Capabilities/CustomParameterTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Vocabulary/Capabilities/CustomParameterTests.cs @@ -103,7 +103,7 @@ private static void VerifyCustomParameter(CustomParameter parameter) value = parameter.ExampleValues[1]; Assert.Null(value.Description); Assert.NotNull(value.Value); - Assert.Equal(new TimeOnly(3, 4, 5, 6).ToString("HH:mm:ss.fffffff"), value.Value.Value.ToString()); + Assert.Equal(new TimeOnly(3, 4, 5, 6).ToString("HH:mm:ss.fffffff"), value.Value.ToString()); } private IEdmModel GetEdmModel(string annotation) diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Vocabulary/Core/PrimitiveExampleValueTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Vocabulary/Core/PrimitiveExampleValueTests.cs index 00f92cc3c..2e2f36872 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Vocabulary/Core/PrimitiveExampleValueTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Vocabulary/Core/PrimitiveExampleValueTests.cs @@ -108,15 +108,15 @@ public void PrimitiveExamplevalueInitializeWorksForPrimitiveData(string data, ob Assert.NotNull(value.Value); switch (except) { - case DateOnly dateOnly: - Assert.Equal(dateOnly.ToString("yyyy-MM-dd"), value.Value.Value.ToString()); - break; - case TimeOnly timeOnly: - Assert.Equal(timeOnly.ToString("HH:mm:ss.fffffff"), value.Value.Value.ToString()); - break; - default: - Assert.Equal(except, value.Value.Value); - break; + case DateOnly dateOnly: + Assert.Equal(dateOnly.ToString("yyyy-MM-dd"), value.Value.ToString()); + break; + case TimeOnly timeOnly: + Assert.Equal(timeOnly.ToString("HH:mm:ss.fffffff"), value.Value.ToString()); + break; + default: + Assert.Equal(except, value.Value.Value); + break; } } diff --git a/test/Microsoft.OpenApi.Hidi.Tests/verify-hidi-package-assembly.Tests.ps1 b/test/Microsoft.OpenApi.Hidi.Tests/verify-hidi-package-assembly.Tests.ps1 index 32cd844c9..53c343eed 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/verify-hidi-package-assembly.Tests.ps1 +++ b/test/Microsoft.OpenApi.Hidi.Tests/verify-hidi-package-assembly.Tests.ps1 @@ -6,10 +6,10 @@ BeforeDiscovery { @{ Name = 'matching signed DLL before NuGet signing' } @{ Name = 'different payload bytes'; Payload = 'unsigned-build'; ExpectedError = '*differs from the signed staging DLL*' } @{ Name = 'missing tool DLL'; Entries = @(); ExpectedError = '*exactly one Hidi DLL*' } - @{ Name = 'wrong tool location'; Entries = @('lib/net8.0/Microsoft.OpenApi.Hidi.dll'); ExpectedError = '*exactly one Hidi DLL*' } - @{ Name = 'duplicate tool DLL'; Entries = @('tools/net8.0/any/Microsoft.OpenApi.Hidi.dll', 'tools/net8.0/any/Microsoft.OpenApi.Hidi.dll'); ExpectedError = '*exactly one Hidi DLL*' } - @{ Name = 'extra tool DLL elsewhere'; Entries = @('tools/net8.0/any/Microsoft.OpenApi.Hidi.dll', 'lib/net8.0/Microsoft.OpenApi.Hidi.dll'); ExpectedError = '*exactly one Hidi DLL*' } - @{ Name = 'wrong entry casing'; Entries = @('tools/net8.0/any/microsoft.openapi.hidi.dll'); ExpectedError = '*exactly one Hidi DLL*' } + @{ Name = 'wrong tool location'; Entries = @('lib/net10.0/Microsoft.OpenApi.Hidi.dll'); ExpectedError = '*exactly one Hidi DLL*' } + @{ Name = 'duplicate tool DLL'; Entries = @('tools/net10.0/any/Microsoft.OpenApi.Hidi.dll', 'tools/net10.0/any/Microsoft.OpenApi.Hidi.dll'); ExpectedError = '*exactly one Hidi DLL*' } + @{ Name = 'extra tool DLL elsewhere'; Entries = @('tools/net10.0/any/Microsoft.OpenApi.Hidi.dll', 'lib/net10.0/Microsoft.OpenApi.Hidi.dll'); ExpectedError = '*exactly one Hidi DLL*' } + @{ Name = 'wrong entry casing'; Entries = @('tools/net10.0/any/microsoft.openapi.hidi.dll'); ExpectedError = '*exactly one Hidi DLL*' } @{ Name = 'unsigned package payload'; PackageStatus = 'NotSigned'; ExpectedError = '*valid Microsoft Corporation Authenticode signature*' } @{ Name = 'damaged package signature'; PackageStatus = 'HashMismatch'; ExpectedError = '*valid Microsoft Corporation Authenticode signature*' } @{ Name = 'unverifiable package signature'; PackageStatus = 'UnknownError'; ExpectedError = '*valid Microsoft Corporation Authenticode signature*' } @@ -46,7 +46,7 @@ Describe 'Hidi package signed-assembly provenance' { else { $archive = [IO.Compression.ZipFile]::Open($package, 'Create') try { - $entryNames = if ($testCase.ContainsKey('Entries')) { $Entries } else { @('tools/net8.0/any/Microsoft.OpenApi.Hidi.dll') } + $entryNames = if ($testCase.ContainsKey('Entries')) { $Entries } else { @('tools/net10.0/any/Microsoft.OpenApi.Hidi.dll') } foreach ($name in $entryNames) { $writer = [IO.StreamWriter]::new($archive.CreateEntry($name).Open()) try { $writer.Write($(if ($Payload) { $Payload } else { 'signed-staging' })) }