diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index 53b181b22..8bef85221 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -65,6 +65,11 @@ extends: displayName: 'Use .NET 8' inputs: version: 8.x + + - task: UseDotNet@2 + displayName: 'Use .NET 10' + inputs: + version: 10.x # Install the nuget tool. - task: NuGetToolInstaller@1 @@ -77,7 +82,7 @@ extends: - task: DotNetCoreCLI@2 displayName: 'build' inputs: - projects: '$(Build.SourcesDirectory)\Microsoft.OpenApi.sln' + projects: '$(Build.SourcesDirectory)\Microsoft.OpenApi.slnx' arguments: '--configuration $(BuildConfiguration) --no-incremental' # Run the Unit test @@ -85,7 +90,7 @@ extends: displayName: 'test' inputs: command: test - projects: '$(Build.SourcesDirectory)\Microsoft.OpenApi.sln' + projects: '$(Build.SourcesDirectory)\Microsoft.OpenApi.slnx' arguments: '--configuration $(BuildConfiguration) --no-build' - task: EsrpCodeSigning@5 diff --git a/.github/workflows/auto-merge-dependabot.yml b/.github/workflows/auto-merge-dependabot.yml index 62a99a480..9d9039433 100644 --- a/.github/workflows/auto-merge-dependabot.yml +++ b/.github/workflows/auto-merge-dependabot.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v3.0.0 + uses: dependabot/fetch-metadata@v3.1.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index df49f2842..b9d453d2a 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -13,11 +13,16 @@ jobs: ARTIFACTS_FOLDER: ${{ github.workspace }}/Artifacts GITHUB_RUN_NUMBER: ${{ github.run_number }} steps: - - name: Setup .NET + - name: Setup .NET 8 uses: actions/setup-dotnet@v5 with: dotnet-version: 8.x + - name: Setup .NET 10 + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + - name: Checkout repository id: checkout_repo uses: actions/checkout@v6 @@ -29,13 +34,13 @@ jobs: id: build_projects shell: pwsh run: | - dotnet build Microsoft.OpenApi.sln -c Release + dotnet build Microsoft.OpenApi.slnx -c Release - name: Run unit tests id: run_unit_tests shell: pwsh run: | - dotnet test Microsoft.OpenApi.sln -c Release -v n + dotnet test Microsoft.OpenApi.slnx -c Release -v n validate-trimming: name: Validate Project for Trimming @@ -43,13 +48,18 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Setup .NET + - name: Setup .NET 8 uses: actions/setup-dotnet@v5 with: dotnet-version: 8.x + - name: Setup .NET 10 + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + - name: Validate Trimming warnings - run: dotnet publish -c Release -r win-x64 /p:TreatWarningsAsErrors=true /warnaserror -f net8.0 + run: dotnet publish -c Release -r win-x64 /p:TreatWarningsAsErrors=true /warnaserror -f net10.0 working-directory: ./test/Microsoft.OpenApi.Trimming.Tests validate-performance: @@ -60,11 +70,16 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - - name: Setup .NET + - name: Setup .NET 8 uses: actions/setup-dotnet@v5 with: dotnet-version: 8.x + - name: Setup .NET 10 + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + - name: Copy committed results run: | mkdir -p ./performanceResults diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 30c8acef4..8ae3c621e 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,11 +22,16 @@ jobs: id: checkout_repo uses: actions/checkout@v6 - - name: Setup .NET + - name: Setup .NET 8 uses: actions/setup-dotnet@v5 with: dotnet-version: 8.0.x + - name: Setup .NET 10 + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0.x + - name: Initialize CodeQL id: init_codeql uses: github/codeql-action/init@v4 diff --git a/.github/workflows/release-please-gha.yml b/.github/workflows/release-please-gha.yml index 2b389c678..a6fdc1927 100644 --- a/.github/workflows/release-please-gha.yml +++ b/.github/workflows/release-please-gha.yml @@ -22,7 +22,16 @@ permissions: contents: read jobs: + check-secret: + runs-on: ubuntu-latest + outputs: + has-token: ${{ steps.check.outputs.has-token }} + steps: + - id: check + run: echo "has-token=${{ secrets.RELEASE_PLEASE_TOKEN_PROVIDER_PEM != '' }}" >> $GITHUB_OUTPUT release: + needs: check-secret + if: needs.check-secret.outputs.has-token == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -59,7 +68,7 @@ jobs: private-key: ${{ secrets.RELEASE_PLEASE_TOKEN_PROVIDER_PEM }} - name: Release Please - uses: googleapis/release-please-action@v4 + uses: googleapis/release-please-action@v5 with: token: ${{ steps.app-token.outputs.token }} config-file: release-please-config.json diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 7cd77844c..fe5afd83a 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -39,10 +39,14 @@ jobs: with: distribution: 'adopt' java-version: 17 - - name: Setup .NET + - name: Setup .NET 8 uses: actions/setup-dotnet@v5 with: dotnet-version: 8.0.x + - name: Setup .NET 10 + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0.x - uses: actions/checkout@v6 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis @@ -64,5 +68,5 @@ jobs: dotnet tool run dotnet-sonarscanner begin /k:"microsoft_OpenAPI.NET" /o:"microsoft" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="test/**/coverage.opencover.xml" dotnet workload restore dotnet build - dotnet test Microsoft.OpenApi.sln --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover + dotnet test Microsoft.OpenApi.slnx --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover dotnet tool run dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c6a6955ca..751389532 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.5.2" + ".": "3.5.3" } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d2bd5722f..e68597ddc 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,7 +10,7 @@ "group": "build", "args": [ "build", - "${workspaceFolder}/Microsoft.OpenApi.sln", + "${workspaceFolder}/Microsoft.OpenApi.slnx", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -23,7 +23,7 @@ "group": "test", "args": [ "test", - "${workspaceFolder}/Microsoft.OpenApi.sln", + "${workspaceFolder}/Microsoft.OpenApi.slnx", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary", "--collect:\"XPlat Code Coverage\"" diff --git a/CHANGELOG.md b/CHANGELOG.md index a0a645e45..486479069 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [3.5.3](https://github.com/microsoft/OpenAPI.NET/compare/v3.5.2...v3.5.3) (2026-04-27) + + +### Bug Fixes + +* null reference exception for boolean component schemas ([f97f91a](https://github.com/microsoft/OpenAPI.NET/commit/f97f91a9f785b88039968024d5d1f4af51332631)) +* null reference exception for boolean component schemas ([fe0b50a](https://github.com/microsoft/OpenAPI.NET/commit/fe0b50aae372e5b08625f888ae424f22da759cdf)) +* **schema:** support boolean schemas in deserializer for OpenAPI 3.1/3.2 ([05b44be](https://github.com/microsoft/OpenAPI.NET/commit/05b44beb9783f5bab1d8988b3cb916d0278d42ce)) + + +### Performance Improvements + +* **schema:** optimize boolean schema deserialization ([7316e3f](https://github.com/microsoft/OpenAPI.NET/commit/7316e3f0e7b9acc11bc1e0255d7ca33837a1bbdc)) + ## [3.5.2](https://github.com/microsoft/OpenAPI.NET/compare/v3.5.1...v3.5.2) (2026-04-14) diff --git a/Directory.Build.props b/Directory.Build.props index 63a6cf699..86e5a6bf9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,13 +12,13 @@ https://github.com/Microsoft/OpenAPI.NET © Microsoft Corporation. All rights reserved. OpenAPI .NET - 3.5.2 + 3.5.3 true - + \ No newline at end of file diff --git a/Microsoft.OpenApi.sln b/Microsoft.OpenApi.sln deleted file mode 100644 index b91029eca..000000000 --- a/Microsoft.OpenApi.sln +++ /dev/null @@ -1,103 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.1.32210.238 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi", "src\Microsoft.OpenApi\Microsoft.OpenApi.csproj", "{A8E50143-69B2-472A-9D45-3F9A05D13202}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4AEDAD90-F854-4940-BFEE-6374CC92CAB0}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - build.cmd = build.cmd - readme.md = readme.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.Workbench", "src\Microsoft.OpenApi.Workbench\Microsoft.OpenApi.Workbench.csproj", "{6A5E91E5-0441-46EE-AEB9-8334981B7F08}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.YamlReader", "src\Microsoft.OpenApi.YamlReader\Microsoft.OpenApi.YamlReader.csproj", "{79933258-0126-4382-8755-D50820ECC483}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.Tests", "test\Microsoft.OpenApi.Tests\Microsoft.OpenApi.Tests.csproj", "{AD83F991-DBF3-4251-8613-9CC54C826964}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.Readers.Tests", "test\Microsoft.OpenApi.Readers.Tests\Microsoft.OpenApi.Readers.Tests.csproj", "{1ED3C2C1-E1E7-4925-B4E6-2D969C3F5237}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E546B92F-20A8-49C3-8323-4B25BB78F3E1}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6357D7FD-2DE4-4900-ADB9-ABC37052040A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OpenApi.Trimming.Tests", "test\Microsoft.OpenApi.Trimming.Tests\Microsoft.OpenApi.Trimming.Tests.csproj", "{1D2E0C6E-B103-4CB6-912E-D56FA1501296}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OpenApi.Hidi", "src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj", "{538936B4-5E14-4EA3-9FD0-F43E2DD014FB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OpenApi.Hidi.Tests", "test\Microsoft.OpenApi.Hidi.Tests\Microsoft.OpenApi.Hidi.Tests.csproj", "{6ADC5D41-EDD2-4206-B815-5DFF739C6832}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PerformanceTests", "performance\benchmark\PerformanceTests.csproj", "{537E49E3-325E-40EE-A90E-7556D4D333AA}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "performance", "performance", "{4BB7E3F7-CA7E-45D3-B5AC-5DBB510FD528}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "resultsComparer", "performance\resultsComparer\resultsComparer.csproj", "{5EEA836B-3E08-4BE1-82B8-5236D031B497}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A8E50143-69B2-472A-9D45-3F9A05D13202}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A8E50143-69B2-472A-9D45-3F9A05D13202}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A8E50143-69B2-472A-9D45-3F9A05D13202}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A8E50143-69B2-472A-9D45-3F9A05D13202}.Release|Any CPU.Build.0 = Release|Any CPU - {6A5E91E5-0441-46EE-AEB9-8334981B7F08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6A5E91E5-0441-46EE-AEB9-8334981B7F08}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A5E91E5-0441-46EE-AEB9-8334981B7F08}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6A5E91E5-0441-46EE-AEB9-8334981B7F08}.Release|Any CPU.Build.0 = Release|Any CPU - {79933258-0126-4382-8755-D50820ECC483}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {79933258-0126-4382-8755-D50820ECC483}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79933258-0126-4382-8755-D50820ECC483}.Release|Any CPU.ActiveCfg = Release|Any CPU - {79933258-0126-4382-8755-D50820ECC483}.Release|Any CPU.Build.0 = Release|Any CPU - {AD83F991-DBF3-4251-8613-9CC54C826964}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AD83F991-DBF3-4251-8613-9CC54C826964}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AD83F991-DBF3-4251-8613-9CC54C826964}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AD83F991-DBF3-4251-8613-9CC54C826964}.Release|Any CPU.Build.0 = Release|Any CPU - {1ED3C2C1-E1E7-4925-B4E6-2D969C3F5237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1ED3C2C1-E1E7-4925-B4E6-2D969C3F5237}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1ED3C2C1-E1E7-4925-B4E6-2D969C3F5237}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1ED3C2C1-E1E7-4925-B4E6-2D969C3F5237}.Release|Any CPU.Build.0 = Release|Any CPU - {1D2E0C6E-B103-4CB6-912E-D56FA1501296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1D2E0C6E-B103-4CB6-912E-D56FA1501296}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1D2E0C6E-B103-4CB6-912E-D56FA1501296}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1D2E0C6E-B103-4CB6-912E-D56FA1501296}.Release|Any CPU.Build.0 = Release|Any CPU - {538936B4-5E14-4EA3-9FD0-F43E2DD014FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {538936B4-5E14-4EA3-9FD0-F43E2DD014FB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {538936B4-5E14-4EA3-9FD0-F43E2DD014FB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {538936B4-5E14-4EA3-9FD0-F43E2DD014FB}.Release|Any CPU.Build.0 = Release|Any CPU - {6ADC5D41-EDD2-4206-B815-5DFF739C6832}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6ADC5D41-EDD2-4206-B815-5DFF739C6832}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6ADC5D41-EDD2-4206-B815-5DFF739C6832}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6ADC5D41-EDD2-4206-B815-5DFF739C6832}.Release|Any CPU.Build.0 = Release|Any CPU - {537E49E3-325E-40EE-A90E-7556D4D333AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {537E49E3-325E-40EE-A90E-7556D4D333AA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {537E49E3-325E-40EE-A90E-7556D4D333AA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {537E49E3-325E-40EE-A90E-7556D4D333AA}.Release|Any CPU.Build.0 = Release|Any CPU - {5EEA836B-3E08-4BE1-82B8-5236D031B497}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5EEA836B-3E08-4BE1-82B8-5236D031B497}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5EEA836B-3E08-4BE1-82B8-5236D031B497}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5EEA836B-3E08-4BE1-82B8-5236D031B497}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {A8E50143-69B2-472A-9D45-3F9A05D13202} = {E546B92F-20A8-49C3-8323-4B25BB78F3E1} - {6A5E91E5-0441-46EE-AEB9-8334981B7F08} = {E546B92F-20A8-49C3-8323-4B25BB78F3E1} - {79933258-0126-4382-8755-D50820ECC483} = {E546B92F-20A8-49C3-8323-4B25BB78F3E1} - {AD83F991-DBF3-4251-8613-9CC54C826964} = {6357D7FD-2DE4-4900-ADB9-ABC37052040A} - {1ED3C2C1-E1E7-4925-B4E6-2D969C3F5237} = {6357D7FD-2DE4-4900-ADB9-ABC37052040A} - {1D2E0C6E-B103-4CB6-912E-D56FA1501296} = {6357D7FD-2DE4-4900-ADB9-ABC37052040A} - {538936B4-5E14-4EA3-9FD0-F43E2DD014FB} = {E546B92F-20A8-49C3-8323-4B25BB78F3E1} - {6ADC5D41-EDD2-4206-B815-5DFF739C6832} = {6357D7FD-2DE4-4900-ADB9-ABC37052040A} - {5EEA836B-3E08-4BE1-82B8-5236D031B497} = {4BB7E3F7-CA7E-45D3-B5AC-5DBB510FD528} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9F171EFC-0DB5-4B10-ABFA-AF48D52CC565} - EndGlobalSection -EndGlobal diff --git a/Microsoft.OpenApi.slnx b/Microsoft.OpenApi.slnx new file mode 100644 index 000000000..f764776bf --- /dev/null +++ b/Microsoft.OpenApi.slnx @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 6a7f787f8..3c77f909f 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ In order to test the validity of an OpenApi document, we avail the following too #### Installation guidelines: 1. Clone the repo locally by running this command: `git clone https://github.com/microsoft/OpenAPI.NET.git` - 2. Open the solution file `(.sln)` in the root of the project with Visual Studio + 2. Open the solution file `(.slnx)` in the root of the project with Visual Studio 3. Navigate to the `src/Microsoft.OpenApi.Workbench` directory and set it as the startup project 4. Run the project and you'll see a GUI pop up resembling the one below: diff --git a/global.json b/global.json index d72c3f669..f234262d9 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0.419" + "version": "10.0.203" } } \ No newline at end of file diff --git a/performance/resultsComparer/resultsComparer.csproj b/performance/resultsComparer/resultsComparer.csproj index 847f570d5..eeeb0c683 100644 --- a/performance/resultsComparer/resultsComparer.csproj +++ b/performance/resultsComparer/resultsComparer.csproj @@ -8,12 +8,12 @@ - - - - + + + + - + diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index d856e9487..eb6a089b6 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -29,10 +29,10 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 11252d3c4..40f24bdd0 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -11,6 +11,12 @@ namespace Microsoft.OpenApi { /// /// The Schema Object allows the definition of input and output data types. + /// + /// For OpenAPI 3.1+ (JSON Schema 2020-12), this class supports boolean schemas: + /// - Deserialization: The boolean literal true deserializes to an empty schema (allows any value). + /// The boolean literal false deserializes to a schema with set to an empty schema (disallows any value). + /// - Serialization: To produce something functionally equivalent to boolean schemas, create an empty + /// for "true" behavior, or create a schema with only set to an empty schema for "false" behavior. /// public class OpenApiSchema : IOpenApiExtensible, IOpenApiSchema, IOpenApiSchemaWithUnevaluatedProperties, IMetadataContainer { diff --git a/src/Microsoft.OpenApi/Reader/ParseNodes/ValueNode.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/ValueNode.cs index a25a6275e..bf207d741 100644 --- a/src/Microsoft.OpenApi/Reader/ParseNodes/ValueNode.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/ValueNode.cs @@ -28,6 +28,17 @@ public override string GetScalarValue() ?? throw new OpenApiReaderException($"Expected a value at {Context.GetLocation()}."); } + /// + /// Attempts to get the underlying value directly as the specified type without string conversion. + /// + /// The type to retrieve the value as. + /// The retrieved value if successful. + /// True if the value was successfully converted to the specified type; otherwise, false. + public bool TryGetValue(out T? value) + { + return _node.TryGetValue(out value); + } + /// /// Create a /// diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs index ee1ca1832..39c32c979 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -362,6 +362,19 @@ internal static partial class OpenApiV31Deserializer public static IOpenApiSchema LoadSchema(ParseNode node, OpenApiDocument hostDocument) { + // Handle boolean schemas (true/false) for JSON Schema 2020-12 compatibility + if (node is ValueNode valueNode && valueNode.TryGetValue(out var boolValue)) + { + var boolSchema = new OpenApiSchema(); + if (!boolValue) + { + // false schema: represents "not valid" -> convert to "not: {}" + boolSchema.Not = new OpenApiSchema(); + } + // true schema: represents "always valid" -> return empty schema (default) + return boolSchema; + } + var mapNode = node.CheckMapNode(OpenApiConstants.Schema); var pointer = mapNode.GetReferencePointer(); diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs index 5dd32b413..c18c48462 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs @@ -362,6 +362,19 @@ internal static partial class OpenApiV32Deserializer public static IOpenApiSchema LoadSchema(ParseNode node, OpenApiDocument hostDocument) { + // Handle boolean schemas (true/false) for JSON Schema 2020-12 compatibility + if (node is ValueNode valueNode && valueNode.TryGetValue(out var boolValue)) + { + var boolSchema = new OpenApiSchema(); + if (!boolValue) + { + // false schema: represents "not valid" -> convert to "not: {}" + boolSchema.Not = new OpenApiSchema(); + } + // true schema: represents "always valid" -> return empty schema (default) + return boolSchema; + } + var mapNode = node.CheckMapNode(OpenApiConstants.Schema); var pointer = mapNode.GetReferencePointer(); diff --git a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs index 14c8d2ee4..22128adc2 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs @@ -93,6 +93,7 @@ public void RegisterComponents(OpenApiDocument document) { foreach (var item in document.Components.Schemas) { + if (item.Value == null) continue; location = item.Value.Id ?? baseUri + ReferenceType.Schema.GetDisplayName() + ComponentSegmentSeparator + item.Key; RegisterComponent(location, item.Value); } @@ -103,6 +104,7 @@ public void RegisterComponents(OpenApiDocument document) { foreach (var item in document.Components.Parameters) { + if (item.Value == null) continue; location = baseUri + ReferenceType.Parameter.GetDisplayName() + ComponentSegmentSeparator + item.Key; RegisterComponent(location, item.Value); } @@ -113,6 +115,7 @@ public void RegisterComponents(OpenApiDocument document) { foreach (var item in document.Components.Responses) { + if (item.Value == null) continue; location = baseUri + ReferenceType.Response.GetDisplayName() + ComponentSegmentSeparator + item.Key; RegisterComponent(location, item.Value); } @@ -123,6 +126,7 @@ public void RegisterComponents(OpenApiDocument document) { foreach (var item in document.Components.RequestBodies) { + if (item.Value == null) continue; location = baseUri + ReferenceType.RequestBody.GetDisplayName() + ComponentSegmentSeparator + item.Key; RegisterComponent(location, item.Value); } @@ -133,6 +137,7 @@ public void RegisterComponents(OpenApiDocument document) { foreach (var item in document.Components.Links) { + if (item.Value == null) continue; location = baseUri + ReferenceType.Link.GetDisplayName() + ComponentSegmentSeparator + item.Key; RegisterComponent(location, item.Value); } @@ -143,6 +148,7 @@ public void RegisterComponents(OpenApiDocument document) { foreach (var item in document.Components.Callbacks) { + if (item.Value == null) continue; location = baseUri + ReferenceType.Callback.GetDisplayName() + ComponentSegmentSeparator + item.Key; RegisterComponent(location, item.Value); } @@ -153,6 +159,7 @@ public void RegisterComponents(OpenApiDocument document) { foreach (var item in document.Components.PathItems) { + if (item.Value == null) continue; location = baseUri + ReferenceType.PathItem.GetDisplayName() + ComponentSegmentSeparator + item.Key; RegisterComponent(location, item.Value); } @@ -163,6 +170,7 @@ public void RegisterComponents(OpenApiDocument document) { foreach (var item in document.Components.Examples) { + if (item.Value == null) continue; location = baseUri + ReferenceType.Example.GetDisplayName() + ComponentSegmentSeparator + item.Key; RegisterComponent(location, item.Value); } @@ -173,6 +181,7 @@ public void RegisterComponents(OpenApiDocument document) { foreach (var item in document.Components.Headers) { + if (item.Value == null) continue; location = baseUri + ReferenceType.Header.GetDisplayName() + ComponentSegmentSeparator + item.Key; RegisterComponent(location, item.Value); } @@ -183,6 +192,7 @@ public void RegisterComponents(OpenApiDocument document) { foreach (var item in document.Components.SecuritySchemes) { + if (item.Value == null) continue; location = baseUri + ReferenceType.SecurityScheme.GetDisplayName() + ComponentSegmentSeparator + item.Key; RegisterComponent(location, item.Value); } @@ -193,6 +203,7 @@ public void RegisterComponents(OpenApiDocument document) { foreach (var item in document.Components.MediaTypes) { + if (item.Value == null) continue; location = baseUri + ReferenceType.MediaType.GetDisplayName() + ComponentSegmentSeparator + item.Key; RegisterComponent(location, item.Value); } diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj index 76afffd97..2ff261d63 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj +++ b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable @@ -13,12 +13,12 @@ - + - + diff --git a/test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs b/test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs index 4654b43b6..535b43a80 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs +++ b/test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs @@ -6,7 +6,7 @@ namespace Microsoft.OpenApi.Tests.UtilityFiles /// /// Mock class that creates a sample OpenAPI document. /// - public static class OpenApiDocumentMock + internal static class OpenApiDocumentMock { /// /// Creates an OpenAPI document. diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index 7bac31d10..370e1b372 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0 false true ..\..\src\Microsoft.OpenApi.snk @@ -15,11 +15,10 @@ - - + + - @@ -30,4 +29,4 @@ - \ No newline at end of file + diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs index 28085736c..8acf84d09 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -630,5 +630,23 @@ public async Task ParseDocumentWithSelfExtensionWorks() Assert.Empty(result.Diagnostic.Errors); Assert.Empty(result.Diagnostic.Warnings); } + + [Fact] + public void LoadDocumentWithBooleanSchemaShouldNotThrowNullReferenceException() + { + // Arrange - OpenAPI 3.1 with a boolean schema in components/schemas (spec-valid per JSON Schema 2020-12) + var bytes = "{\"openapi\":\"3.1.0\",\"components\":{\"schemas\":{\"X\":true}}}"u8.ToArray(); + using var ms = new MemoryStream(bytes); + + // Act & Assert - should not throw NullReferenceException + var exception = Record.Exception(() => OpenApiDocument.Load(ms, format: null, new OpenApiReaderSettings())); + + // The parser should handle the boolean schema gracefully + // Either accepting it or surfacing a structured diagnostic, but not throwing NullReferenceException + if (exception != null) + { + Assert.IsNotType(exception); + } + } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs index 7ed518b95..b65b2d8c3 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs @@ -844,5 +844,34 @@ public void ParseSchemaWithoutUnevaluatedPropertiesDefaultsToTrue() Assert.Equivalent(expected, actual); Assert.True(actual.UnevaluatedProperties); // Explicitly verify the default } + + [Theory] + [InlineData("{}")] + [InlineData("true")] + public void DeserializeTrueSchemaParsesAsEmptySchema(string schemaSource) + { + // Arrange & Act + var schema = OpenApiModelFactory.Parse(schemaSource, OpenApiSpecVersion.OpenApi3_1, new(), out _, OpenApiConstants.Json); + + // Assert - schema should deserialize without error + Assert.NotNull(schema); + } + + [Fact] + public void DeserializeFalseSchemaParsesAsNotEmptySchema() + { + // Arrange + var schemaSource = "false"; + + // Act + var schema = OpenApiModelFactory.Parse(schemaSource, OpenApiSpecVersion.OpenApi3_1, new(), out _, OpenApiConstants.Json); + + // Assert - false schema should deserialize to not: {} + Assert.NotNull(schema); + Assert.NotNull(schema.Not); + Assert.Empty(schema.Not.AnyOf ?? []); + Assert.Empty(schema.Not.AllOf ?? []); + Assert.Empty(schema.Not.OneOf ?? []); + } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs index ed868a913..80051f05c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs @@ -645,6 +645,24 @@ public async Task ParseDocumentWithSelfPropertyFromJsonWorks() Assert.Empty(result.Diagnostic.Errors); Assert.Empty(result.Diagnostic.Warnings); } + + [Fact] + public void LoadDocumentWithBooleanSchemaShouldNotThrowNullReferenceException() + { + // Arrange - OpenAPI 3.2 with a boolean schema in components/schemas (spec-valid per JSON Schema 2020-12) + var bytes = "{\"openapi\":\"3.2.0\",\"components\":{\"schemas\":{\"X\":true}}}"u8.ToArray(); + using var ms = new MemoryStream(bytes); + + // Act & Assert - should not throw NullReferenceException + var exception = Record.Exception(() => OpenApiDocument.Load(ms, format: null, new OpenApiReaderSettings())); + + // The parser should handle the boolean schema gracefully + // Either accepting it or surfacing a structured diagnostic, but not throwing NullReferenceException + if (exception != null) + { + Assert.IsNotType(exception); + } + } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs index 286a0baf8..7f91b0327 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System.Collections.Generic; @@ -695,6 +695,35 @@ public void ParseSchemaWithUnevaluatedPropertiesComplexSchema() // Assert Assert.Equivalent(expected, actual); } + + [Theory] + [InlineData("{}")] + [InlineData("true")] + public void DeserializeTrueSchemaParsesAsEmptySchema(string schemaSource) + { + // Arrange & Act + var schema = OpenApiModelFactory.Parse(schemaSource, OpenApiSpecVersion.OpenApi3_2, new(), out _, OpenApiConstants.Json); + + // Assert - schema should deserialize without error + Assert.NotNull(schema); + } + + [Fact] + public void DeserializeFalseSchemaParsesAsNotEmptySchema() + { + // Arrange + var schemaSource = "false"; + + // Act + var schema = OpenApiModelFactory.Parse(schemaSource, OpenApiSpecVersion.OpenApi3_2, new(), out _, OpenApiConstants.Json); + + // Assert - false schema should deserialize to not: {} + Assert.NotNull(schema); + Assert.NotNull(schema.Not); + Assert.Empty(schema.Not.AnyOf ?? []); + Assert.Empty(schema.Not.AllOf ?? []); + Assert.Empty(schema.Not.OneOf ?? []); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index c4d967bde..41666803a 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0 false true Library @@ -8,16 +8,14 @@ - - + + - - @@ -67,4 +65,4 @@ - \ No newline at end of file + diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 64874fb37..40034eded 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -204,14 +204,18 @@ public class OpenApiSchemaTests } }; - [Fact] - public async Task SerializeBasicSchemaAsV3JsonWorks() + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi2_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_1)] + [InlineData(OpenApiSpecVersion.OpenApi3_2)] + public async Task SerializeBasicSchemaAsJsonWorks(OpenApiSpecVersion version) { // Arrange var expected = @"{ }"; // Act - var actual = await BasicSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await BasicSchema.SerializeAsJsonAsync(version); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); diff --git a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj index 20d8d8d70..a6a858526 100644 --- a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj +++ b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj @@ -1,7 +1,7 @@  Exe - net8.0 + net10.0 enable enable true