diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bc38da9f39..0a16709d8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,9 +10,9 @@ name: Build on: push: - branches: [ 'master', 'release/**' ] + branches: [ 'master', 'release/**', 'net10-preview' ] pull_request: - branches: [ 'master', 'release/**' ] + branches: [ 'master', 'release/**', 'net10-preview' ] release: types: [published] @@ -48,6 +48,11 @@ jobs: dotnet-version: | 8.0.* 9.0.* + - name: Setup .NET 10 preview + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.* + dotnet-quality: 'preview' - name: Show installed versions shell: pwsh run: | @@ -166,6 +171,11 @@ jobs: dotnet-version: | 8.0.* 9.0.* + - name: Setup .NET 10 preview + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.* + dotnet-quality: 'preview' - name: Git checkout uses: actions/checkout@v4 - name: Restore tools @@ -221,6 +231,11 @@ jobs: dotnet-version: | 8.0.* 9.0.* + - name: Setup .NET 10 preview + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.* + dotnet-quality: 'preview' - name: Git checkout uses: actions/checkout@v4 with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 508d210158..455071b122 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -29,6 +29,11 @@ jobs: dotnet-version: | 8.0.* 9.0.* + - name: Setup .NET 10 preview + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.* + dotnet-quality: 'preview' - name: Git checkout uses: actions/checkout@v4 - name: Initialize CodeQL diff --git a/Directory.Build.props b/Directory.Build.props index 1ef255f56e..4664c01565 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -15,6 +15,12 @@ direct + + + true + $(NoWarn);NU1903;NU5104;NU1608 + + + 10.0.*-* + 9.0.0-pr.3283.* + + + 10.0.*-* + 9.0.0-pr.3283.* + 2.0.0-preview.21 + 10.0.*-* + 10.0.*-* + 9.0.*-* + + N/A diff --git a/src/Examples/DapperExample/DapperExample.csproj b/src/Examples/DapperExample/DapperExample.csproj index ed7bd358eb..b2c71ee0f8 100644 --- a/src/Examples/DapperExample/DapperExample.csproj +++ b/src/Examples/DapperExample/DapperExample.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 @@ -14,6 +14,7 @@ + diff --git a/src/Examples/DatabasePerTenantExample/DatabasePerTenantExample.csproj b/src/Examples/DatabasePerTenantExample/DatabasePerTenantExample.csproj index 3edc993428..c7cee804b1 100644 --- a/src/Examples/DatabasePerTenantExample/DatabasePerTenantExample.csproj +++ b/src/Examples/DatabasePerTenantExample/DatabasePerTenantExample.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Examples/GettingStarted/GettingStarted.csproj b/src/Examples/GettingStarted/GettingStarted.csproj index 611aeb37a5..806fdf45a5 100644 --- a/src/Examples/GettingStarted/GettingStarted.csproj +++ b/src/Examples/GettingStarted/GettingStarted.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj index 768a2de827..319c72b262 100644 --- a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj +++ b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0 true GeneratedSwagger diff --git a/src/Examples/JsonApiDotNetCoreExample/SetOpenApiServerAtBuildTimeFilter.cs b/src/Examples/JsonApiDotNetCoreExample/SetOpenApiServerAtBuildTimeFilter.cs index 894c0d0966..e790f70b6a 100644 --- a/src/Examples/JsonApiDotNetCoreExample/SetOpenApiServerAtBuildTimeFilter.cs +++ b/src/Examples/JsonApiDotNetCoreExample/SetOpenApiServerAtBuildTimeFilter.cs @@ -16,6 +16,8 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { if (_httpContextAccessor.HttpContext == null) { + swaggerDoc.Servers ??= []; + swaggerDoc.Servers.Add(new OpenApiServer { Url = "https://localhost:44340" diff --git a/src/Examples/MultiDbContextExample/MultiDbContextExample.csproj b/src/Examples/MultiDbContextExample/MultiDbContextExample.csproj index 611aeb37a5..806fdf45a5 100644 --- a/src/Examples/MultiDbContextExample/MultiDbContextExample.csproj +++ b/src/Examples/MultiDbContextExample/MultiDbContextExample.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj b/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj index 15a485c08f..e50874f733 100644 --- a/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj +++ b/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Examples/OpenApiKiotaClientExample/OpenApiKiotaClientExample.csproj b/src/Examples/OpenApiKiotaClientExample/OpenApiKiotaClientExample.csproj index 8f65b2b688..8dd3e2e4df 100644 --- a/src/Examples/OpenApiKiotaClientExample/OpenApiKiotaClientExample.csproj +++ b/src/Examples/OpenApiKiotaClientExample/OpenApiKiotaClientExample.csproj @@ -1,6 +1,6 @@ - net9.0 + net10.0 diff --git a/src/Examples/OpenApiNSwagClientExample/OpenApiNSwagClientExample.csproj b/src/Examples/OpenApiNSwagClientExample/OpenApiNSwagClientExample.csproj index c30833a39a..079e43121c 100644 --- a/src/Examples/OpenApiNSwagClientExample/OpenApiNSwagClientExample.csproj +++ b/src/Examples/OpenApiNSwagClientExample/OpenApiNSwagClientExample.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 diff --git a/src/Examples/ReportsExample/ReportsExample.csproj b/src/Examples/ReportsExample/ReportsExample.csproj index 6ade1386be..73a16ddf4f 100644 --- a/src/Examples/ReportsExample/ReportsExample.csproj +++ b/src/Examples/ReportsExample/ReportsExample.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj b/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj index ed36e0797c..68bd9ac569 100644 --- a/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj +++ b/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj @@ -1,6 +1,6 @@ - net8.0;netstandard1.0 + net10.0;net8.0;netstandard1.0 true true JsonApiDotNetCore diff --git a/src/JsonApiDotNetCore.OpenApi.Client.Kiota/JsonApiDotNetCore.OpenApi.Client.Kiota.csproj b/src/JsonApiDotNetCore.OpenApi.Client.Kiota/JsonApiDotNetCore.OpenApi.Client.Kiota.csproj index 640b949477..fce1fb7c40 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client.Kiota/JsonApiDotNetCore.OpenApi.Client.Kiota.csproj +++ b/src/JsonApiDotNetCore.OpenApi.Client.Kiota/JsonApiDotNetCore.OpenApi.Client.Kiota.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0;net8.0 true true false diff --git a/src/JsonApiDotNetCore.OpenApi.Client.NSwag/JsonApiDotNetCore.OpenApi.Client.NSwag.csproj b/src/JsonApiDotNetCore.OpenApi.Client.NSwag/JsonApiDotNetCore.OpenApi.Client.NSwag.csproj index 20e2306730..550f284509 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client.NSwag/JsonApiDotNetCore.OpenApi.Client.NSwag.csproj +++ b/src/JsonApiDotNetCore.OpenApi.Client.NSwag/JsonApiDotNetCore.OpenApi.Client.NSwag.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0;net8.0 true true false diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/ConfigureSwaggerGenOptions.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/ConfigureSwaggerGenOptions.cs index f3fb5198ca..200aa29ac7 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/ConfigureSwaggerGenOptions.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/ConfigureSwaggerGenOptions.cs @@ -74,6 +74,8 @@ public void Configure(SwaggerGenOptions options) options.DocumentFilter(); options.DocumentFilter(); options.DocumentFilter(); + options.DocumentFilter(); + options.DocumentFilter(); } private List SelectDerivedTypes(Type baseType) diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/JsonApiDotNetCore.OpenApi.Swashbuckle.csproj b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/JsonApiDotNetCore.OpenApi.Swashbuckle.csproj index 4a57ca1c85..b8be841a34 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/JsonApiDotNetCore.OpenApi.Swashbuckle.csproj +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/JsonApiDotNetCore.OpenApi.Swashbuckle.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0 true true false @@ -33,6 +33,7 @@ + diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/JsonApiSchemaIdSelector.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/JsonApiSchemaIdSelector.cs index 0e2fc803de..fc0cec1974 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/JsonApiSchemaIdSelector.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/JsonApiSchemaIdSelector.cs @@ -120,7 +120,7 @@ public string GetSchemaId(Type type) private string ApplySchemaTemplate(string schemaTemplate, ResourceType? resourceType, string? relationshipName, AtomicOperationCode? operationCode) { - string schemaId = schemaTemplate; + string? schemaId = schemaTemplate; schemaId = resourceType != null ? schemaId.Replace("[ResourceName]", resourceType.PublicName.Singularize()).Pascalize() @@ -136,7 +136,7 @@ private string ApplySchemaTemplate(string schemaTemplate, ResourceType? resource schemaId = schemaId.Replace("[OperationCode]", operationCode.Value.ToString().Pascalize()); } - string pascalCaseSchemaId = schemaId.Pascalize(); + string? pascalCaseSchemaId = schemaId.Pascalize(); JsonNamingPolicy? namingPolicy = _options.SerializerOptions.PropertyNamingPolicy; return namingPolicy != null ? namingPolicy.ConvertName(pascalCaseSchemaId) : pascalCaseSchemaId; diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/MicrosoftOpenApiCompatibilityExtensions.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/MicrosoftOpenApiCompatibilityExtensions.cs new file mode 100644 index 0000000000..176d5cba82 --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/MicrosoftOpenApiCompatibilityExtensions.cs @@ -0,0 +1,24 @@ +using Microsoft.OpenApi.Models; + +namespace JsonApiDotNetCore.OpenApi.Swashbuckle; + +internal static class MicrosoftOpenApiCompatibilityExtensions +{ + public static void SetNullable(this OpenApiSchema schema, bool nullable) + { + ArgumentNullException.ThrowIfNull(schema); + + if (nullable) + { + schema.Type ??= JsonSchemaType.Null; + schema.Type |= JsonSchemaType.Null; + } + else + { + if (schema.Type != null) + { + schema.Type &= ~JsonSchemaType.Null; + } + } + } +} diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiDescriptionLinkProvider.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiDescriptionLinkProvider.cs index 278c2154a9..2638711d1c 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiDescriptionLinkProvider.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiDescriptionLinkProvider.cs @@ -30,7 +30,7 @@ public OpenApiDescriptionLinkProvider(IOptionsMonitor s if (swaggerGeneratorOptions.SwaggerDocs.Count > 0) { - string latestVersionDocumentName = swaggerGeneratorOptions.SwaggerDocs.Last().Key; + string? latestVersionDocumentName = swaggerGeneratorOptions.SwaggerDocs.Last().Key; SwaggerOptions swaggerOptions = _swaggerOptionsMonitor.CurrentValue; return swaggerOptions.RouteTemplate.Replace("{documentName}", latestVersionDocumentName).Replace("{extension:regex(^(json|ya?ml)$)}", "json"); diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiOperationIdSelector.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiOperationIdSelector.cs index ed11481e27..550b8b599b 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiOperationIdSelector.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiOperationIdSelector.cs @@ -94,7 +94,7 @@ private string ApplyTemplate(string openApiOperationIdTemplate, ResourceType? re // @formatter:wrap_chained_method_calls chop_always // @formatter:wrap_before_first_method_call true - string pascalCaseOpenApiOperationId = openApiOperationIdTemplate + string? pascalCaseOpenApiOperationId = openApiOperationIdTemplate .Replace("[Method]", method) .Replace("[PrimaryResourceName]", resourceType?.PublicName.Singularize()) .Replace("[RelationshipName]", relationshipName) diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiSchemaExtensions.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiSchemaExtensions.cs index 10095834b2..78416f6f76 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiSchemaExtensions.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiSchemaExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; namespace JsonApiDotNetCore.OpenApi.Swashbuckle; @@ -9,22 +10,23 @@ public static void ReorderProperties(this OpenApiSchema fullSchema, IEnumerable< ArgumentNullException.ThrowIfNull(fullSchema); ArgumentNullException.ThrowIfNull(propertyNamesInOrder); - var propertiesInOrder = new Dictionary(); + var propertiesInOrder = new Dictionary(); foreach (string propertyName in propertyNamesInOrder) { - if (fullSchema.Properties.TryGetValue(propertyName, out OpenApiSchema? schema)) + if (fullSchema.Properties != null && fullSchema.Properties.TryGetValue(propertyName, out IOpenApiSchema? schema)) { propertiesInOrder.Add(propertyName, schema); } } + ConsistencyGuard.ThrowIf(fullSchema.Properties == null); ConsistencyGuard.ThrowIf(fullSchema.Properties.Count != propertiesInOrder.Count); fullSchema.Properties = propertiesInOrder; } - public static OpenApiSchema WrapInExtendedSchema(this OpenApiSchema source) + public static OpenApiSchema WrapInExtendedSchema(this IOpenApiSchema source) { ArgumentNullException.ThrowIfNull(source); @@ -34,11 +36,11 @@ public static OpenApiSchema WrapInExtendedSchema(this OpenApiSchema source) }; } - public static OpenApiSchema UnwrapLastExtendedSchema(this OpenApiSchema source) + public static IOpenApiSchema UnwrapLastExtendedSchema(this IOpenApiSchema source) { ArgumentNullException.ThrowIfNull(source); - if (source.AllOf is { Count: > 0 }) + if (source is OpenApiSchema && source.AllOf is { Count: > 0 }) { return source.AllOf.Last(); } diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/RemoveTagsFilter.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/RemoveTagsFilter.cs new file mode 100644 index 0000000000..e38f5e5730 --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/RemoveTagsFilter.cs @@ -0,0 +1,14 @@ +using JetBrains.Annotations; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace JsonApiDotNetCore.OpenApi.Swashbuckle; + +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +internal sealed class RemoveTagsFilter : IDocumentFilter +{ + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + swaggerDoc.Tags?.Clear(); + } +} diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/AtomicOperationCodeSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/AtomicOperationCodeSchemaGenerator.cs index 78a25da441..37bb42fe40 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/AtomicOperationCodeSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/AtomicOperationCodeSchemaGenerator.cs @@ -1,6 +1,7 @@ using JsonApiDotNetCore.Serialization.Objects; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; @@ -19,7 +20,7 @@ public AtomicOperationCodeSchemaGenerator(SchemaGenerationTracer schemaGeneratio _schemaIdSelector = schemaIdSelector; } - public OpenApiSchema GenerateSchema(AtomicOperationCode operationCode, SchemaRepository schemaRepository) + public IOpenApiSchema GenerateSchema(AtomicOperationCode operationCode, SchemaRepository schemaRepository) { ArgumentNullException.ThrowIfNull(schemaRepository); @@ -27,14 +28,7 @@ public OpenApiSchema GenerateSchema(AtomicOperationCode operationCode, SchemaRep if (schemaRepository.Schemas.ContainsKey(schemaId)) { - return new OpenApiSchema - { - Reference = new OpenApiReference - { - Id = schemaId, - Type = ReferenceType.Schema - } - }; + return new OpenApiSchemaReference(schemaId); } using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, operationCode); @@ -43,11 +37,11 @@ public OpenApiSchema GenerateSchema(AtomicOperationCode operationCode, SchemaRep var fullSchema = new OpenApiSchema { - Type = "string", - Enum = [new OpenApiString(enumValue)] + Type = JsonSchemaType.String, + Enum = [enumValue] }; - OpenApiSchema referenceSchema = schemaRepository.AddDefinition(schemaId, fullSchema); + OpenApiSchemaReference? referenceSchema = schemaRepository.AddDefinition(schemaId, fullSchema); traceScope.TraceSucceeded(schemaId); return referenceSchema; diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/DataContainerSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/DataContainerSchemaGenerator.cs index c4b41dd0f5..15ac1e84f3 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/DataContainerSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/DataContainerSchemaGenerator.cs @@ -2,7 +2,8 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.OpenApi.Swashbuckle.JsonApiObjects.ResourceObjects; using JsonApiDotNetCore.OpenApi.Swashbuckle.SwaggerComponents; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; @@ -27,21 +28,21 @@ public DataContainerSchemaGenerator(SchemaGenerationTracer schemaGenerationTrace _resourceGraph = resourceGraph; } - public OpenApiSchema GenerateSchemaForCommonResourceDataInResponse(SchemaRepository schemaRepository) + public OpenApiSchemaReference GenerateSchemaForCommonResourceDataInResponse(SchemaRepository schemaRepository) { ArgumentNullException.ThrowIfNull(schemaRepository); return _dataSchemaGenerator.GenerateSchemaForCommonData(typeof(ResourceInResponse), schemaRepository); } - public OpenApiSchema GenerateSchema(Type dataContainerSchemaType, ResourceType resourceType, bool forRequestSchema, bool canIncludeRelated, + public IOpenApiSchema GenerateSchema(Type dataContainerSchemaType, ResourceType resourceType, bool forRequestSchema, bool canIncludeRelated, SchemaRepository schemaRepository) { ArgumentNullException.ThrowIfNull(dataContainerSchemaType); ArgumentNullException.ThrowIfNull(resourceType); ArgumentNullException.ThrowIfNull(schemaRepository); - if (schemaRepository.TryLookupByType(dataContainerSchemaType, out OpenApiSchema referenceSchemaForData)) + if (schemaRepository.TryLookupByType(dataContainerSchemaType, out OpenApiSchemaReference? referenceSchemaForData)) { return referenceSchemaForData; } @@ -68,7 +69,7 @@ public OpenApiSchema GenerateSchema(Type dataContainerSchemaType, ResourceType r } referenceSchemaForData = _dataSchemaGenerator.GenerateSchema(dataConstructedType, forRequestSchema, schemaRepository); - traceScope.TraceSucceeded(referenceSchemaForData.Reference.Id); + traceScope.TraceSucceeded(referenceSchemaForData.Reference.Id!); return referenceSchemaForData; } diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/DataSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/DataSchemaGenerator.cs index 4d7783f780..dedf35e2f2 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/DataSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/DataSchemaGenerator.cs @@ -1,12 +1,17 @@ using System.Collections.Concurrent; using System.Reflection; using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Nodes; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.OpenApi.Swashbuckle.JsonApiMetadata; using JsonApiDotNetCore.OpenApi.Swashbuckle.JsonApiObjects.ResourceObjects; using JsonApiDotNetCore.OpenApi.Swashbuckle.SwaggerComponents; -using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; @@ -79,7 +84,7 @@ public DataSchemaGenerator(SchemaGenerationTracer schemaGenerationTracer, Schema _resourceDocumentationReader = resourceDocumentationReader; } - public OpenApiSchema GenerateSchema(Type dataSchemaType, bool forRequestSchema, SchemaRepository schemaRepository) + public OpenApiSchemaReference GenerateSchema(Type dataSchemaType, bool forRequestSchema, SchemaRepository schemaRepository) { // For a given resource (identifier) type, we always generate the full type hierarchy. Discriminator mappings // are managed manually, because there's no way to intercept in the Swashbuckle recursive component schema generation. @@ -87,7 +92,7 @@ public OpenApiSchema GenerateSchema(Type dataSchemaType, bool forRequestSchema, ArgumentNullException.ThrowIfNull(dataSchemaType); ArgumentNullException.ThrowIfNull(schemaRepository); - if (schemaRepository.TryLookupByType(dataSchemaType, out OpenApiSchema referenceSchemaForData)) + if (schemaRepository.TryLookupByType(dataSchemaType, out OpenApiSchemaReference? referenceSchemaForData)) { return referenceSchemaForData; } @@ -114,11 +119,11 @@ public OpenApiSchema GenerateSchema(Type dataSchemaType, bool forRequestSchema, using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, dataSchemaType); - referenceSchemaForData = _defaultSchemaGenerator.GenerateSchema(dataSchemaType, schemaRepository); - OpenApiSchema fullSchemaForData = schemaRepository.Schemas[referenceSchemaForData.Reference.Id]; + referenceSchemaForData = (OpenApiSchemaReference)_defaultSchemaGenerator.GenerateSchema(dataSchemaType, schemaRepository); + var fullSchemaForData = (OpenApiSchema)schemaRepository.Schemas[referenceSchemaForData.Reference.Id!]; fullSchemaForData.AdditionalPropertiesAllowed = false; - OpenApiSchema inlineSchemaForData = fullSchemaForData.UnwrapLastExtendedSchema(); + var inlineSchemaForData = (OpenApiSchema)fullSchemaForData.UnwrapLastExtendedSchema(); SetAbstract(inlineSchemaForData, resourceSchemaType); SetResourceType(inlineSchemaForData, resourceType, schemaRepository); @@ -142,10 +147,11 @@ public OpenApiSchema GenerateSchema(Type dataSchemaType, bool forRequestSchema, if (RequiresRootObjectTypeInDataSchema(resourceSchemaType, forRequestSchema)) { - fullSchemaForData.Extensions[SetSchemaTypeToObjectDocumentFilter.RequiresRootObjectTypeKey] = new OpenApiBoolean(true); + fullSchemaForData.Extensions ??= new Dictionary(); + fullSchemaForData.Extensions[SetSchemaTypeToObjectDocumentFilter.RequiresRootObjectTypeKey] = new JsonNodeExtension(true); } - traceScope.TraceSucceeded(referenceSchemaForData.Reference.Id); + traceScope.TraceSucceeded(referenceSchemaForData.Reference.Id!); return referenceSchemaForData; } @@ -202,39 +208,39 @@ public OpenApiSchema GenerateSchema(Type dataSchemaType, bool forRequestSchema, return boxedSchemaType.Value; } - public OpenApiSchema GenerateSchemaForCommonData(Type commonDataSchemaType, SchemaRepository schemaRepository) + public OpenApiSchemaReference GenerateSchemaForCommonData(Type commonDataSchemaType, SchemaRepository schemaRepository) { ArgumentNullException.ThrowIfNull(commonDataSchemaType); ArgumentNullException.ThrowIfNull(schemaRepository); - if (schemaRepository.TryLookupByType(commonDataSchemaType, out OpenApiSchema? referenceSchema)) + if (schemaRepository.TryLookupByType(commonDataSchemaType, out OpenApiSchemaReference? referenceSchema)) { return referenceSchema; } using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, commonDataSchemaType); - OpenApiSchema referenceSchemaForResourceType = _resourceTypeSchemaGenerator.GenerateSchema(schemaRepository); - OpenApiSchema referenceSchemaForMeta = _metaSchemaGenerator.GenerateSchema(schemaRepository); + OpenApiSchemaReference referenceSchemaForResourceType = _resourceTypeSchemaGenerator.GenerateSchema(schemaRepository); + OpenApiSchemaReference referenceSchemaForMeta = _metaSchemaGenerator.GenerateSchema(schemaRepository); var fullSchema = new OpenApiSchema { - Type = "object", - Required = new SortedSet([JsonApiPropertyName.Type]), - Properties = new Dictionary + Type = JsonSchemaType.Object, + Required = [JsonApiPropertyName.Type], + Properties = new Dictionary { [JsonApiPropertyName.Type] = referenceSchemaForResourceType.WrapInExtendedSchema(), - [referenceSchemaForMeta.Reference.Id] = referenceSchemaForMeta.WrapInExtendedSchema() + [referenceSchemaForMeta.Reference.Id!] = referenceSchemaForMeta.WrapInExtendedSchema() }, AdditionalPropertiesAllowed = false, Discriminator = new OpenApiDiscriminator { PropertyName = JsonApiPropertyName.Type, - Mapping = new SortedDictionary(StringComparer.Ordinal) + Mapping = new Dictionary(StringComparer.Ordinal) }, - Extensions = + Extensions = new Dictionary { - ["x-abstract"] = new OpenApiBoolean(true) + ["x-abstract"] = new JsonNodeExtension(true) } }; @@ -251,7 +257,7 @@ private static ResourceType GetUltimateBaseType(ResourceType resourceType) { return UltimateBaseResourceTypeCache.GetOrAdd(resourceType, type => { - ResourceType baseType = type; + ResourceType? baseType = type; while (baseType.BaseType != null) { @@ -272,15 +278,16 @@ private static void SetAbstract(OpenApiSchema fullSchema, ResourceSchemaType res { if (resourceSchemaType.ResourceType.ClrType.IsAbstract && resourceSchemaType.SchemaOpenType != typeof(IdentifierInRequest<>)) { - fullSchema.Extensions["x-abstract"] = new OpenApiBoolean(true); + fullSchema.Extensions ??= new Dictionary(); + fullSchema.Extensions["x-abstract"] = new JsonNodeExtension(true); } } private void SetResourceType(OpenApiSchema fullSchema, ResourceType resourceType, SchemaRepository schemaRepository) { - if (fullSchema.Properties.ContainsKey(JsonApiPropertyName.Type)) + if (fullSchema.Properties != null && fullSchema.Properties.ContainsKey(JsonApiPropertyName.Type)) { - OpenApiSchema referenceSchema = _resourceTypeSchemaGenerator.GenerateSchema(resourceType, schemaRepository); + OpenApiSchemaReference referenceSchema = _resourceTypeSchemaGenerator.GenerateSchema(resourceType, schemaRepository); fullSchema.Properties[JsonApiPropertyName.Type] = referenceSchema.WrapInExtendedSchema(); } } @@ -295,9 +302,12 @@ private void AdaptResourceIdentity(OpenApiSchema fullSchema, ResourceSchemaType bool hasAtomicOperationsEndpoint = _generationCacheSchemaGenerator.HasAtomicOperationsEndpoint(schemaRepository); + Dictionary fullSchemaProperties = fullSchema.Properties!; + HashSet fullSchemaRequired = fullSchema.Required!; + if (!hasAtomicOperationsEndpoint) { - fullSchema.Properties.Remove(JsonApiPropertyName.Lid); + fullSchemaProperties.Remove(JsonApiPropertyName.Lid); } if (resourceSchemaType.SchemaOpenType == typeof(DataInCreateRequest<>)) @@ -308,23 +318,23 @@ private void AdaptResourceIdentity(OpenApiSchema fullSchema, ResourceSchemaType { if (clientIdGeneration == ClientIdGenerationMode.Forbidden) { - fullSchema.Properties.Remove(JsonApiPropertyName.Id); + fullSchemaProperties.Remove(JsonApiPropertyName.Id); } else if (clientIdGeneration == ClientIdGenerationMode.Required) { - fullSchema.Properties.Remove(JsonApiPropertyName.Lid); - fullSchema.Required.Add(JsonApiPropertyName.Id); + fullSchemaProperties.Remove(JsonApiPropertyName.Lid); + fullSchemaRequired.Add(JsonApiPropertyName.Id); } } else { if (clientIdGeneration == ClientIdGenerationMode.Forbidden) { - fullSchema.Properties.Remove(JsonApiPropertyName.Id); + fullSchemaProperties.Remove(JsonApiPropertyName.Id); } else if (clientIdGeneration == ClientIdGenerationMode.Required) { - fullSchema.Required.Add(JsonApiPropertyName.Id); + fullSchemaRequired.Add(JsonApiPropertyName.Id); } } } @@ -332,14 +342,14 @@ private void AdaptResourceIdentity(OpenApiSchema fullSchema, ResourceSchemaType { if (!hasAtomicOperationsEndpoint) { - fullSchema.Required.Add(JsonApiPropertyName.Id); + fullSchemaRequired.Add(JsonApiPropertyName.Id); } } } private void SetResourceId(OpenApiSchema fullSchema, ResourceType resourceType, SchemaRepository schemaRepository) { - if (fullSchema.Properties.ContainsKey(JsonApiPropertyName.Id)) + if (fullSchema.Properties != null && fullSchema.Properties.ContainsKey(JsonApiPropertyName.Id)) { OpenApiSchema idSchema = _resourceIdSchemaGenerator.GenerateSchema(resourceType, schemaRepository); fullSchema.Properties[JsonApiPropertyName.Id] = idSchema; @@ -349,7 +359,7 @@ private void SetResourceId(OpenApiSchema fullSchema, ResourceType resourceType, private void SetResourceFields(OpenApiSchema fullSchemaForData, ResourceSchemaType resourceSchemaType, bool forRequestSchema, SchemaRepository schemaRepository) { - bool schemaHasFields = fullSchemaForData.Properties.ContainsKey(JsonApiPropertyName.Attributes) && + bool schemaHasFields = fullSchemaForData.Properties != null && fullSchemaForData.Properties.ContainsKey(JsonApiPropertyName.Attributes) && fullSchemaForData.Properties.ContainsKey(JsonApiPropertyName.Relationships); if (schemaHasFields) @@ -367,8 +377,8 @@ private void SetFieldSchemaMembers(OpenApiSchema fullSchemaForData, ResourceSche { string propertyNameInSchema = forAttributes ? JsonApiPropertyName.Attributes : JsonApiPropertyName.Relationships; - OpenApiSchema referenceSchemaForFields = fullSchemaForData.Properties[propertyNameInSchema].UnwrapLastExtendedSchema(); - OpenApiSchema fullSchemaForFields = schemaRepository.Schemas[referenceSchemaForFields.Reference.Id]; + var referenceSchemaForFields = (OpenApiSchemaReference)fullSchemaForData.Properties![propertyNameInSchema].UnwrapLastExtendedSchema(); + var fullSchemaForFields = (OpenApiSchema)schemaRepository.Schemas[referenceSchemaForFields.Reference.Id!]; fullSchemaForFields.AdditionalPropertiesAllowed = false; SetAbstract(fullSchemaForFields, resourceSchemaTypeForData); @@ -382,10 +392,10 @@ private void SetFieldSchemaMembers(OpenApiSchema fullSchemaForData, ResourceSche fieldSchemaBuilder.SetMembersOfRelationships(fullSchemaForFields, forRequestSchema, schemaRepository); } - if (fullSchemaForFields.Properties.Count == 0 && !resourceSchemaTypeForData.ResourceType.IsPartOfTypeHierarchy()) + if (fullSchemaForFields.Properties is { Count: 0 } && !resourceSchemaTypeForData.ResourceType.IsPartOfTypeHierarchy()) { fullSchemaForData.Properties.Remove(propertyNameInSchema); - schemaRepository.Schemas.Remove(referenceSchemaForFields.Reference.Id); + schemaRepository.Schemas.Remove(referenceSchemaForFields.Reference.Id!); } else { @@ -414,9 +424,9 @@ private void SetFieldSchemaMembers(OpenApiSchema fullSchemaForData, ResourceSche baseSchemaType = commonFieldsSchemaType; } - OpenApiSchema referenceSchemaForBase = schemaRepository.LookupByType(baseSchemaType); + OpenApiSchemaReference referenceSchemaForBase = schemaRepository.LookupByType(baseSchemaType); - schemaRepository.Schemas[referenceSchemaForFields.Reference.Id] = new OpenApiSchema + schemaRepository.Schemas[referenceSchemaForFields.Reference.Id!] = new OpenApiSchema { AllOf = [ @@ -437,22 +447,22 @@ private ResourceSchemaType GetResourceSchemaTypeForFieldsProperty(ResourceSchema return ResourceSchemaType.Create(fieldsConstructedType, _resourceGraph); } - private OpenApiSchema GenerateSchemaForCommonFields(Type commonFieldsSchemaType, SchemaRepository schemaRepository) + private OpenApiSchemaReference GenerateSchemaForCommonFields(Type commonFieldsSchemaType, SchemaRepository schemaRepository) { - if (schemaRepository.TryLookupByType(commonFieldsSchemaType, out OpenApiSchema? referenceSchema)) + if (schemaRepository.TryLookupByType(commonFieldsSchemaType, out OpenApiSchemaReference? referenceSchema)) { return referenceSchema; } using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, commonFieldsSchemaType); - OpenApiSchema referenceSchemaForResourceType = _resourceTypeSchemaGenerator.GenerateSchema(schemaRepository); + OpenApiSchemaReference referenceSchemaForResourceType = _resourceTypeSchemaGenerator.GenerateSchema(schemaRepository); var fullSchema = new OpenApiSchema { - Type = "object", - Required = new SortedSet([OpenApiMediaTypeExtension.FullyQualifiedOpenApiDiscriminatorPropertyName]), - Properties = new Dictionary + Type = JsonSchemaType.Object, + Required = [OpenApiMediaTypeExtension.FullyQualifiedOpenApiDiscriminatorPropertyName], + Properties = new Dictionary { [OpenApiMediaTypeExtension.FullyQualifiedOpenApiDiscriminatorPropertyName] = referenceSchemaForResourceType.WrapInExtendedSchema() }, @@ -460,11 +470,11 @@ private OpenApiSchema GenerateSchemaForCommonFields(Type commonFieldsSchemaType, Discriminator = new OpenApiDiscriminator { PropertyName = OpenApiMediaTypeExtension.FullyQualifiedOpenApiDiscriminatorPropertyName, - Mapping = new SortedDictionary(StringComparer.Ordinal) + Mapping = new Dictionary(StringComparer.Ordinal) }, - Extensions = + Extensions = new Dictionary { - ["x-abstract"] = new OpenApiBoolean(true) + ["x-abstract"] = new JsonNodeExtension(true) } }; @@ -480,7 +490,7 @@ private OpenApiSchema GenerateSchemaForCommonFields(Type commonFieldsSchemaType, private void MapInDiscriminator(ResourceSchemaType resourceSchemaType, bool forRequestSchema, string discriminatorPropertyName, SchemaRepository schemaRepository) { - OpenApiSchema referenceSchemaForDerived = schemaRepository.LookupByType(resourceSchemaType.SchemaConstructedType); + OpenApiSchemaReference referenceSchemaForDerived = schemaRepository.LookupByType(resourceSchemaType.SchemaConstructedType); foreach (ResourceType? baseResourceType in GetBaseTypesToMapInto(resourceSchemaType, forRequestSchema)) { @@ -488,23 +498,25 @@ private void MapInDiscriminator(ResourceSchemaType resourceSchemaType, bool forR ? GetCommonSchemaType(resourceSchemaType.SchemaOpenType)! : resourceSchemaType.ChangeResourceType(baseResourceType).SchemaConstructedType; - OpenApiSchema referenceSchemaForBase = schemaRepository.LookupByType(baseSchemaType); - OpenApiSchema inlineSchemaForBase = schemaRepository.Schemas[referenceSchemaForBase.Reference.Id].UnwrapLastExtendedSchema(); + OpenApiSchemaReference referenceSchemaForBase = schemaRepository.LookupByType(baseSchemaType); + var inlineSchemaForBase = (OpenApiSchema)schemaRepository.Schemas[referenceSchemaForBase.Reference.Id!].UnwrapLastExtendedSchema(); inlineSchemaForBase.Discriminator ??= new OpenApiDiscriminator { PropertyName = discriminatorPropertyName, - Mapping = new SortedDictionary(StringComparer.Ordinal) + Mapping = new Dictionary(StringComparer.Ordinal) }; if (RepeatDiscriminatorInResponseDerivedTypes && !forRequestSchema) { + inlineSchemaForBase.Required ??= []; inlineSchemaForBase.Required.Add(discriminatorPropertyName); } string publicName = resourceSchemaType.ResourceType.PublicName; + inlineSchemaForBase.Discriminator.Mapping ??= []; - if (inlineSchemaForBase.Discriminator.Mapping.TryAdd(publicName, referenceSchemaForDerived.Reference.ReferenceV3) && baseResourceType == null) + if (inlineSchemaForBase.Discriminator.Mapping.TryAdd(publicName, referenceSchemaForDerived) && baseResourceType == null) { MapResourceTypeInEnum(publicName, schemaRepository); } @@ -543,11 +555,13 @@ private void MapInDiscriminator(ResourceSchemaType resourceSchemaType, bool forR private void MapResourceTypeInEnum(string publicName, SchemaRepository schemaRepository) { string schemaId = _schemaIdSelector.GetResourceTypeSchemaId(null); - OpenApiSchema fullSchema = schemaRepository.Schemas[schemaId]; + var fullSchema = (OpenApiSchema)schemaRepository.Schemas[schemaId]; + fullSchema.Enum ??= []; - if (!fullSchema.Enum.Any(openApiAny => openApiAny is OpenApiString openApiString && openApiString.Value == publicName)) + if (!fullSchema.Enum.Any(openApiAny => openApiAny is JsonValue openApiString && openApiString.GetValueKind() == JsonValueKind.String && + openApiString.GetValue() == publicName)) { - fullSchema.Enum.Add(new OpenApiString(publicName)); + fullSchema.Enum.Add(publicName); } } @@ -563,7 +577,7 @@ private void SetLinksVisibility(OpenApiSchema fullSchema, ResourceSchemaType res private void GenerateDataSchemasForDirectlyDerivedTypes(ResourceSchemaType resourceSchemaType, bool forRequestSchema, SchemaRepository schemaRepository) { - OpenApiSchema referenceSchemaForBase = schemaRepository.LookupByType(resourceSchemaType.SchemaConstructedType); + OpenApiSchemaReference referenceSchemaForBase = schemaRepository.LookupByType(resourceSchemaType.SchemaConstructedType); foreach (ResourceType derivedType in resourceSchemaType.ResourceType.DirectlyDerivedTypes) { @@ -572,18 +586,18 @@ private void GenerateDataSchemasForDirectlyDerivedTypes(ResourceSchemaType resou using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, resourceSchemaTypeForDerived.SchemaConstructedType); - OpenApiSchema referenceSchemaForDerived = _defaultSchemaGenerator.GenerateSchema(derivedSchemaType, schemaRepository); - OpenApiSchema fullSchemaForDerived = schemaRepository.Schemas[referenceSchemaForDerived.Reference.Id]; + var referenceSchemaForDerived = (OpenApiSchemaReference)_defaultSchemaGenerator.GenerateSchema(derivedSchemaType, schemaRepository); + var fullSchemaForDerived = (OpenApiSchema)schemaRepository.Schemas[referenceSchemaForDerived.Reference.Id!]; fullSchemaForDerived.AdditionalPropertiesAllowed = false; - OpenApiSchema inlineSchemaForDerived = fullSchemaForDerived.UnwrapLastExtendedSchema(); + var inlineSchemaForDerived = (OpenApiSchema)fullSchemaForDerived.UnwrapLastExtendedSchema(); SetResourceFields(inlineSchemaForDerived, resourceSchemaTypeForDerived, forRequestSchema, schemaRepository); SetAbstract(inlineSchemaForDerived, resourceSchemaTypeForDerived); RemoveProperties(inlineSchemaForDerived); MapInDiscriminator(resourceSchemaTypeForDerived, forRequestSchema, JsonApiPropertyName.Type, schemaRepository); - if (fullSchemaForDerived.AllOf.Count == 0) + if (fullSchemaForDerived.AllOf == null || fullSchemaForDerived.AllOf.Count == 0) { var compositeSchemaForDerived = new OpenApiSchema { @@ -595,7 +609,7 @@ private void GenerateDataSchemasForDirectlyDerivedTypes(ResourceSchemaType resou AdditionalPropertiesAllowed = false }; - schemaRepository.Schemas[referenceSchemaForDerived.Reference.Id] = compositeSchemaForDerived; + schemaRepository.Schemas[referenceSchemaForDerived.Reference.Id!] = compositeSchemaForDerived; } else { @@ -604,22 +618,26 @@ private void GenerateDataSchemasForDirectlyDerivedTypes(ResourceSchemaType resou if (RequiresRootObjectTypeInDataSchema(resourceSchemaTypeForDerived, forRequestSchema)) { - OpenApiSchema fullSchemaForData = schemaRepository.Schemas[referenceSchemaForDerived.Reference.Id]; - fullSchemaForData.Extensions[SetSchemaTypeToObjectDocumentFilter.RequiresRootObjectTypeKey] = new OpenApiBoolean(true); + var fullSchemaForData = (OpenApiSchema)schemaRepository.Schemas[referenceSchemaForDerived.Reference.Id!]; + fullSchemaForData.Extensions ??= new Dictionary(); + fullSchemaForData.Extensions[SetSchemaTypeToObjectDocumentFilter.RequiresRootObjectTypeKey] = new JsonNodeExtension(true); } GenerateDataSchemasForDirectlyDerivedTypes(resourceSchemaTypeForDerived, forRequestSchema, schemaRepository); - traceScope.TraceSucceeded(referenceSchemaForDerived.Reference.Id); + traceScope.TraceSucceeded(referenceSchemaForDerived.Reference.Id!); } } private static void RemoveProperties(OpenApiSchema fullSchema) { - foreach (string propertyName in fullSchema.Properties.Keys) + if (fullSchema.Properties != null) { - fullSchema.Properties.Remove(propertyName); - fullSchema.Required.Remove(propertyName); + foreach (string propertyName in fullSchema.Properties.Keys) + { + fullSchema.Properties.Remove(propertyName); + fullSchema.Required?.Remove(propertyName); + } } } diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/LinksVisibilitySchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/LinksVisibilitySchemaGenerator.cs index 5c099a3fc1..d7c87dd316 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/LinksVisibilitySchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/LinksVisibilitySchemaGenerator.cs @@ -5,6 +5,8 @@ using JsonApiDotNetCore.OpenApi.Swashbuckle.SwaggerComponents; using JsonApiDotNetCore.Resources.Annotations; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; @@ -110,32 +112,32 @@ public void UpdateSchemaForRelationship(Type schemaType, OpenApiSchema fullSchem private void UpdateLinksProperty(OpenApiSchema fullSchemaForLinksContainer, LinkTypes visibleLinkTypes, LinkTypes possibleLinkTypes, SchemaRepository schemaRepository) { - OpenApiSchema referenceSchemaForLinks = fullSchemaForLinksContainer.Properties[JsonApiPropertyName.Links].UnwrapLastExtendedSchema(); + var referenceSchemaForLinks = (OpenApiSchemaReference)fullSchemaForLinksContainer.Properties![JsonApiPropertyName.Links].UnwrapLastExtendedSchema(); if ((visibleLinkTypes & possibleLinkTypes) == 0) { - fullSchemaForLinksContainer.Required.Remove(JsonApiPropertyName.Links); + fullSchemaForLinksContainer.Required?.Remove(JsonApiPropertyName.Links); fullSchemaForLinksContainer.Properties.Remove(JsonApiPropertyName.Links); - schemaRepository.Schemas.Remove(referenceSchemaForLinks.Reference.Id); + schemaRepository.Schemas.Remove(referenceSchemaForLinks.Reference.Id!); } else if (visibleLinkTypes != possibleLinkTypes) { - string linksSchemaId = referenceSchemaForLinks.Reference.Id; + string linksSchemaId = referenceSchemaForLinks.Reference.Id!; - if (schemaRepository.Schemas.TryGetValue(linksSchemaId, out OpenApiSchema? fullSchemaForLinks)) + if (schemaRepository.Schemas.TryGetValue(linksSchemaId, out IOpenApiSchema? fullSchemaForLinks)) { UpdateLinkProperties(fullSchemaForLinks, visibleLinkTypes); } } } - private void UpdateLinkProperties(OpenApiSchema fullSchemaForLinks, LinkTypes availableLinkTypes) + private void UpdateLinkProperties(IOpenApiSchema fullSchemaForLinks, LinkTypes availableLinkTypes) { foreach (string propertyName in LinkTypeToPropertyNamesMap.Where(pair => !availableLinkTypes.HasFlag(pair.Key)).SelectMany(pair => pair.Value)) { - fullSchemaForLinks.Required.Remove(propertyName); - fullSchemaForLinks.Properties.Remove(propertyName); + fullSchemaForLinks.Required?.Remove(propertyName); + fullSchemaForLinks.Properties?.Remove(propertyName); } } diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/MetaSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/MetaSchemaGenerator.cs index 1e1bd07852..f33a95c084 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/MetaSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/MetaSchemaGenerator.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.OpenApi.Swashbuckle.JsonApiObjects; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; @@ -19,11 +20,11 @@ public MetaSchemaGenerator(SchemaGenerationTracer schemaGenerationTracer, JsonAp _schemaIdSelector = schemaIdSelector; } - public OpenApiSchema GenerateSchema(SchemaRepository schemaRepository) + public OpenApiSchemaReference GenerateSchema(SchemaRepository schemaRepository) { ArgumentNullException.ThrowIfNull(schemaRepository); - if (schemaRepository.TryLookupByType(SchemaType, out OpenApiSchema? referenceSchema)) + if (schemaRepository.TryLookupByType(SchemaType, out OpenApiSchemaReference? referenceSchema)) { return referenceSchema; } @@ -32,10 +33,10 @@ public OpenApiSchema GenerateSchema(SchemaRepository schemaRepository) var fullSchema = new OpenApiSchema { - Type = "object", + Type = JsonSchemaType.Object, AdditionalProperties = new OpenApiSchema { - Nullable = true + Type = JsonSchemaType.Null } }; diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/RelationshipIdentifierSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/RelationshipIdentifierSchemaGenerator.cs index 98f176e8df..be9d64c37b 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/RelationshipIdentifierSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/RelationshipIdentifierSchemaGenerator.cs @@ -2,6 +2,7 @@ using JsonApiDotNetCore.OpenApi.Swashbuckle.JsonApiObjects.ResourceObjects; using JsonApiDotNetCore.Resources.Annotations; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; @@ -34,7 +35,7 @@ public RelationshipIdentifierSchemaGenerator(SchemaGenerationTracer schemaGenera _schemaIdSelector = schemaIdSelector; } - public OpenApiSchema GenerateSchema(RelationshipAttribute relationship, SchemaRepository schemaRepository) + public OpenApiSchemaReference GenerateSchema(RelationshipAttribute relationship, SchemaRepository schemaRepository) { ArgumentNullException.ThrowIfNull(relationship); ArgumentNullException.ThrowIfNull(schemaRepository); @@ -43,14 +44,7 @@ public OpenApiSchema GenerateSchema(RelationshipAttribute relationship, SchemaRe if (schemaRepository.Schemas.ContainsKey(schemaId)) { - return new OpenApiSchema - { - Reference = new OpenApiReference - { - Id = schemaId, - Type = ReferenceType.Schema - } - }; + return new OpenApiSchemaReference(schemaId); } using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, relationship); @@ -58,17 +52,19 @@ public OpenApiSchema GenerateSchema(RelationshipAttribute relationship, SchemaRe Type relationshipIdentifierConstructedType = typeof(RelationshipIdentifier<>).MakeGenericType(relationship.LeftType.ClrType); ConsistencyGuard.ThrowIf(schemaRepository.TryLookupByType(relationshipIdentifierConstructedType, out _)); - OpenApiSchema referenceSchemaForIdentifier = _defaultSchemaGenerator.GenerateSchema(relationshipIdentifierConstructedType, schemaRepository); - OpenApiSchema fullSchemaForIdentifier = schemaRepository.Schemas[referenceSchemaForIdentifier.Reference.Id]; + var referenceSchemaForIdentifier = + (OpenApiSchemaReference)_defaultSchemaGenerator.GenerateSchema(relationshipIdentifierConstructedType, schemaRepository); - fullSchemaForIdentifier.Properties.Remove(JsonApiPropertyName.Meta); + var fullSchemaForIdentifier = (OpenApiSchema)schemaRepository.Schemas[referenceSchemaForIdentifier.Reference.Id!]; + + fullSchemaForIdentifier.Properties?.Remove(JsonApiPropertyName.Meta); SetResourceType(fullSchemaForIdentifier, relationship.LeftType, schemaRepository); SetResourceId(fullSchemaForIdentifier, relationship.LeftType, schemaRepository); SetRelationship(fullSchemaForIdentifier, relationship, schemaRepository); schemaRepository.ReplaceSchemaId(relationshipIdentifierConstructedType, schemaId); - referenceSchemaForIdentifier.Reference.Id = schemaId; + referenceSchemaForIdentifier = new OpenApiSchemaReference(schemaId); traceScope.TraceSucceeded(schemaId); return referenceSchemaForIdentifier; @@ -76,19 +72,22 @@ public OpenApiSchema GenerateSchema(RelationshipAttribute relationship, SchemaRe private void SetResourceType(OpenApiSchema fullSchemaForIdentifier, ResourceType resourceType, SchemaRepository schemaRepository) { - OpenApiSchema referenceSchema = _resourceTypeSchemaGenerator.GenerateSchema(resourceType, schemaRepository); + OpenApiSchemaReference referenceSchema = _resourceTypeSchemaGenerator.GenerateSchema(resourceType, schemaRepository); + fullSchemaForIdentifier.Properties ??= []; fullSchemaForIdentifier.Properties[JsonApiPropertyName.Type] = referenceSchema.WrapInExtendedSchema(); } private void SetResourceId(OpenApiSchema fullSchemaForResourceData, ResourceType resourceType, SchemaRepository schemaRepository) { OpenApiSchema idSchema = _resourceIdSchemaGenerator.GenerateSchema(resourceType, schemaRepository); + fullSchemaForResourceData.Properties ??= []; fullSchemaForResourceData.Properties[JsonApiPropertyName.Id] = idSchema; } private void SetRelationship(OpenApiSchema fullSchemaForIdentifier, RelationshipAttribute relationship, SchemaRepository schemaRepository) { - OpenApiSchema referenceSchema = _relationshipNameSchemaGenerator.GenerateSchema(relationship, schemaRepository); + OpenApiSchemaReference referenceSchema = _relationshipNameSchemaGenerator.GenerateSchema(relationship, schemaRepository); + fullSchemaForIdentifier.Properties ??= []; fullSchemaForIdentifier.Properties[JsonApiPropertyName.Relationship] = referenceSchema.WrapInExtendedSchema(); } } diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/RelationshipNameSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/RelationshipNameSchemaGenerator.cs index 7f5c0c5d3a..06f92571b6 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/RelationshipNameSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/RelationshipNameSchemaGenerator.cs @@ -1,6 +1,6 @@ using JsonApiDotNetCore.Resources.Annotations; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; @@ -19,7 +19,7 @@ public RelationshipNameSchemaGenerator(SchemaGenerationTracer schemaGenerationTr _schemaIdSelector = schemaIdSelector; } - public OpenApiSchema GenerateSchema(RelationshipAttribute relationship, SchemaRepository schemaRepository) + public OpenApiSchemaReference GenerateSchema(RelationshipAttribute relationship, SchemaRepository schemaRepository) { ArgumentNullException.ThrowIfNull(relationship); ArgumentNullException.ThrowIfNull(schemaRepository); @@ -28,25 +28,18 @@ public OpenApiSchema GenerateSchema(RelationshipAttribute relationship, SchemaRe if (schemaRepository.Schemas.ContainsKey(schemaId)) { - return new OpenApiSchema - { - Reference = new OpenApiReference - { - Id = schemaId, - Type = ReferenceType.Schema - } - }; + return new OpenApiSchemaReference(schemaId); } using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, relationship); var fullSchema = new OpenApiSchema { - Type = "string", - Enum = [new OpenApiString(relationship.PublicName)] + Type = JsonSchemaType.String, + Enum = [relationship.PublicName] }; - OpenApiSchema referenceSchema = schemaRepository.AddDefinition(schemaId, fullSchema); + OpenApiSchemaReference? referenceSchema = schemaRepository.AddDefinition(schemaId, fullSchema); traceScope.TraceSucceeded(schemaId); return referenceSchema; diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/ResourceIdSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/ResourceIdSchemaGenerator.cs index 21bc5020ed..d749770df6 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/ResourceIdSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/ResourceIdSchemaGenerator.cs @@ -27,10 +27,10 @@ public OpenApiSchema GenerateSchema(Type resourceIdClrType, SchemaRepository sch ArgumentNullException.ThrowIfNull(resourceIdClrType); ArgumentNullException.ThrowIfNull(schemaRepository); - OpenApiSchema idSchema = _defaultSchemaGenerator.GenerateSchema(resourceIdClrType, schemaRepository); - ConsistencyGuard.ThrowIf(idSchema.Reference != null); + var idSchema = _defaultSchemaGenerator.GenerateSchema(resourceIdClrType, schemaRepository) as OpenApiSchema; + ConsistencyGuard.ThrowIf(idSchema == null); - idSchema.Type = "string"; + idSchema.Type = JsonSchemaType.String; if (resourceIdClrType != typeof(string)) { diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/ResourceTypeSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/ResourceTypeSchemaGenerator.cs index eb933bedbe..c52acb7acc 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/ResourceTypeSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/ResourceTypeSchemaGenerator.cs @@ -1,7 +1,9 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.OpenApi.Swashbuckle.SwaggerComponents; -using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; @@ -20,12 +22,12 @@ public ResourceTypeSchemaGenerator(SchemaGenerationTracer schemaGenerationTracer _schemaIdSelector = schemaIdSelector; } - public OpenApiSchema GenerateSchema(ResourceType resourceType, SchemaRepository schemaRepository) + public OpenApiSchemaReference GenerateSchema(ResourceType resourceType, SchemaRepository schemaRepository) { ArgumentNullException.ThrowIfNull(resourceType); ArgumentNullException.ThrowIfNull(schemaRepository); - if (schemaRepository.TryLookupByType(resourceType.ClrType, out OpenApiSchema? referenceSchema)) + if (schemaRepository.TryLookupByType(resourceType.ClrType, out OpenApiSchemaReference? referenceSchema)) { return referenceSchema; } @@ -34,17 +36,17 @@ public OpenApiSchema GenerateSchema(ResourceType resourceType, SchemaRepository var fullSchema = new OpenApiSchema { - Type = "string", - Enum = resourceType.ClrType.IsAbstract ? [] : [new OpenApiString(resourceType.PublicName)], - Extensions = + Type = JsonSchemaType.String, + Enum = resourceType.ClrType.IsAbstract ? [] : [resourceType.PublicName], + Extensions = new Dictionary { - [StringEnumOrderingFilter.RequiresSortKey] = new OpenApiBoolean(true) + [StringEnumOrderingFilter.RequiresSortKey] = new JsonNodeExtension(true) } }; foreach (ResourceType derivedType in resourceType.GetAllConcreteDerivedTypes()) { - fullSchema.Enum.Add(new OpenApiString(derivedType.PublicName)); + fullSchema.Enum.Add(derivedType.PublicName); } string schemaId = _schemaIdSelector.GetResourceTypeSchemaId(resourceType); @@ -56,34 +58,27 @@ public OpenApiSchema GenerateSchema(ResourceType resourceType, SchemaRepository return referenceSchema; } - public OpenApiSchema GenerateSchema(SchemaRepository schemaRepository) + public OpenApiSchemaReference GenerateSchema(SchemaRepository schemaRepository) { string schemaId = _schemaIdSelector.GetResourceTypeSchemaId(null); if (schemaRepository.Schemas.ContainsKey(schemaId)) { - return new OpenApiSchema - { - Reference = new OpenApiReference - { - Id = schemaId, - Type = ReferenceType.Schema - } - }; + return new OpenApiSchemaReference(schemaId); } using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this); var fullSchema = new OpenApiSchema { - Type = "string", - Extensions = + Type = JsonSchemaType.String, + Extensions = new Dictionary { - [StringEnumOrderingFilter.RequiresSortKey] = new OpenApiBoolean(true) + [StringEnumOrderingFilter.RequiresSortKey] = new JsonNodeExtension(true) } }; - OpenApiSchema referenceSchema = schemaRepository.AddDefinition(schemaId, fullSchema); + OpenApiSchemaReference? referenceSchema = schemaRepository.AddDefinition(schemaId, fullSchema); traceScope.TraceSucceeded(schemaId); return referenceSchema; diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/AtomicOperationsDocumentSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/AtomicOperationsDocumentSchemaGenerator.cs index f128f89299..b4b6a4c725 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/AtomicOperationsDocumentSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/AtomicOperationsDocumentSchemaGenerator.cs @@ -7,8 +7,11 @@ using JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; using JsonApiDotNetCore.Resources.Annotations; using JsonApiDotNetCore.Serialization.Objects; -using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Documents; @@ -68,7 +71,7 @@ public override bool CanGenerate(Type schemaType) return schemaType == typeof(OperationsRequestDocument) || schemaType == typeof(OperationsResponseDocument); } - protected override OpenApiSchema GenerateDocumentSchema(Type schemaType, SchemaRepository schemaRepository) + protected override OpenApiSchemaReference GenerateDocumentSchema(Type schemaType, SchemaRepository schemaRepository) { ArgumentNullException.ThrowIfNull(schemaType); ArgumentNullException.ThrowIfNull(schemaRepository); @@ -84,7 +87,7 @@ protected override OpenApiSchema GenerateDocumentSchema(Type schemaType, SchemaR GenerateSchemasForResponseDocument(schemaRepository); } - return _defaultSchemaGenerator.GenerateSchema(schemaType, schemaRepository); + return (OpenApiSchemaReference)_defaultSchemaGenerator.GenerateSchema(schemaType, schemaRepository); } private void GenerateSchemasForRequestDocument(SchemaRepository schemaRepository) @@ -97,38 +100,38 @@ private void GenerateSchemasForRequestDocument(SchemaRepository schemaRepository } } - private OpenApiSchema GenerateSchemaForAbstractOperation(SchemaRepository schemaRepository) + private OpenApiSchemaReference GenerateSchemaForAbstractOperation(SchemaRepository schemaRepository) { - if (schemaRepository.TryLookupByType(AtomicOperationAbstractType, out OpenApiSchema? referenceSchema)) + if (schemaRepository.TryLookupByType(AtomicOperationAbstractType, out OpenApiSchemaReference? referenceSchema)) { return referenceSchema; } using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, AtomicOperationAbstractType); - OpenApiSchema referenceSchemaForMeta = _metaSchemaGenerator.GenerateSchema(schemaRepository); + OpenApiSchemaReference referenceSchemaForMeta = _metaSchemaGenerator.GenerateSchema(schemaRepository); var fullSchema = new OpenApiSchema { - Type = "object", - Required = new SortedSet([OpenApiMediaTypeExtension.FullyQualifiedOpenApiDiscriminatorPropertyName]), - Properties = new Dictionary + Type = JsonSchemaType.Object, + Required = [OpenApiMediaTypeExtension.FullyQualifiedOpenApiDiscriminatorPropertyName], + Properties = new Dictionary { - [OpenApiMediaTypeExtension.FullyQualifiedOpenApiDiscriminatorPropertyName] = new() + [OpenApiMediaTypeExtension.FullyQualifiedOpenApiDiscriminatorPropertyName] = new OpenApiSchema { - Type = "string" + Type = JsonSchemaType.String }, - [referenceSchemaForMeta.Reference.Id] = referenceSchemaForMeta.WrapInExtendedSchema() + [referenceSchemaForMeta.Reference.Id!] = referenceSchemaForMeta.WrapInExtendedSchema() }, AdditionalPropertiesAllowed = false, Discriminator = new OpenApiDiscriminator { PropertyName = OpenApiMediaTypeExtension.FullyQualifiedOpenApiDiscriminatorPropertyName, - Mapping = new SortedDictionary(StringComparer.Ordinal) + Mapping = new Dictionary(StringComparer.Ordinal) }, - Extensions = + Extensions = new Dictionary { - ["x-abstract"] = new OpenApiBoolean(true) + ["x-abstract"] = new JsonNodeExtension(true) } }; @@ -188,17 +191,18 @@ private void GenerateSchemaForResourceOperation(Type operationOpenType, Resource } } - OpenApiSchema referenceSchemaForOperation = _defaultSchemaGenerator.GenerateSchema(operationConstructedType, schemaRepository); - OpenApiSchema fullSchemaForOperation = schemaRepository.Schemas[referenceSchemaForOperation.Reference.Id]; + var referenceSchemaForOperation = (OpenApiSchemaReference)_defaultSchemaGenerator.GenerateSchema(operationConstructedType, schemaRepository); + var fullSchemaForOperation = (OpenApiSchema)schemaRepository.Schemas[referenceSchemaForOperation.Reference.Id!]; fullSchemaForOperation.AdditionalPropertiesAllowed = false; - OpenApiSchema inlineSchemaForOperation = fullSchemaForOperation.UnwrapLastExtendedSchema(); + var inlineSchemaForOperation = (OpenApiSchema)fullSchemaForOperation.UnwrapLastExtendedSchema(); if (needsEmptyDerivedSchema) { Type baseOperationSchemaType = ChangeResourceTypeInSchemaType(operationOpenType, resourceType.BaseType!); - OpenApiSchema referenceSchemaForBaseOperation = schemaRepository.LookupByType(baseOperationSchemaType); + OpenApiSchemaReference referenceSchemaForBaseOperation = schemaRepository.LookupByType(baseOperationSchemaType); RemoveProperties(inlineSchemaForOperation); + fullSchemaForOperation.AllOf ??= []; fullSchemaForOperation.AllOf[0] = referenceSchemaForBaseOperation; } else @@ -209,7 +213,7 @@ private void GenerateSchemaForResourceOperation(Type operationOpenType, Resource string discriminatorValue = _schemaIdSelector.GetAtomicOperationDiscriminatorValue(operationCode, resourceType); MapInDiscriminator(referenceSchemaForOperation, discriminatorValue, schemaRepository); - traceScope.TraceSucceeded(referenceSchemaForOperation.Reference.Id); + traceScope.TraceSucceeded(referenceSchemaForOperation.Reference.Id!); } foreach (ResourceType derivedType in resourceType.DirectlyDerivedTypes) @@ -251,24 +255,30 @@ private static Type ChangeResourceTypeInSchemaType(Type schemaOpenType, Resource private static void RemoveProperties(OpenApiSchema fullSchema) { - foreach (string propertyName in fullSchema.Properties.Keys) + if (fullSchema.Properties != null) { - fullSchema.Properties.Remove(propertyName); - fullSchema.Required.Remove(propertyName); + foreach (string propertyName in fullSchema.Properties.Keys) + { + fullSchema.Properties.Remove(propertyName); + fullSchema.Required?.Remove(propertyName); + } } } private void SetOperationCode(OpenApiSchema fullSchema, AtomicOperationCode operationCode, SchemaRepository schemaRepository) { - OpenApiSchema referenceSchema = _atomicOperationCodeSchemaGenerator.GenerateSchema(operationCode, schemaRepository); + var referenceSchema = (OpenApiSchemaReference)_atomicOperationCodeSchemaGenerator.GenerateSchema(operationCode, schemaRepository); + fullSchema.Properties ??= []; fullSchema.Properties[JsonApiPropertyName.Op] = referenceSchema.WrapInExtendedSchema(); } - private static void MapInDiscriminator(OpenApiSchema referenceSchemaForOperation, string discriminatorValue, SchemaRepository schemaRepository) + private static void MapInDiscriminator(OpenApiSchemaReference referenceSchemaForOperation, string discriminatorValue, SchemaRepository schemaRepository) { - OpenApiSchema referenceSchemaForAbstractOperation = schemaRepository.LookupByType(AtomicOperationAbstractType); - OpenApiSchema fullSchemaForAbstractOperation = schemaRepository.Schemas[referenceSchemaForAbstractOperation.Reference.Id]; - fullSchemaForAbstractOperation.Discriminator.Mapping.Add(discriminatorValue, referenceSchemaForOperation.Reference.ReferenceV3); + OpenApiSchemaReference referenceSchemaForAbstractOperation = schemaRepository.LookupByType(AtomicOperationAbstractType); + var fullSchemaForAbstractOperation = (OpenApiSchema)schemaRepository.Schemas[referenceSchemaForAbstractOperation.Reference.Id!]; + fullSchemaForAbstractOperation.Discriminator ??= new OpenApiDiscriminator(); + fullSchemaForAbstractOperation.Discriminator.Mapping ??= []; + fullSchemaForAbstractOperation.Discriminator.Mapping.Add(discriminatorValue, referenceSchemaForOperation); } private static HashSet GetRelationshipsInTypeHierarchy(ResourceType baseType) @@ -315,7 +325,7 @@ private void GenerateSchemaForRelationshipOperation(Type operationOpenType, Rela RelationshipAttribute? relationshipInAnyBaseResourceType = GetRelationshipEnabledInAnyBase(relationship, writeOperation); - OpenApiSchema? referenceSchemaForRelationshipIdentifier; + OpenApiSchemaReference? referenceSchemaForRelationshipIdentifier; if (relationshipInAnyBaseResourceType == null) { @@ -337,22 +347,24 @@ private void GenerateSchemaForRelationshipOperation(Type operationOpenType, Rela // the relationship name because there's no runtime Type available for it. string schemaId = _schemaIdSelector.GetRelationshipAtomicOperationSchemaId(relationship, operationCode); - OpenApiSchema referenceSchemaForOperation = _defaultSchemaGenerator.GenerateSchema(operationConstructedType, schemaRepository); - OpenApiSchema fullSchemaForOperation = schemaRepository.Schemas[referenceSchemaForOperation.Reference.Id]; + var referenceSchemaForOperation = (OpenApiSchemaReference)_defaultSchemaGenerator.GenerateSchema(operationConstructedType, schemaRepository); + var fullSchemaForOperation = (OpenApiSchema)schemaRepository.Schemas[referenceSchemaForOperation.Reference.Id!]; fullSchemaForOperation.AdditionalPropertiesAllowed = false; - OpenApiSchema inlineSchemaForOperation = fullSchemaForOperation.UnwrapLastExtendedSchema(); + var inlineSchemaForOperation = (OpenApiSchema)fullSchemaForOperation.UnwrapLastExtendedSchema(); SetOperationCode(inlineSchemaForOperation, operationCode, schemaRepository); if (referenceSchemaForRelationshipIdentifier != null) { + inlineSchemaForOperation.Properties ??= []; inlineSchemaForOperation.Properties[JsonApiPropertyName.Ref] = referenceSchemaForRelationshipIdentifier.WrapInExtendedSchema(); } - inlineSchemaForOperation.Properties[JsonApiPropertyName.Data].Nullable = _resourceFieldValidationMetadataProvider.IsNullable(relationship); + bool isNullable = _resourceFieldValidationMetadataProvider.IsNullable(relationship); + ((OpenApiSchema)inlineSchemaForOperation.Properties![JsonApiPropertyName.Data]).SetNullable(isNullable); schemaRepository.ReplaceSchemaId(operationConstructedType, schemaId); - referenceSchemaForOperation.Reference.Id = schemaId; + referenceSchemaForOperation = new OpenApiSchemaReference(schemaId); if (relationshipInAnyBaseResourceType != null) { @@ -361,14 +373,8 @@ private void GenerateSchemaForRelationshipOperation(Type operationOpenType, Rela string baseRelationshipSchemaId = _schemaIdSelector.GetRelationshipAtomicOperationSchemaId(relationshipInAnyBaseResourceType, operationCode); ConsistencyGuard.ThrowIf(!schemaRepository.Schemas.ContainsKey(baseRelationshipSchemaId)); - fullSchemaForOperation.AllOf[0] = new OpenApiSchema - { - Reference = new OpenApiReference - { - Id = baseRelationshipSchemaId, - Type = ReferenceType.Schema - } - }; + fullSchemaForOperation.AllOf ??= []; + fullSchemaForOperation.AllOf[0] = new OpenApiSchemaReference(baseRelationshipSchemaId); } string discriminatorValue = _schemaIdSelector.GetAtomicOperationDiscriminatorValue(operationCode, relationship); diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/DocumentSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/DocumentSchemaGenerator.cs index 740b2fca43..1baca73aa6 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/DocumentSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/DocumentSchemaGenerator.cs @@ -1,6 +1,8 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Documents; @@ -31,12 +33,12 @@ protected DocumentSchemaGenerator(SchemaGenerationTracer schemaGenerationTracer, public abstract bool CanGenerate(Type schemaType); - public OpenApiSchema GenerateSchema(Type schemaType, SchemaRepository schemaRepository) + public IOpenApiSchema GenerateSchema(Type schemaType, SchemaRepository schemaRepository) { ArgumentNullException.ThrowIfNull(schemaType); ArgumentNullException.ThrowIfNull(schemaRepository); - if (schemaRepository.TryLookupByType(schemaType, out OpenApiSchema? referenceSchema)) + if (schemaRepository.TryLookupByType(schemaType, out OpenApiSchemaReference? referenceSchema)) { return referenceSchema; } @@ -46,21 +48,21 @@ public OpenApiSchema GenerateSchema(Type schemaType, SchemaRepository schemaRepo _metaSchemaGenerator.GenerateSchema(schemaRepository); referenceSchema = GenerateDocumentSchema(schemaType, schemaRepository); - OpenApiSchema fullSchema = schemaRepository.Schemas[referenceSchema.Reference.Id]; + var fullSchema = (OpenApiSchema)schemaRepository.Schemas[referenceSchema.Reference.Id!]; _linksVisibilitySchemaGenerator.UpdateSchemaForTopLevel(schemaType, fullSchema, schemaRepository); SetJsonApiVersion(fullSchema, schemaRepository); - traceScope.TraceSucceeded(referenceSchema.Reference.Id); + traceScope.TraceSucceeded(referenceSchema.Reference.Id!); return referenceSchema; } - protected abstract OpenApiSchema GenerateDocumentSchema(Type schemaType, SchemaRepository schemaRepository); + protected abstract OpenApiSchemaReference GenerateDocumentSchema(Type schemaType, SchemaRepository schemaRepository); private void SetJsonApiVersion(OpenApiSchema fullSchema, SchemaRepository schemaRepository) { - if (fullSchema.Properties.ContainsKey(JsonApiPropertyName.Jsonapi) && !_options.IncludeJsonApiVersion) + if (fullSchema.Properties != null && fullSchema.Properties.ContainsKey(JsonApiPropertyName.Jsonapi) && !_options.IncludeJsonApiVersion) { fullSchema.Properties.Remove(JsonApiPropertyName.Jsonapi); schemaRepository.Schemas.Remove(JsonApiPropertyName.Jsonapi); diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/ErrorResponseDocumentSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/ErrorResponseDocumentSchemaGenerator.cs index 3d305e889b..1783ba7b8d 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/ErrorResponseDocumentSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/ErrorResponseDocumentSchemaGenerator.cs @@ -3,6 +3,7 @@ using JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Documents; @@ -34,27 +35,28 @@ public override bool CanGenerate(Type schemaType) return schemaType == typeof(ErrorResponseDocument); } - protected override OpenApiSchema GenerateDocumentSchema(Type schemaType, SchemaRepository schemaRepository) + protected override OpenApiSchemaReference GenerateDocumentSchema(Type schemaType, SchemaRepository schemaRepository) { ArgumentNullException.ThrowIfNull(schemaType); ArgumentNullException.ThrowIfNull(schemaRepository); - OpenApiSchema referenceSchemaForErrorObject = GenerateSchemaForErrorObject(schemaRepository); - OpenApiSchema fullSchemaForErrorObject = schemaRepository.Schemas[referenceSchemaForErrorObject.Reference.Id]; + OpenApiSchemaReference referenceSchemaForErrorObject = GenerateSchemaForErrorObject(schemaRepository); + var fullSchemaForErrorObject = (OpenApiSchema)schemaRepository.Schemas[referenceSchemaForErrorObject.Reference.Id!]; - OpenApiSchema referenceSchemaForMeta = _metaSchemaGenerator.GenerateSchema(schemaRepository); + OpenApiSchemaReference referenceSchemaForMeta = _metaSchemaGenerator.GenerateSchema(schemaRepository); + fullSchemaForErrorObject.Properties ??= []; fullSchemaForErrorObject.Properties[JsonApiPropertyName.Meta] = referenceSchemaForMeta.WrapInExtendedSchema(); - return _defaultSchemaGenerator.GenerateSchema(schemaType, schemaRepository); + return (OpenApiSchemaReference)_defaultSchemaGenerator.GenerateSchema(schemaType, schemaRepository); } - private OpenApiSchema GenerateSchemaForErrorObject(SchemaRepository schemaRepository) + private OpenApiSchemaReference GenerateSchemaForErrorObject(SchemaRepository schemaRepository) { using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, ErrorObjectType); - OpenApiSchema referenceSchema = _defaultSchemaGenerator.GenerateSchema(ErrorObjectType, schemaRepository); + var referenceSchema = (OpenApiSchemaReference)_defaultSchemaGenerator.GenerateSchema(ErrorObjectType, schemaRepository); - traceScope.TraceSucceeded(referenceSchema.Reference.Id); + traceScope.TraceSucceeded(referenceSchema.Reference.Id!); return referenceSchema; } } diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/ResourceOrRelationshipDocumentSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/ResourceOrRelationshipDocumentSchemaGenerator.cs index 767f0d0143..a177be776e 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/ResourceOrRelationshipDocumentSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Documents/ResourceOrRelationshipDocumentSchemaGenerator.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; using JsonApiDotNetCore.OpenApi.Swashbuckle.SwaggerComponents; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Documents; @@ -57,7 +58,7 @@ public override bool CanGenerate(Type schemaType) return RequestDocumentSchemaTypes.Contains(schemaOpenType) || ResponseDocumentSchemaTypes.Contains(schemaOpenType); } - protected override OpenApiSchema GenerateDocumentSchema(Type schemaType, SchemaRepository schemaRepository) + protected override OpenApiSchemaReference GenerateDocumentSchema(Type schemaType, SchemaRepository schemaRepository) { ArgumentNullException.ThrowIfNull(schemaType); ArgumentNullException.ThrowIfNull(schemaRepository); @@ -67,12 +68,13 @@ protected override OpenApiSchema GenerateDocumentSchema(Type schemaType, SchemaR _ = _dataContainerSchemaGenerator.GenerateSchema(schemaType, resourceSchemaType.ResourceType, isRequestSchema, !isRequestSchema, schemaRepository); - OpenApiSchema? referenceSchemaForDocument = _defaultSchemaGenerator.GenerateSchema(schemaType, schemaRepository); - OpenApiSchema inlineSchemaForDocument = schemaRepository.Schemas[referenceSchemaForDocument.Reference.Id].UnwrapLastExtendedSchema(); + var referenceSchemaForDocument = (OpenApiSchemaReference)_defaultSchemaGenerator.GenerateSchema(schemaType, schemaRepository); + var inlineSchemaForDocument = (OpenApiSchema)schemaRepository.Schemas[referenceSchemaForDocument.Reference.Id!].UnwrapLastExtendedSchema(); if (JsonApiSchemaFacts.HasNullableDataProperty(resourceSchemaType.SchemaOpenType)) { - inlineSchemaForDocument.Properties[JsonApiPropertyName.Data].Nullable = true; + inlineSchemaForDocument.Properties ??= []; + ((OpenApiSchema)inlineSchemaForDocument.Properties[JsonApiPropertyName.Data]).SetNullable(true); } return referenceSchemaForDocument; diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/GenerationCacheSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/GenerationCacheSchemaGenerator.cs index beba632ebf..2bd2963fd9 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/GenerationCacheSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/GenerationCacheSchemaGenerator.cs @@ -2,8 +2,8 @@ using JsonApiDotNetCore.OpenApi.Swashbuckle.JsonApiMetadata; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators; @@ -38,30 +38,29 @@ public bool HasAtomicOperationsEndpoint(SchemaRepository schemaRepository) OpenApiSchema fullSchema = GenerateFullSchema(schemaRepository); - var hasAtomicOperationsEndpoint = (OpenApiBoolean)fullSchema.Properties[HasAtomicOperationsEndpointPropertyName].Default; - return hasAtomicOperationsEndpoint.Value; + return fullSchema.Properties != null && (bool)fullSchema.Properties[HasAtomicOperationsEndpointPropertyName].Default!; } private OpenApiSchema GenerateFullSchema(SchemaRepository schemaRepository) { - if (schemaRepository.Schemas.TryGetValue(SchemaId, out OpenApiSchema? fullSchema)) + if (schemaRepository.Schemas.TryGetValue(SchemaId, out IOpenApiSchema? existingFullSchema)) { - return fullSchema; + return (OpenApiSchema)existingFullSchema; } using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this); bool hasAtomicOperationsEndpoint = EvaluateHasAtomicOperationsEndpoint(); - fullSchema = new OpenApiSchema + var fullSchema = new OpenApiSchema { - Type = "object", - Properties = new Dictionary + Type = JsonSchemaType.Object, + Properties = new Dictionary { - [HasAtomicOperationsEndpointPropertyName] = new() + [HasAtomicOperationsEndpointPropertyName] = new OpenApiSchema { - Type = "boolean", - Default = new OpenApiBoolean(hasAtomicOperationsEndpoint) + Type = JsonSchemaType.Boolean, + Default = hasAtomicOperationsEndpoint } } }; diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/JsonApiSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/JsonApiSchemaGenerator.cs index 19d94eb48e..7d22a3b7e1 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/JsonApiSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/JsonApiSchemaGenerator.cs @@ -3,7 +3,7 @@ using JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; using JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Documents; using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators; @@ -22,7 +22,7 @@ public JsonApiSchemaGenerator(ResourceIdSchemaGenerator resourceIdSchemaGenerato _documentSchemaGenerators = documentSchemaGenerators as DocumentSchemaGenerator[] ?? documentSchemaGenerators.ToArray(); } - public OpenApiSchema GenerateSchema(Type schemaType, SchemaRepository schemaRepository, MemberInfo? memberInfo = null, ParameterInfo? parameterInfo = null, + public IOpenApiSchema GenerateSchema(Type schemaType, SchemaRepository schemaRepository, MemberInfo? memberInfo = null, ParameterInfo? parameterInfo = null, ApiParameterRouteInfo? routeInfo = null) { ArgumentNullException.ThrowIfNull(schemaType); @@ -34,7 +34,7 @@ public OpenApiSchema GenerateSchema(Type schemaType, SchemaRepository schemaRepo } DocumentSchemaGenerator schemaGenerator = GetDocumentSchemaGenerator(schemaType); - OpenApiSchema referenceSchema = schemaGenerator.GenerateSchema(schemaType, schemaRepository); + IOpenApiSchema referenceSchema = schemaGenerator.GenerateSchema(schemaType, schemaRepository); if (memberInfo != null || parameterInfo != null) { diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaRepositoryExtensions.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaRepositoryExtensions.cs index 669c50b4e0..71a3c62135 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaRepositoryExtensions.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaRepositoryExtensions.cs @@ -1,5 +1,6 @@ using System.Reflection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle; @@ -28,12 +29,12 @@ private static FieldInfo GetReservedIdsField() return field; } - public static OpenApiSchema LookupByType(this SchemaRepository schemaRepository, Type schemaType) + public static OpenApiSchemaReference LookupByType(this SchemaRepository schemaRepository, Type schemaType) { ArgumentNullException.ThrowIfNull(schemaRepository); ArgumentNullException.ThrowIfNull(schemaType); - if (!schemaRepository.TryLookupByType(schemaType, out OpenApiSchema? referenceSchema)) + if (!schemaRepository.TryLookupByType(schemaType, out OpenApiSchemaReference? referenceSchema)) { throw new InvalidOperationException($"Reference schema for '{schemaType.Name}' does not exist."); } @@ -47,11 +48,11 @@ public static void ReplaceSchemaId(this SchemaRepository schemaRepository, Type ArgumentNullException.ThrowIfNull(oldSchemaType); ArgumentException.ThrowIfNullOrEmpty(newSchemaId); - if (schemaRepository.TryLookupByType(oldSchemaType, out OpenApiSchema? referenceSchema)) + if (schemaRepository.TryLookupByType(oldSchemaType, out OpenApiSchemaReference? referenceSchema)) { - string oldSchemaId = referenceSchema.Reference.Id; + string oldSchemaId = referenceSchema.Reference.Id!; - OpenApiSchema fullSchema = schemaRepository.Schemas[oldSchemaId]; + IOpenApiSchema? fullSchema = schemaRepository.Schemas[oldSchemaId]; schemaRepository.Schemas.Remove(oldSchemaId); schemaRepository.Schemas.Add(newSchemaId, fullSchema); diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SetSchemaTypeToObjectDocumentFilter.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SetSchemaTypeToObjectDocumentFilter.cs index 2764f868e6..69aef99066 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SetSchemaTypeToObjectDocumentFilter.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SetSchemaTypeToObjectDocumentFilter.cs @@ -11,12 +11,15 @@ internal sealed class SetSchemaTypeToObjectDocumentFilter : IDocumentFilter public void Apply(OpenApiDocument document, DocumentFilterContext context) { - foreach (OpenApiSchema schema in document.Components.Schemas.Values) + if (document.Components?.Schemas != null) { - if (schema.Extensions.ContainsKey(RequiresRootObjectTypeKey)) + foreach (OpenApiSchema schema in document.Components.Schemas.Values.OfType()) { - schema.Type = "object"; - schema.Extensions.Remove(RequiresRootObjectTypeKey); + if (schema.Extensions != null && schema.Extensions.ContainsKey(RequiresRootObjectTypeKey)) + { + schema.Type = JsonSchemaType.Object; + schema.Extensions.Remove(RequiresRootObjectTypeKey); + } } } } diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SortSchemasFilter.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SortSchemasFilter.cs new file mode 100644 index 0000000000..1b90db881e --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SortSchemasFilter.cs @@ -0,0 +1,57 @@ +using JetBrains.Annotations; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models.References; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace JsonApiDotNetCore.OpenApi.Swashbuckle; + +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +internal sealed class SortSchemasFilter : IDocumentFilter +{ + // Should use SwaggerGeneratorOptions.SchemaComparer + private static readonly StringComparer DefaultStringComparer = StringComparer.Ordinal; + + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + // Workaround for the change that ordering is no longer applied. See https://github.com/microsoft/OpenAPI.NET/issues/1314#issuecomment-2836828481. + // Pending: https://github.com/microsoft/OpenAPI.NET/issues/2342 + + swaggerDoc.Components ??= new OpenApiComponents(); + + if (swaggerDoc.Components.Schemas != null) + { + swaggerDoc.Components.Schemas = new SortedDictionary(swaggerDoc.Components.Schemas, DefaultStringComparer).ToDictionary(); + + foreach (IOpenApiSchema schema in swaggerDoc.Components.Schemas.Values) + { + SortSchema(schema); + } + } + } + + private static void SortSchema(IOpenApiSchema schema) + { + if (schema is OpenApiSchema concreteSchema) + { + if (concreteSchema.Required != null) + { + concreteSchema.Required = new SortedSet(concreteSchema.Required, DefaultStringComparer).ToHashSet(); + } + + if (concreteSchema.Discriminator?.Mapping != null) + { + concreteSchema.Discriminator.Mapping = + new SortedDictionary(concreteSchema.Discriminator.Mapping, DefaultStringComparer).ToDictionary(); + } + + if (concreteSchema.AllOf != null) + { + foreach (IOpenApiSchema subSchema in concreteSchema.AllOf) + { + SortSchema(subSchema); + } + } + } + } +} diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/DocumentationOpenApiOperationFilter.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/DocumentationOpenApiOperationFilter.cs index 1b5c0d5f4c..1e1ea7b6cc 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/DocumentationOpenApiOperationFilter.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/DocumentationOpenApiOperationFilter.cs @@ -9,10 +9,12 @@ using JsonApiDotNetCore.Resources.Annotations; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.Net.Http.Headers; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using Swashbuckle.AspNetCore.SwaggerGen; +#pragma warning disable AV1568 // Parameter value should not be overwritten in method body + namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SwaggerComponents; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] @@ -78,7 +80,7 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) if (hasHeadVerb) { - operation.Responses.Clear(); + operation.Responses?.Clear(); } MethodInfo actionMethod = context.ApiDescription.ActionDescriptor.GetActionMethod(); @@ -162,7 +164,7 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) private static void ApplyGetPrimary(OpenApiOperation operation, ResourceType resourceType, bool hasHeadVerb) { - if (operation.Parameters.Count == 0) + if (operation.Parameters == null || operation.Parameters.Count == 0) { if (hasHeadVerb) { @@ -192,7 +194,7 @@ private static void ApplyGetPrimary(OpenApiOperation operation, ResourceType res } else if (operation.Parameters.Count == 1) { - string singularName = resourceType.PublicName.Singularize(); + string? singularName = resourceType.PublicName.Singularize(); if (hasHeadVerb) { @@ -223,7 +225,7 @@ private static void ApplyGetPrimary(OpenApiOperation operation, ResourceType res private void ApplyPostResource(OpenApiOperation operation, ResourceType resourceType) { - string singularName = resourceType.PublicName.Singularize(); + string? singularName = resourceType.PublicName.Singularize(); SetOperationSummary(operation, $"Creates a new {singularName}."); AddQueryStringParameters(operation, false); @@ -253,10 +255,10 @@ private void ApplyPostResource(OpenApiOperation operation, ResourceType resource private void ApplyPatchResource(OpenApiOperation operation, ResourceType resourceType) { - string singularName = resourceType.PublicName.Singularize(); + string? singularName = resourceType.PublicName.Singularize(); SetOperationSummary(operation, $"Updates an existing {singularName}."); - SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularName} to update."); + SetParameterDescription(operation.Parameters![0], $"The identifier of the {singularName} to update."); AddQueryStringParameters(operation, false); SetRequestBodyDescription(operation.RequestBody, @@ -276,18 +278,18 @@ private void ApplyPatchResource(OpenApiOperation operation, ResourceType resourc private void ApplyDeleteResource(OpenApiOperation operation, ResourceType resourceType) { - string singularName = resourceType.PublicName.Singularize(); + string? singularName = resourceType.PublicName.Singularize(); SetOperationSummary(operation, $"Deletes an existing {singularName} by its identifier."); - SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularName} to delete."); + SetParameterDescription(operation.Parameters![0], $"The identifier of the {singularName} to delete."); SetResponseDescription(operation.Responses, HttpStatusCode.NoContent, $"The {singularName} was successfully deleted."); SetResponseDescription(operation.Responses, HttpStatusCode.NotFound, $"The {singularName} does not exist."); } private static void ApplyGetSecondary(OpenApiOperation operation, RelationshipAttribute relationship, bool hasHeadVerb) { - string singularLeftName = relationship.LeftType.PublicName.Singularize(); - string rightName = relationship is HasOneAttribute ? relationship.RightType.PublicName.Singularize() : relationship.RightType.PublicName; + string? singularLeftName = relationship.LeftType.PublicName.Singularize(); + string? rightName = relationship is HasOneAttribute ? relationship.RightType.PublicName.Singularize() : relationship.RightType.PublicName; if (hasHeadVerb) { @@ -317,7 +319,7 @@ relationship is HasOneAttribute SetResponseHeaderETag(operation.Responses, HttpStatusCode.NotModified); } - SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularLeftName} whose related {rightName} to retrieve."); + SetParameterDescription(operation.Parameters![0], $"The identifier of the {singularLeftName} whose related {rightName} to retrieve."); AddQueryStringParameters(operation, false); AddRequestHeaderIfNoneMatch(operation); SetResponseDescription(operation.Responses, HttpStatusCode.BadRequest, TextQueryStringBad); @@ -326,8 +328,8 @@ relationship is HasOneAttribute private static void ApplyGetRelationship(OpenApiOperation operation, RelationshipAttribute relationship, bool hasHeadVerb) { - string singularLeftName = relationship.LeftType.PublicName.Singularize(); - string singularRightName = relationship.RightType.PublicName.Singularize(); + string? singularLeftName = relationship.LeftType.PublicName.Singularize(); + string? singularRightName = relationship.RightType.PublicName.Singularize(); string ident = relationship is HasOneAttribute ? "identity" : "identities"; if (hasHeadVerb) @@ -359,7 +361,7 @@ relationship is HasOneAttribute SetResponseHeaderETag(operation.Responses, HttpStatusCode.NotModified); } - SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularLeftName} whose related {singularRightName} {ident} to retrieve."); + SetParameterDescription(operation.Parameters![0], $"The identifier of the {singularLeftName} whose related {singularRightName} {ident} to retrieve."); AddQueryStringParameters(operation, true); AddRequestHeaderIfNoneMatch(operation); SetResponseDescription(operation.Responses, HttpStatusCode.BadRequest, TextQueryStringBad); @@ -368,11 +370,11 @@ relationship is HasOneAttribute private void ApplyPostRelationship(OpenApiOperation operation, RelationshipAttribute relationship) { - string singularLeftName = relationship.LeftType.PublicName.Singularize(); + string? singularLeftName = relationship.LeftType.PublicName.Singularize(); string rightName = relationship.RightType.PublicName; SetOperationSummary(operation, $"Adds existing {rightName} to the {relationship} relationship of an individual {singularLeftName}."); - SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularLeftName} to add {rightName} to."); + SetParameterDescription(operation.Parameters![0], $"The identifier of the {singularLeftName} to add {rightName} to."); SetRequestBodyDescription(operation.RequestBody, $"The identities of the {rightName} to add to the {relationship} relationship."); SetResponseDescription(operation.Responses, HttpStatusCode.NoContent, @@ -387,8 +389,8 @@ private void ApplyPostRelationship(OpenApiOperation operation, RelationshipAttri private void ApplyPatchRelationship(OpenApiOperation operation, RelationshipAttribute relationship) { bool isOptional = _resourceFieldValidationMetadataProvider.IsNullable(relationship); - string singularLeftName = relationship.LeftType.PublicName.Singularize(); - string rightName = relationship is HasOneAttribute ? relationship.RightType.PublicName.Singularize() : relationship.RightType.PublicName; + string? singularLeftName = relationship.LeftType.PublicName.Singularize(); + string? rightName = relationship is HasOneAttribute ? relationship.RightType.PublicName.Singularize() : relationship.RightType.PublicName; SetOperationSummary(operation, relationship is HasOneAttribute @@ -397,7 +399,7 @@ relationship is HasOneAttribute : $"Assigns an existing {rightName} to the {relationship} relationship of an individual {singularLeftName}." : $"Assigns existing {rightName} to the {relationship} relationship of an individual {singularLeftName}."); - SetParameterDescription(operation.Parameters[0], + SetParameterDescription(operation.Parameters![0], isOptional ? $"The identifier of the {singularLeftName} whose {relationship} relationship to assign or clear." : $"The identifier of the {singularLeftName} whose {relationship} relationship to assign."); @@ -420,11 +422,11 @@ relationship is HasOneAttribute private void ApplyDeleteRelationship(OpenApiOperation operation, RelationshipAttribute relationship) { - string singularLeftName = relationship.LeftType.PublicName.Singularize(); + string? singularLeftName = relationship.LeftType.PublicName.Singularize(); string rightName = relationship.RightType.PublicName; SetOperationSummary(operation, $"Removes existing {rightName} from the {relationship} relationship of an individual {singularLeftName}."); - SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularLeftName} to remove {rightName} from."); + SetParameterDescription(operation.Parameters![0], $"The identifier of the {singularLeftName} to remove {rightName} from."); SetRequestBodyDescription(operation.RequestBody, $"The identities of the {rightName} to remove from the {relationship} relationship."); SetResponseDescription(operation.Responses, HttpStatusCode.NoContent, @@ -454,64 +456,76 @@ private static void SetOperationRemarks(OpenApiOperation operation, string descr operation.Description = XmlCommentsTextHelper.Humanize(description); } - private static void SetParameterDescription(OpenApiParameter parameter, string description) + private static void SetParameterDescription(IOpenApiParameter parameter, string description) { parameter.Description = XmlCommentsTextHelper.Humanize(description); } - private static void SetRequestBodyDescription(OpenApiRequestBody requestBody, string description) + private static void SetRequestBodyDescription(IOpenApiRequestBody? requestBody, string description) { + requestBody ??= new OpenApiRequestBody(); requestBody.Description = XmlCommentsTextHelper.Humanize(description); } - private static void SetResponseDescription(OpenApiResponses responses, HttpStatusCode statusCode, string description) + private static void SetResponseDescription(OpenApiResponses? responses, HttpStatusCode statusCode, string description) { + responses ??= new OpenApiResponses(); + OpenApiResponse response = GetOrAddResponse(responses, statusCode); response.Description = XmlCommentsTextHelper.Humanize(description); } - private static void SetResponseHeaderETag(OpenApiResponses responses, HttpStatusCode statusCode) + private static void SetResponseHeaderETag(OpenApiResponses? responses, HttpStatusCode statusCode) { + responses ??= new OpenApiResponses(); OpenApiResponse response = GetOrAddResponse(responses, statusCode); + response.Headers ??= []; + response.Headers[HeaderNames.ETag] = new OpenApiHeader { Description = "A fingerprint of the HTTP response, which can be used in an If-None-Match header to only fetch changes.", Required = true, Schema = new OpenApiSchema { - Type = "string" + Type = JsonSchemaType.String } }; } - private static void SetResponseHeaderContentLength(OpenApiResponses responses, HttpStatusCode statusCode) + private static void SetResponseHeaderContentLength(OpenApiResponses? responses, HttpStatusCode statusCode) { + responses ??= new OpenApiResponses(); OpenApiResponse response = GetOrAddResponse(responses, statusCode); + response.Headers ??= []; + response.Headers[HeaderNames.ContentLength] = new OpenApiHeader { Description = "Size of the HTTP response body, in bytes.", Required = true, Schema = new OpenApiSchema { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" } }; } - private static void SetResponseHeaderLocation(OpenApiResponses responses, HttpStatusCode statusCode, string resourceName) + private static void SetResponseHeaderLocation(OpenApiResponses? responses, HttpStatusCode statusCode, string resourceName) { + responses ??= new OpenApiResponses(); OpenApiResponse response = GetOrAddResponse(responses, statusCode); + response.Headers ??= []; + response.Headers[HeaderNames.Location] = new OpenApiHeader { Description = $"The URL at which the newly created {resourceName} can be retrieved.", Required = true, Schema = new OpenApiSchema { - Type = "string", + Type = JsonSchemaType.String, Format = "uri" } }; @@ -521,13 +535,15 @@ private static OpenApiResponse GetOrAddResponse(OpenApiResponses responses, Http { string responseCode = ((int)statusCode).ToString(); - if (!responses.TryGetValue(responseCode, out OpenApiResponse? response)) + if (!responses.TryGetValue(responseCode, out IOpenApiResponse? response) || response is not OpenApiResponse) { response = new OpenApiResponse(); responses.Add(responseCode, response); } - return response; + var concreteResponse = (OpenApiResponse)response; + concreteResponse.Headers ??= new Dictionary(); + return concreteResponse; } private static void AddQueryStringParameters(OpenApiOperation operation, bool isRelationshipEndpoint) @@ -542,20 +558,21 @@ private static void AddQueryStringParameters(OpenApiOperation operation, bool is // - This makes NSwag produce a C# client with method signature: GetAsync(IDictionary? query) // when combined with true in the project file. + operation.Parameters ??= []; + operation.Parameters.Add(new OpenApiParameter { In = ParameterLocation.Query, Name = "query", Schema = new OpenApiSchema { - Type = "object", + Type = JsonSchemaType.Object, AdditionalProperties = new OpenApiSchema { - Type = "string", - Nullable = true + Type = JsonSchemaType.String | JsonSchemaType.Null }, // Prevent SwaggerUI from producing sample, which fails when used because unknown query string parameters are blocked by default. - Example = new OpenApiString(string.Empty) + Example = string.Empty }, Description = isRelationshipEndpoint ? RelationshipQueryStringParameters : ResourceQueryStringParameters }); @@ -563,6 +580,8 @@ private static void AddQueryStringParameters(OpenApiOperation operation, bool is private static void AddRequestHeaderIfNoneMatch(OpenApiOperation operation) { + operation.Parameters ??= []; + operation.Parameters.Add(new OpenApiParameter { In = ParameterLocation.Header, @@ -570,7 +589,7 @@ private static void AddRequestHeaderIfNoneMatch(OpenApiOperation operation) Description = "A list of ETags, resulting in HTTP status 304 without a body, if one of them matches the current fingerprint.", Schema = new OpenApiSchema { - Type = "string" + Type = JsonSchemaType.String } }); } diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/EndpointOrderingFilter.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/EndpointOrderingFilter.cs index 047ba4355a..fdeb97018b 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/EndpointOrderingFilter.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/EndpointOrderingFilter.cs @@ -1,6 +1,8 @@ using System.Text.RegularExpressions; using JetBrains.Annotations; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SwaggerComponents; @@ -13,28 +15,43 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) ArgumentNullException.ThrowIfNull(swaggerDoc); ArgumentNullException.ThrowIfNull(context); - KeyValuePair[] endpointsInOrder = swaggerDoc.Paths.OrderBy(GetPrimaryResourcePublicName) + KeyValuePair[] endpointsInOrder = swaggerDoc.Paths.OrderBy(GetPrimaryResourcePublicName) .ThenBy(GetRelationshipName).ThenBy(IsSecondaryEndpoint).ToArray(); - swaggerDoc.Paths.Clear(); + swaggerDoc.Paths = new OpenApiPaths(); - foreach ((string url, OpenApiPathItem path) in endpointsInOrder) + foreach ((string url, IOpenApiPathItem path) in endpointsInOrder) { swaggerDoc.Paths.Add(url, path); } } - private static string GetPrimaryResourcePublicName(KeyValuePair entry) + private static string GetPrimaryResourcePublicName(KeyValuePair entry) { - return entry.Value.Operations.First().Value.Tags.First().Name; + if (entry.Value.Operations is { Count: > 0 }) + { + ISet? references = entry.Value.Operations.First().Value.Tags; + + if (references is { Count: > 0 }) + { + OpenApiTagReference openApiTagReference = references.First(); + + if (openApiTagReference.Name != null) + { + return openApiTagReference.Name; + } + } + } + + throw new InvalidOperationException($"Failed to find tag value for endpoint '{entry.Key}'."); } - private static bool IsSecondaryEndpoint(KeyValuePair entry) + private static bool IsSecondaryEndpoint(KeyValuePair entry) { return entry.Key.Contains("/relationships"); } - private static string GetRelationshipName(KeyValuePair entry) + private static string GetRelationshipName(KeyValuePair entry) { Match match = RelationshipNameInUrlRegex().Match(entry.Key); diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/JsonApiDataContractResolver.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/JsonApiDataContractResolver.cs index dcc652c696..ba1cc86d99 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/JsonApiDataContractResolver.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/JsonApiDataContractResolver.cs @@ -33,7 +33,7 @@ public DataContract GetDataContractForType(Type type) return DataContract.ForDynamic(typeof(object)); } - DataContract dataContract = _dataContractResolver.GetDataContractForType(type); + DataContract? dataContract = _dataContractResolver.GetDataContractForType(type); IList? replacementProperties = null; @@ -61,7 +61,7 @@ private List GetDataPropertiesThatExistInResourceClrType(Type reso ResourceType resourceType = _resourceGraph.GetResourceType(resourceClrType); List dataProperties = []; - foreach (DataProperty property in dataContract.ObjectProperties) + foreach (DataProperty? property in dataContract.ObjectProperties) { if (property.MemberInfo.Name == nameof(Identifiable.Id)) { diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/ResourceDocumentationReader.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/ResourceDocumentationReader.cs index cd2727854c..27f053a938 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/ResourceDocumentationReader.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/ResourceDocumentationReader.cs @@ -20,7 +20,7 @@ internal sealed class ResourceDocumentationReader if (navigator != null) { - string typeMemberName = XmlCommentsNodeNameHelper.GetMemberNameForType(resourceType.ClrType); + string? typeMemberName = XmlCommentsNodeNameHelper.GetMemberNameForType(resourceType.ClrType); return GetSummary(navigator, typeMemberName); } @@ -35,7 +35,7 @@ internal sealed class ResourceDocumentationReader if (navigator != null) { - string propertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(attribute.Property); + string? propertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(attribute.Property); return GetSummary(navigator, propertyMemberName); } @@ -50,7 +50,7 @@ internal sealed class ResourceDocumentationReader if (navigator != null) { - string propertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(relationship.Property); + string? propertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(relationship.Property); return GetSummary(navigator, propertyMemberName); } diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/ResourceFieldSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/ResourceFieldSchemaBuilder.cs index 31bf7be625..fdb31f5db4 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/ResourceFieldSchemaBuilder.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/ResourceFieldSchemaBuilder.cs @@ -4,6 +4,8 @@ using JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators.Components; using JsonApiDotNetCore.Resources.Annotations; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models.References; using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SwaggerComponents; @@ -20,7 +22,7 @@ internal sealed class ResourceFieldSchemaBuilder private readonly SchemaRepository _resourceSchemaRepository = new(); private readonly ResourceDocumentationReader _resourceDocumentationReader = new(); - private readonly IDictionary _schemasForResourceFields; + private readonly IDictionary _schemasForResourceFields; public ResourceFieldSchemaBuilder(SchemaGenerationTracer schemaGenerationTracer, SchemaGenerator defaultSchemaGenerator, DataSchemaGenerator dataSchemaGenerator, LinksVisibilitySchemaGenerator linksVisibilitySchemaGenerator, @@ -46,15 +48,16 @@ public ResourceFieldSchemaBuilder(SchemaGenerationTracer schemaGenerationTracer, _schemasForResourceFields = GetFieldSchemas(); } - private IDictionary GetFieldSchemas() + private Dictionary GetFieldSchemas() { - if (!_resourceSchemaRepository.TryLookupByType(_resourceSchemaType.ResourceType.ClrType, out OpenApiSchema referenceSchemaForResource)) + if (!_resourceSchemaRepository.TryLookupByType(_resourceSchemaType.ResourceType.ClrType, out OpenApiSchemaReference? referenceSchemaForResource)) { - referenceSchemaForResource = _defaultSchemaGenerator.GenerateSchema(_resourceSchemaType.ResourceType.ClrType, _resourceSchemaRepository); + referenceSchemaForResource = + (OpenApiSchemaReference)_defaultSchemaGenerator.GenerateSchema(_resourceSchemaType.ResourceType.ClrType, _resourceSchemaRepository); } - OpenApiSchema inlineSchemaForResource = _resourceSchemaRepository.Schemas[referenceSchemaForResource.Reference.Id].UnwrapLastExtendedSchema(); - return inlineSchemaForResource.Properties; + IOpenApiSchema inlineSchemaForResource = _resourceSchemaRepository.Schemas[referenceSchemaForResource.Reference.Id!].UnwrapLastExtendedSchema(); + return inlineSchemaForResource.Properties ?? []; } public void SetMembersOfAttributes(OpenApiSchema fullSchemaForAttributes, bool forRequestSchema, SchemaRepository schemaRepository) @@ -65,7 +68,7 @@ public void SetMembersOfAttributes(OpenApiSchema fullSchemaForAttributes, bool f AttrCapabilities requiredCapability = GetRequiredCapabilityForAttributes(_resourceSchemaType.SchemaOpenType); - foreach ((string publicName, OpenApiSchema schemaForResourceField) in _schemasForResourceFields) + foreach ((string publicName, IOpenApiSchema schemaForResourceField) in _schemasForResourceFields) { AttrAttribute? matchingAttribute = _resourceSchemaType.ResourceType.FindAttributeByPublicName(publicName); @@ -86,21 +89,23 @@ public void SetMembersOfAttributes(OpenApiSchema fullSchemaForAttributes, bool f } } - bool isInlineSchemaType = schemaForResourceField.AllOf.Count == 0; + bool isInlineSchemaType = schemaForResourceField.AllOf == null || schemaForResourceField.AllOf.Count == 0; // Schemas for types like enum and complex attributes are handled as reference schemas. if (!isInlineSchemaType) { - OpenApiSchema referenceSchemaForAttribute = schemaForResourceField.UnwrapLastExtendedSchema(); + var referenceSchemaForAttribute = (OpenApiSchemaReference)schemaForResourceField.UnwrapLastExtendedSchema(); EnsureAttributeSchemaIsExposed(referenceSchemaForAttribute, matchingAttribute, schemaRepository); } + fullSchemaForAttributes.Properties ??= []; fullSchemaForAttributes.Properties.Add(matchingAttribute.PublicName, schemaForResourceField); - schemaForResourceField.Nullable = _resourceFieldValidationMetadataProvider.IsNullable(matchingAttribute); + ((OpenApiSchema)schemaForResourceField).SetNullable(_resourceFieldValidationMetadataProvider.IsNullable(matchingAttribute)); if (IsFieldRequired(matchingAttribute)) { + fullSchemaForAttributes.Required ??= []; fullSchemaForAttributes.Required.Add(matchingAttribute.PublicName); } @@ -130,7 +135,7 @@ private static AttrCapabilities GetRequiredCapabilityForAttributes(Type resource return capabilities.Value; } - private void EnsureAttributeSchemaIsExposed(OpenApiSchema referenceSchemaForAttribute, AttrAttribute attribute, SchemaRepository schemaRepository) + private void EnsureAttributeSchemaIsExposed(OpenApiSchemaReference referenceSchemaForAttribute, AttrAttribute attribute, SchemaRepository schemaRepository) { Type nonNullableTypeInPropertyType = GetRepresentedTypeForAttributeSchema(attribute); @@ -141,8 +146,8 @@ private void EnsureAttributeSchemaIsExposed(OpenApiSchema referenceSchemaForAttr using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, nonNullableTypeInPropertyType); - string schemaId = referenceSchemaForAttribute.Reference.Id; - OpenApiSchema fullSchema = _resourceSchemaRepository.Schemas[schemaId]; + string schemaId = referenceSchemaForAttribute.Reference.Id!; + var fullSchema = (OpenApiSchema)_resourceSchemaRepository.Schemas[schemaId]; schemaRepository.AddDefinition(schemaId, fullSchema); schemaRepository.RegisterType(nonNullableTypeInPropertyType, schemaId); @@ -196,16 +201,18 @@ private void AddRelationshipSchemaToResourceData(RelationshipAttribute relations { Type relationshipSchemaType = GetRelationshipSchemaType(relationship, _resourceSchemaType.SchemaOpenType); - OpenApiSchema referenceSchemaForRelationship = GetReferenceSchemaForRelationship(relationshipSchemaType, schemaRepository) ?? + OpenApiSchemaReference referenceSchemaForRelationship = GetReferenceSchemaForRelationship(relationshipSchemaType, schemaRepository) ?? CreateReferenceSchemaForRelationship(relationshipSchemaType, schemaRepository); OpenApiSchema extendedReferenceSchemaForRelationship = referenceSchemaForRelationship.WrapInExtendedSchema(); extendedReferenceSchemaForRelationship.Description = _resourceDocumentationReader.GetDocumentationForRelationship(relationship); + fullSchemaForRelationships.Properties ??= []; fullSchemaForRelationships.Properties.Add(relationship.PublicName, extendedReferenceSchemaForRelationship); if (IsFieldRequired(relationship)) { + fullSchemaForRelationships.Required ??= []; fullSchemaForRelationships.Required.Add(relationship.PublicName); } } @@ -216,22 +223,23 @@ private Type GetRelationshipSchemaType(RelationshipAttribute relationship, Type return isResponseSchemaType ? _relationshipTypeFactory.GetForResponse(relationship) : _relationshipTypeFactory.GetForRequest(relationship); } - private OpenApiSchema? GetReferenceSchemaForRelationship(Type relationshipSchemaType, SchemaRepository schemaRepository) + private OpenApiSchemaReference? GetReferenceSchemaForRelationship(Type relationshipSchemaType, SchemaRepository schemaRepository) { - return schemaRepository.TryLookupByType(relationshipSchemaType, out OpenApiSchema? referenceSchema) ? referenceSchema : null; + return schemaRepository.TryLookupByType(relationshipSchemaType, out OpenApiSchemaReference? referenceSchema) ? referenceSchema : null; } - private OpenApiSchema CreateReferenceSchemaForRelationship(Type relationshipSchemaType, SchemaRepository schemaRepository) + private OpenApiSchemaReference CreateReferenceSchemaForRelationship(Type relationshipSchemaType, SchemaRepository schemaRepository) { using ISchemaGenerationTraceScope traceScope = _schemaGenerationTracer.TraceStart(this, relationshipSchemaType); - OpenApiSchema referenceSchema = _defaultSchemaGenerator.GenerateSchema(relationshipSchemaType, schemaRepository); + var referenceSchema = (OpenApiSchemaReference)_defaultSchemaGenerator.GenerateSchema(relationshipSchemaType, schemaRepository); - OpenApiSchema fullSchema = schemaRepository.Schemas[referenceSchema.Reference.Id]; + var fullSchema = (OpenApiSchema)schemaRepository.Schemas[referenceSchema.Reference.Id!]; if (JsonApiSchemaFacts.HasNullableDataProperty(relationshipSchemaType)) { - fullSchema.Properties[JsonApiPropertyName.Data].Nullable = true; + fullSchema.Properties ??= []; + ((OpenApiSchema)fullSchema.Properties[JsonApiPropertyName.Data]).SetNullable(true); } if (JsonApiSchemaFacts.IsRelationshipInResponseType(relationshipSchemaType)) @@ -239,12 +247,12 @@ private OpenApiSchema CreateReferenceSchemaForRelationship(Type relationshipSche _linksVisibilitySchemaGenerator.UpdateSchemaForRelationship(relationshipSchemaType, fullSchema, schemaRepository); } - traceScope.TraceSucceeded(referenceSchema.Reference.Id); + traceScope.TraceSucceeded(referenceSchema.Reference.Id!); return referenceSchema; } private static void AssertHasNoProperties(OpenApiSchema fullSchema) { - ConsistencyGuard.ThrowIf(fullSchema.Properties.Count > 0); + ConsistencyGuard.ThrowIf(fullSchema.Properties is { Count: > 0 }); } } diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/ServerDocumentFilter.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/ServerDocumentFilter.cs index 723499d39c..e6a0d9469c 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/ServerDocumentFilter.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/ServerDocumentFilter.cs @@ -19,6 +19,8 @@ public ServerDocumentFilter(IHttpContextAccessor httpContextAccessor) public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { + swaggerDoc.Servers ??= []; + if (swaggerDoc.Servers.Count == 0 && _httpContextAccessor.HttpContext != null) { HttpRequest httpRequest = _httpContextAccessor.HttpContext.Request; diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/StringEnumOrderingFilter.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/StringEnumOrderingFilter.cs index dec10db97b..fc8592deb0 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/StringEnumOrderingFilter.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/StringEnumOrderingFilter.cs @@ -1,7 +1,10 @@ +using System.Text.Json; +using System.Text.Json.Nodes; using JetBrains.Annotations; -using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using Microsoft.OpenApi.Services; using Swashbuckle.AspNetCore.SwaggerGen; @@ -24,31 +27,32 @@ public void Apply(OpenApiDocument document, DocumentFilterContext context) private sealed class OpenApiEnumVisitor : OpenApiVisitorBase { - public override void Visit(OpenApiSchema schema) + public override void Visit(IOpenApiSchema schema) { - if (HasSortAnnotation(schema)) + if (schema is OpenApiSchema { Extensions: not null } concreteSchema && HasSortAnnotation(concreteSchema.Extensions)) { - if (schema.Enum.Count > 1) - { - OrderEnumMembers(schema); - } + OrderEnumMembers(concreteSchema); } - schema.Extensions.Remove(RequiresSortKey); + schema.Extensions?.Remove(RequiresSortKey); } - private static bool HasSortAnnotation(OpenApiSchema schema) + private static bool HasSortAnnotation(Dictionary schemaExtensions) { // Order our own enums, but don't touch enums from user-defined resource attributes. - return schema.Extensions.TryGetValue(RequiresSortKey, out IOpenApiExtension? extension) && extension is OpenApiBoolean { Value: true }; + return schemaExtensions.TryGetValue(RequiresSortKey, out IOpenApiExtension? extension) && + extension is JsonNodeExtension { Node: JsonValue value } && value.GetValueKind() == JsonValueKind.True; } private static void OrderEnumMembers(OpenApiSchema schema) { - List ordered = schema.Enum.OfType().OrderBy(openApiString => openApiString.Value).Cast().ToList(); - ConsistencyGuard.ThrowIf(ordered.Count != schema.Enum.Count); + if (schema.Enum is { Count: > 1 }) + { + List ordered = schema.Enum.OrderBy(node => node.ToString()).ToList(); + ConsistencyGuard.ThrowIf(ordered.Count != schema.Enum.Count); - schema.Enum = ordered; + schema.Enum = ordered; + } } } } diff --git a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/UnusedComponentSchemaCleaner.cs b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/UnusedComponentSchemaCleaner.cs index 4e2addabb3..27196d0b17 100644 --- a/src/JsonApiDotNetCore.OpenApi.Swashbuckle/UnusedComponentSchemaCleaner.cs +++ b/src/JsonApiDotNetCore.OpenApi.Swashbuckle/UnusedComponentSchemaCleaner.cs @@ -3,6 +3,8 @@ using JsonApiDotNetCore.OpenApi.Swashbuckle.SchemaGenerators; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Services; using Swashbuckle.AspNetCore.SwaggerGen; @@ -21,6 +23,8 @@ public void Apply(OpenApiDocument document, DocumentFilterContext context) ArgumentNullException.ThrowIfNull(document); ArgumentNullException.ThrowIfNull(context); + document.Components ??= new OpenApiComponents(); + document.Components.Schemas ??= new Dictionary(); document.Components.Schemas.Remove(GenerationCacheSchemaGenerator.SchemaId); HashSet unusedSchemaIds = GetUnusedSchemaIds(document); @@ -50,6 +54,8 @@ private static void RemoveUnusedComponentSchemas(OpenApiDocument document, HashS { foreach (string schemaId in unusedSchemaIds) { + document.Components ??= new OpenApiComponents(); + document.Components.Schemas ??= new Dictionary(); document.Components.Schemas.Remove(schemaId); } } @@ -78,11 +84,11 @@ private sealed class ComponentSchemaReferenceVisitor : OpenApiVisitorBase { public HashSet ReachableSchemaIds { get; } = []; - public override void Visit(IOpenApiReferenceable referenceable) + public override void Visit(IOpenApiReferenceHolder referenceHolder) { if (!PathString.StartsWith(ComponentSchemaPrefix, StringComparison.Ordinal)) { - if (referenceable is OpenApiSchema schema) + if (referenceHolder is OpenApiSchemaReference { Reference.Id: not null } schema) { ReachableSchemaIds.Add(schema.Reference.Id); } @@ -93,13 +99,16 @@ public override void Visit(IOpenApiReferenceable referenceable) private sealed class ComponentSchemaUsageCollector { - private readonly IDictionary _componentSchemas; + private static readonly Dictionary.ValueCollection EmptyValueCollection = new(new Dictionary()); + private readonly IDictionary _componentSchemas; private readonly HashSet _schemaIdsInUse = []; public ComponentSchemaUsageCollector(OpenApiDocument document) { ArgumentNullException.ThrowIfNull(document); + document.Components ??= new OpenApiComponents(); + document.Components.Schemas ??= new Dictionary(); _componentSchemas = document.Components.Schemas; } @@ -117,18 +126,18 @@ public HashSet CollectUnusedSchemaIds(ICollection reachableSchem return unusedSchemaIds; } - private void WalkSchemaId(string schemaId) + private void WalkSchemaId(string? schemaId) { - if (_schemaIdsInUse.Add(schemaId)) + if (schemaId != null && _schemaIdsInUse.Add(schemaId)) { - if (_componentSchemas.TryGetValue(schemaId, out OpenApiSchema? schema)) + if (_componentSchemas.TryGetValue(schemaId, out IOpenApiSchema? schema)) { WalkSchema(schema); } } } - private void WalkSchema(OpenApiSchema? schema) + private void WalkSchema(IOpenApiSchema? schema) { if (schema != null) { @@ -137,22 +146,22 @@ private void WalkSchema(OpenApiSchema? schema) WalkSchema(schema.Items); WalkSchema(schema.Not); - foreach (OpenApiSchema? subSchema in schema.AllOf) + foreach (IOpenApiSchema subSchema in schema.AllOf ?? []) { WalkSchema(subSchema); } - foreach (OpenApiSchema? subSchema in schema.AnyOf) + foreach (IOpenApiSchema subSchema in schema.AnyOf ?? []) { WalkSchema(subSchema); } - foreach (OpenApiSchema? subSchema in schema.OneOf) + foreach (IOpenApiSchema subSchema in schema.OneOf ?? []) { WalkSchema(subSchema); } - foreach (OpenApiSchema? subSchema in schema.Properties.Values) + foreach (IOpenApiSchema subSchema in schema.Properties?.Values ?? EmptyValueCollection) { WalkSchema(subSchema); } @@ -162,22 +171,18 @@ private void WalkSchema(OpenApiSchema? schema) } } - private void VisitSchema(OpenApiSchema schema) + private void VisitSchema(IOpenApiSchema schema) { - if (schema.Reference is { Type: ReferenceType.Schema, IsExternal: false }) + if (schema is OpenApiSchemaReference { Reference: { Type: ReferenceType.Schema, IsExternal: false } } refSchema) { - WalkSchemaId(schema.Reference.Id); + WalkSchemaId(refSchema.Reference.Id); } - if (schema.Discriminator != null) + if (schema.Discriminator is { Mapping: not null }) { - foreach (string mappingValue in schema.Discriminator.Mapping.Values) + foreach (OpenApiSchemaReference mappingValue in schema.Discriminator.Mapping.Values) { - if (mappingValue.StartsWith(ComponentSchemaPrefix, StringComparison.Ordinal)) - { - string schemaId = mappingValue[ComponentSchemaPrefix.Length..]; - WalkSchemaId(schemaId); - } + WalkSchemaId(mappingValue.Reference.Id); } } } diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index d36600e878..009c7d8656 100644 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 true true diff --git a/test/AnnotationTests/AnnotationTests.csproj b/test/AnnotationTests/AnnotationTests.csproj index 885e9f769c..6d2bfff1e6 100644 --- a/test/AnnotationTests/AnnotationTests.csproj +++ b/test/AnnotationTests/AnnotationTests.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0;netstandard2.0 + net10.0;net9.0;net8.0;netstandard2.0 diff --git a/test/DapperTests/DapperTests.csproj b/test/DapperTests/DapperTests.csproj index 7d41d78911..3aca3d377c 100644 --- a/test/DapperTests/DapperTests.csproj +++ b/test/DapperTests/DapperTests.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/test/DiscoveryTests/DiscoveryTests.csproj b/test/DiscoveryTests/DiscoveryTests.csproj index 11567b9113..f343d97296 100644 --- a/test/DiscoveryTests/DiscoveryTests.csproj +++ b/test/DiscoveryTests/DiscoveryTests.csproj @@ -1,6 +1,6 @@  - net9.0;net8.0 + net10.0 @@ -17,4 +17,10 @@ + + + + + + diff --git a/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj b/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj index bdfea682d9..6ef3bfd5a1 100644 --- a/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj +++ b/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj @@ -1,6 +1,6 @@ - + - net9.0;net8.0 + net10.0;net9.0;net8.0 @@ -19,6 +19,6 @@ - + diff --git a/test/MultiDbContextTests/MultiDbContextTests.csproj b/test/MultiDbContextTests/MultiDbContextTests.csproj index e80f03c69e..524270f7e2 100644 --- a/test/MultiDbContextTests/MultiDbContextTests.csproj +++ b/test/MultiDbContextTests/MultiDbContextTests.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj b/test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj index 4deb6b21cd..9ff82322ba 100644 --- a/test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj +++ b/test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/test/OpenApiKiotaEndToEndTests/OpenApiKiotaEndToEndTests.csproj b/test/OpenApiKiotaEndToEndTests/OpenApiKiotaEndToEndTests.csproj index ca0db62217..43742e6311 100644 --- a/test/OpenApiKiotaEndToEndTests/OpenApiKiotaEndToEndTests.csproj +++ b/test/OpenApiKiotaEndToEndTests/OpenApiKiotaEndToEndTests.csproj @@ -1,6 +1,6 @@ - net9.0 + net10.0 diff --git a/test/OpenApiNSwagClientTests/OpenApiNSwagClientTests.csproj b/test/OpenApiNSwagClientTests/OpenApiNSwagClientTests.csproj index 35a6ae73ff..eb8adc2586 100644 --- a/test/OpenApiNSwagClientTests/OpenApiNSwagClientTests.csproj +++ b/test/OpenApiNSwagClientTests/OpenApiNSwagClientTests.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 diff --git a/test/OpenApiNSwagEndToEndTests/OpenApiNSwagEndToEndTests.csproj b/test/OpenApiNSwagEndToEndTests/OpenApiNSwagEndToEndTests.csproj index 2fc34289fa..a28de8288c 100644 --- a/test/OpenApiNSwagEndToEndTests/OpenApiNSwagEndToEndTests.csproj +++ b/test/OpenApiNSwagEndToEndTests/OpenApiNSwagEndToEndTests.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 diff --git a/test/OpenApiTests/OpenApiTestContext.cs b/test/OpenApiTests/OpenApiTestContext.cs index 7743ab7533..fe6f0144fc 100644 --- a/test/OpenApiTests/OpenApiTestContext.cs +++ b/test/OpenApiTests/OpenApiTestContext.cs @@ -80,7 +80,10 @@ private async Task GetAsync(string requestUrl) private static JsonElement ParseSwaggerDocument(string content) { - using JsonDocument jsonDocument = JsonDocument.Parse(content); + // Temporary workaround for https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/3426. + string patchedContent = content.Replace("\"minimum\": 0,1", "\"minimum\": 0.1"); + + using JsonDocument jsonDocument = JsonDocument.Parse(patchedContent); return jsonDocument.RootElement.Clone(); } diff --git a/test/OpenApiTests/OpenApiTests.csproj b/test/OpenApiTests/OpenApiTests.csproj index ed2a75b5f4..771616b8fd 100644 --- a/test/OpenApiTests/OpenApiTests.csproj +++ b/test/OpenApiTests/OpenApiTests.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0 True false $(NoWarn);1591 diff --git a/test/SourceGeneratorTests/SourceGeneratorTests.csproj b/test/SourceGeneratorTests/SourceGeneratorTests.csproj index 4f487fa168..61946fbc1a 100644 --- a/test/SourceGeneratorTests/SourceGeneratorTests.csproj +++ b/test/SourceGeneratorTests/SourceGeneratorTests.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index 3f16e3818e..c27c3d22b8 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index 68076a51e1..503547d0c5 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0