From 99dcfa802c51643ae1279a14068783bae547300d Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Mon, 5 Sep 2022 15:46:09 +0200 Subject: [PATCH 01/26] Added tests for nullable and required properties in schema generation --- test/OpenApiTests/JsonElementExtensions.cs | 7 ++ .../ModelStateValidationDisabledStartup.cs | 19 +++++ .../NullableReferenceTypesDisabled/Chicken.cs | 34 +++++++++ .../ModelStateValidationDisabledTests.cs | 40 +++++++++++ .../ModelStateValidationEnabledTests.cs | 40 +++++++++++ .../NullabilityTests.cs | 62 ++++++++++++++++ ...NullableReferenceTypesDisabledDbContext.cs | 15 ++++ .../NullableReferenceTypesEnabled/Cow.cs | 39 ++++++++++ .../ModelStateValidationDisabledTests.cs | 42 +++++++++++ .../ModelStateValidationEnabledTests.cs | 42 +++++++++++ .../NullabilityTests.cs | 72 +++++++++++++++++++ .../NullableReferenceTypesEnabledDbContext.cs | 15 ++++ .../SchemaPropertiesStartup.cs | 18 +++++ 13 files changed, 445 insertions(+) create mode 100644 test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/Chicken.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs create mode 100644 test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs diff --git a/test/OpenApiTests/JsonElementExtensions.cs b/test/OpenApiTests/JsonElementExtensions.cs index d8dc7d4e20..6f1ffe1432 100644 --- a/test/OpenApiTests/JsonElementExtensions.cs +++ b/test/OpenApiTests/JsonElementExtensions.cs @@ -19,6 +19,13 @@ public static JsonElement ShouldContainPath(this JsonElement source, string path return elementSelector.Should().NotThrow().Subject; } + public static void ShouldNotContainPath(this JsonElement source, string path) + { + JsonElement? pathToken = source.SelectToken(path); + + pathToken.Should().BeNull(); + } + public static void ShouldBeString(this JsonElement source, string value) { source.ValueKind.Should().Be(JsonValueKind.String); diff --git a/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs b/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs new file mode 100644 index 0000000000..9895d7d61b --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; +using JetBrains.Annotations; +using JsonApiDotNetCore.Configuration; +using Microsoft.EntityFrameworkCore; + +namespace OpenApiTests.SchemaProperties; + +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +public sealed class ModelStateValidationDisabledStartup : OpenApiStartup + where TDbContext : DbContext +{ + protected override void SetJsonApiOptions(JsonApiOptions options) + { + base.SetJsonApiOptions(options); + + options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); + options.ValidateModelState = false; + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/Chicken.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/Chicken.cs new file mode 100644 index 0000000000..4865d33af9 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/Chicken.cs @@ -0,0 +1,34 @@ +#nullable disable + +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] +public sealed class Chicken : Identifiable +{ + [Attr] + public string Name { get; set; } + + [Attr] + [Required] + public string NameOfCurrentFarm { get; set; } + + [Attr] + public int Age { get; set; } + + [Attr] + [Required] + public int Weight { get; set; } + + [Attr] + public int? TimeAtCurrentFarmInDays { get; set; } + + [Attr] + [Required] + public bool? HasProducedEggs { get; set; } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs new file mode 100644 index 0000000000..838e2a6bc2 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs @@ -0,0 +1,40 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; + +public sealed class ModelStateValidationDisabledTests + : IClassFixture, NullableReferenceTypesDisabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> + _testContext; + + public ModelStateValidationDisabledTests( + OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + { + _testContext = testContext; + testContext.UseController(); + } + + [Fact] + public async Task Resource_when_ModelStateValidation_is_disabled_produces_expected_required_property_in_schema() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(requiredElement => + { + var requiredAttributes = JsonSerializer.Deserialize>(requiredElement.GetRawText()); + requiredAttributes.ShouldNotBeNull(); + + requiredAttributes.Should().Contain("nameOfCurrentFarm"); + requiredAttributes.Should().Contain("weight"); + requiredAttributes.Should().Contain("hasProducedEggs"); + + requiredAttributes.ShouldHaveCount(3); + }); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs new file mode 100644 index 0000000000..b1c39ca090 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs @@ -0,0 +1,40 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; + +public sealed class ModelStateValidationEnabledTests + : IClassFixture, NullableReferenceTypesDisabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; + + public ModelStateValidationEnabledTests( + OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Fact] + public async Task Resource_when_ModelStateValidation_is_enabled_produces_expected_required_property_in_schema() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(requiredElement => + { + var requiredAttributes = JsonSerializer.Deserialize>(requiredElement.GetRawText()); + requiredAttributes.ShouldNotBeNull(); + + requiredAttributes.Should().Contain("nameOfCurrentFarm"); + requiredAttributes.Should().Contain("weight"); + requiredAttributes.Should().Contain("hasProducedEggs"); + + requiredAttributes.ShouldHaveCount(3); + }); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs new file mode 100644 index 0000000000..e13be25d25 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs @@ -0,0 +1,62 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; + +public sealed class NullabilityTests + : IClassFixture, NullableReferenceTypesDisabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; + + public NullabilityTests( + OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled"; + } + + [Fact] + public async Task Resource_produces_expected_nullable_properties_in_schema() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(propertiesElement => + { + propertiesElement.ShouldContainPath("name").With(propertyElement => + { + propertyElement.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + + propertiesElement.ShouldContainPath("nameOfCurrentFarm").With(propertyElement => + { + propertyElement.ShouldNotContainPath("nullable"); + }); + + propertiesElement.ShouldContainPath("age").With(propertyElement => + { + propertyElement.ShouldNotContainPath("nullable"); + }); + + propertiesElement.ShouldContainPath("weight").With(propertyElement => + { + propertyElement.ShouldNotContainPath("nullable"); + }); + + propertiesElement.ShouldContainPath("timeAtCurrentFarmInDays").With(propertyElement => + { + propertyElement.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + + propertiesElement.ShouldContainPath("hasProducedEggs").With(propertyElement => + { + propertyElement.ShouldNotContainPath("nullable"); + }); + }); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs new file mode 100644 index 0000000000..1208a369ef --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs @@ -0,0 +1,15 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class NullableReferenceTypesDisabledDbContext : DbContext +{ + public DbSet Chicken => Set(); + + public NullableReferenceTypesDisabledDbContext(DbContextOptions options) + : base(options) + { + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs new file mode 100644 index 0000000000..3b5562a0b3 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs @@ -0,0 +1,39 @@ +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] +public sealed class Cow : Identifiable +{ + [Attr] + public string Name { get; set; } = null!; + + [Attr] + [Required] + public string NameOfCurrentFarm { get; set; } = null!; + + [Attr] + public string? NameOfPreviousFarm { get; set; } + + [Attr] + [Required] + public string? Nickname { get; set; } + + [Attr] + public int Age { get; set; } + + [Attr] + [Required] + public int Weight { get; set; } + + [Attr] + public int? TimeAtCurrentFarmInDays { get; set; } + + [Attr] + [Required] + public bool? HasProducedMilk { get; set; } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs new file mode 100644 index 0000000000..f57c3bf4bc --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs @@ -0,0 +1,42 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; + +public sealed class ModelStateValidationDisabledTests + : IClassFixture, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> + _testContext; + + public ModelStateValidationDisabledTests( + OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Fact] + public async Task Resource_when_ModelStateValidation_is_disabled_produces_expected_required_property_in_schema() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(requiredElement => + { + var requiredAttributes = JsonSerializer.Deserialize>(requiredElement.GetRawText()); + requiredAttributes.ShouldNotBeNull(); + + requiredAttributes.Should().Contain("nameOfCurrentFarm"); + requiredAttributes.Should().Contain("nickname"); + requiredAttributes.Should().Contain("weight"); + requiredAttributes.Should().Contain("hasProducedMilk"); + + requiredAttributes.ShouldHaveCount(4); + }); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs new file mode 100644 index 0000000000..7cb98fceda --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs @@ -0,0 +1,42 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; + +public sealed class ModelStateValidationEnabledTests + : IClassFixture, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; + + public ModelStateValidationEnabledTests( + OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Fact] + public async Task Resource_when_ModelStateValidation_is_enabled_produces_expected_required_property_in_schema() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(requiredElement => + { + var requiredAttributes = JsonSerializer.Deserialize>(requiredElement.GetRawText()); + requiredAttributes.ShouldNotBeNull(); + + requiredAttributes.Should().Contain("name"); + requiredAttributes.Should().Contain("nameOfCurrentFarm"); + requiredAttributes.Should().Contain("nickname"); + requiredAttributes.Should().Contain("weight"); + requiredAttributes.Should().Contain("hasProducedMilk"); + + requiredAttributes.ShouldHaveCount(5); + }); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs new file mode 100644 index 0000000000..b69bb69d12 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs @@ -0,0 +1,72 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; + +public sealed class NullabilityTests + : IClassFixture, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; + + public NullabilityTests( + OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled"; + } + + [Fact] + public async Task Resource_produces_expected_nullable_properties_in_schema() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(propertiesElement => + { + propertiesElement.ShouldContainPath("name").With(propertyElement => + { + propertyElement.ShouldNotContainPath("nullable"); + }); + + propertiesElement.ShouldContainPath("nameOfCurrentFarm").With(propertyElement => + { + propertyElement.ShouldNotContainPath("nullable"); + }); + + propertiesElement.ShouldContainPath("nameOfPreviousFarm").With(propertyElement => + { + propertyElement.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + + propertiesElement.ShouldContainPath("nickname").With(propertyElement => + { + propertyElement.ShouldNotContainPath("nullable"); + }); + + propertiesElement.ShouldContainPath("age").With(propertyElement => + { + propertyElement.ShouldNotContainPath("nullable"); + }); + + propertiesElement.ShouldContainPath("weight").With(propertyElement => + { + propertyElement.ShouldNotContainPath("nullable"); + }); + + propertiesElement.ShouldContainPath("timeAtCurrentFarmInDays").With(propertyElement => + { + propertyElement.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + + propertiesElement.ShouldContainPath("hasProducedMilk").With(propertyElement => + { + propertyElement.ShouldNotContainPath("nullable"); + }); + }); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs new file mode 100644 index 0000000000..01c61aa589 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs @@ -0,0 +1,15 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class NullableReferenceTypesEnabledDbContext : DbContext +{ + public DbSet Cow => Set(); + + public NullableReferenceTypesEnabledDbContext(DbContextOptions options) + : base(options) + { + } +} diff --git a/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs b/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs new file mode 100644 index 0000000000..d4a1bf1d19 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; +using JetBrains.Annotations; +using JsonApiDotNetCore.Configuration; +using Microsoft.EntityFrameworkCore; + +namespace OpenApiTests.SchemaProperties; + +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +public sealed class SchemaPropertiesStartup : OpenApiStartup + where TDbContext : DbContext +{ + protected override void SetJsonApiOptions(JsonApiOptions options) + { + base.SetJsonApiOptions(options); + + options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); + } +} From 2604f842e727972fbf022f27e05e569b63e524e7 Mon Sep 17 00:00:00 2001 From: maurei Date: Tue, 5 Jul 2022 19:17:22 +0200 Subject: [PATCH 02/26] Added handling of modelstate validation in setting required attributes --- .../ResourceFieldObjectSchemaBuilder.cs | 11 +++++++---- .../ResourceObjectSchemaGenerator.cs | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs index ef8c705424..c56054c858 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.Reflection; -using System.Text.Json; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.OpenApi.JsonApiObjects; using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships; using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; @@ -23,24 +23,27 @@ internal sealed class ResourceFieldObjectSchemaBuilder private readonly ISchemaRepositoryAccessor _schemaRepositoryAccessor; private readonly SchemaGenerator _defaultSchemaGenerator; private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator; + private readonly IJsonApiOptions _options; private readonly SchemaRepository _resourceSchemaRepository = new(); private readonly NullableReferenceSchemaGenerator _nullableReferenceSchemaGenerator; private readonly IDictionary _schemasForResourceFields; public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISchemaRepositoryAccessor schemaRepositoryAccessor, - SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, JsonNamingPolicy? namingPolicy) + SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, IJsonApiOptions options) { ArgumentGuard.NotNull(resourceTypeInfo, nameof(resourceTypeInfo)); ArgumentGuard.NotNull(schemaRepositoryAccessor, nameof(schemaRepositoryAccessor)); ArgumentGuard.NotNull(defaultSchemaGenerator, nameof(defaultSchemaGenerator)); ArgumentGuard.NotNull(resourceTypeSchemaGenerator, nameof(resourceTypeSchemaGenerator)); + ArgumentGuard.NotNull(options, nameof(options)); _resourceTypeInfo = resourceTypeInfo; _schemaRepositoryAccessor = schemaRepositoryAccessor; _defaultSchemaGenerator = defaultSchemaGenerator; _resourceTypeSchemaGenerator = resourceTypeSchemaGenerator; + _options = options; - _nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(schemaRepositoryAccessor, namingPolicy); + _nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(schemaRepositoryAccessor, options.SerializerOptions.PropertyNamingPolicy); _schemasForResourceFields = GetFieldSchemas(); } @@ -113,7 +116,7 @@ private bool IsFieldRequired(ResourceFieldAttribute field) return fieldTypeCategory switch { - TypeCategory.NonNullableReferenceType => true, + TypeCategory.NonNullableReferenceType => hasRequiredAttribute || _options.ValidateModelState, TypeCategory.ValueType => hasRequiredAttribute, TypeCategory.NullableReferenceType or TypeCategory.NullableValueType => hasRequiredAttribute, _ => throw new UnreachableCodeException() diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs index 616b98ef63..a4fda8d7d0 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs @@ -31,7 +31,7 @@ public ResourceObjectSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IRe _allowClientGeneratedIds = options.AllowClientGeneratedIds; _resourceFieldObjectSchemaBuilderFactory = resourceTypeInfo => new ResourceFieldObjectSchemaBuilder(resourceTypeInfo, schemaRepositoryAccessor, - defaultSchemaGenerator, _resourceTypeSchemaGenerator, options.SerializerOptions.PropertyNamingPolicy); + defaultSchemaGenerator, _resourceTypeSchemaGenerator, options); } public OpenApiSchema GenerateSchema(Type resourceObjectType) From f7194d94bafdd86c4831b3cd619b4cd5c238f00f Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Mon, 5 Sep 2022 15:50:13 +0200 Subject: [PATCH 03/26] Not all swagger documents need to be saved to disk; changes in OpenApiTestContext to allow for this --- test/OpenApiTests/OpenApiTestContext.cs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/test/OpenApiTests/OpenApiTestContext.cs b/test/OpenApiTests/OpenApiTestContext.cs index a750929827..9d4a95c33d 100644 --- a/test/OpenApiTests/OpenApiTestContext.cs +++ b/test/OpenApiTests/OpenApiTestContext.cs @@ -28,34 +28,27 @@ internal async Task GetSwaggerDocumentAsync() private async Task CreateSwaggerDocumentAsync() { - string absoluteOutputPath = GetSwaggerDocumentAbsoluteOutputPath(SwaggerDocumentOutputPath); - string content = await GetAsync("swagger/v1/swagger.json"); JsonElement rootElement = ParseSwaggerDocument(content); - await WriteToDiskAsync(absoluteOutputPath, rootElement); + + if (SwaggerDocumentOutputPath != null) + { + string absoluteOutputPath = GetSwaggerDocumentAbsoluteOutputPath(SwaggerDocumentOutputPath); + await WriteToDiskAsync(absoluteOutputPath, rootElement); + } return rootElement; } - private static string GetSwaggerDocumentAbsoluteOutputPath(string? relativePath) + private static string GetSwaggerDocumentAbsoluteOutputPath(string relativePath) { - AssertHasSwaggerDocumentOutputPath(relativePath); - string solutionRoot = Path.Combine(Assembly.GetExecutingAssembly().Location, "../../../../../../"); string outputPath = Path.Combine(solutionRoot, relativePath, "swagger.g.json"); return Path.GetFullPath(outputPath); } - private static void AssertHasSwaggerDocumentOutputPath([SysNotNull] string? relativePath) - { - if (relativePath is null) - { - throw new Exception($"Property '{nameof(OpenApiTestContext)}.{nameof(SwaggerDocumentOutputPath)}' must be set."); - } - } - private async Task GetAsync(string requestUrl) { using var request = new HttpRequestMessage(HttpMethod.Get, requestUrl); From 449a07985ae0041eedb82136e419272af5e51c51 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Tue, 6 Sep 2022 13:13:26 +0200 Subject: [PATCH 04/26] Added OpenApi client tests for nullable and required properties --- .editorconfig | 4 + .../{LegacyClient => }/ApiResponse.cs | 2 +- .../FakeHttpClientWrapper.cs | 2 +- .../MemberInfoExtensions.cs | 37 ++ .../OpenApiClientTests.csproj | 18 +- .../PropertyInfoAssertionsExtension.cs | 29 + .../NullableReferenceTypesDisabledClient.cs | 14 + .../NullabilityTests.cs | 27 + .../RequiredAttributesTests.cs | 114 ++++ .../swagger.g.json | 516 +++++++++++++++++ .../NullableReferenceTypesEnabledClient.cs | 14 + .../NullabilityTests.cs | 27 + .../RequiredAttributesTests.cs | 94 +++ .../swagger.g.json | 536 ++++++++++++++++++ test/OpenApiClientTests/TypeCategory.cs | 9 + 15 files changed, 1437 insertions(+), 6 deletions(-) rename test/OpenApiClientTests/{LegacyClient => }/ApiResponse.cs (94%) rename test/OpenApiClientTests/{LegacyClient => }/FakeHttpClientWrapper.cs (98%) create mode 100644 test/OpenApiClientTests/MemberInfoExtensions.cs create mode 100644 test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json create mode 100644 test/OpenApiClientTests/TypeCategory.cs diff --git a/.editorconfig b/.editorconfig index b6d9a8990c..70d678243d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,6 +12,10 @@ insert_final_newline = true [*.{csproj,json}] indent_size = 2 +[test/OpenApiClientTests/obj/*.{cs}] +# Ignore compiler warnings triggered by code auto-generated by NSWag +dotnet_diagnostic.CS8625.severity = suggestion + [*.{cs}] #### .NET Coding Conventions #### diff --git a/test/OpenApiClientTests/LegacyClient/ApiResponse.cs b/test/OpenApiClientTests/ApiResponse.cs similarity index 94% rename from test/OpenApiClientTests/LegacyClient/ApiResponse.cs rename to test/OpenApiClientTests/ApiResponse.cs index d009d2acee..0c353ea5c0 100644 --- a/test/OpenApiClientTests/LegacyClient/ApiResponse.cs +++ b/test/OpenApiClientTests/ApiResponse.cs @@ -3,7 +3,7 @@ #pragma warning disable AV1008 // Class should not be static -namespace OpenApiClientTests.LegacyClient; +namespace OpenApiClientTests; internal static class ApiResponse { diff --git a/test/OpenApiClientTests/LegacyClient/FakeHttpClientWrapper.cs b/test/OpenApiClientTests/FakeHttpClientWrapper.cs similarity index 98% rename from test/OpenApiClientTests/LegacyClient/FakeHttpClientWrapper.cs rename to test/OpenApiClientTests/FakeHttpClientWrapper.cs index 254f7f31cd..7240daf029 100644 --- a/test/OpenApiClientTests/LegacyClient/FakeHttpClientWrapper.cs +++ b/test/OpenApiClientTests/FakeHttpClientWrapper.cs @@ -3,7 +3,7 @@ using System.Text; using JsonApiDotNetCore.OpenApi.Client; -namespace OpenApiClientTests.LegacyClient; +namespace OpenApiClientTests; /// /// Enables to inject an outgoing response body and inspect the incoming request. diff --git a/test/OpenApiClientTests/MemberInfoExtensions.cs b/test/OpenApiClientTests/MemberInfoExtensions.cs new file mode 100644 index 0000000000..089570ccf2 --- /dev/null +++ b/test/OpenApiClientTests/MemberInfoExtensions.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using JsonApiDotNetCore.OpenApi.Client; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace OpenApiClientTests; + +internal static class MemberInfoExtensions +{ + public static TypeCategory GetTypeCategory(this MemberInfo source) + { + ArgumentGuard.NotNull(source, nameof(source)); + + Type memberType; + + if (source.MemberType.HasFlag(MemberTypes.Field)) + { + memberType = ((FieldInfo)source).FieldType; + } + else if (source.MemberType.HasFlag(MemberTypes.Property)) + { + memberType = ((PropertyInfo)source).PropertyType; + } + else + { + throw new NotSupportedException($"Member type '{source.MemberType}' must be a property or field."); + } + + if (memberType.IsValueType) + { + return Nullable.GetUnderlyingType(memberType) != null ? TypeCategory.NullableValueType : TypeCategory.ValueType; + } + + // Once we switch to .NET 6, we should rely instead on the built-in reflection APIs for nullability information. + // See https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-7/#libraries-reflection-apis-for-nullability-information. + return source.IsNonNullableReferenceType() ? TypeCategory.NonNullableReferenceType : TypeCategory.NullableReferenceType; + } +} diff --git a/test/OpenApiClientTests/OpenApiClientTests.csproj b/test/OpenApiClientTests/OpenApiClientTests.csproj index 3f54c161b8..b16a6f3947 100644 --- a/test/OpenApiClientTests/OpenApiClientTests.csproj +++ b/test/OpenApiClientTests/OpenApiClientTests.csproj @@ -19,10 +19,6 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -62,5 +58,19 @@ NSwagCSharp /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions + + OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.GeneratedCode + NullableReferenceTypesEnabledClient + NullableReferenceTypesEnabledClient.cs + NSwagCSharp + /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true + + + OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode + NullableReferenceTypesDisabledClient + NullableReferenceTypesDisabledClient.cs + NSwagCSharp + /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false + \ No newline at end of file diff --git a/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs b/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs new file mode 100644 index 0000000000..81992fd251 --- /dev/null +++ b/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using FluentAssertions; +using FluentAssertions.Types; + +namespace OpenApiClientTests; + +internal static class PropertyInfoAssertionsExtensions +{ + [CustomAssertion] + public static void BeNullable(this PropertyInfoAssertions source, string because = "", params object[] becauseArgs) + { + MemberInfo memberInfo = source.Subject; + + TypeCategory typeCategory = memberInfo.GetTypeCategory(); + + typeCategory.Should().Match(category => category == TypeCategory.NullableReferenceType || category == TypeCategory.NullableValueType, because, + becauseArgs); + } + + [CustomAssertion] + public static void BeNonNullable(this PropertyInfoAssertions source, string because = "", params object[] becauseArgs) + { + MemberInfo memberInfo = source.Subject; + + TypeCategory typeCategory = memberInfo.GetTypeCategory(); + + typeCategory.Should().Match(category => category == TypeCategory.NonNullableReferenceType || category == TypeCategory.ValueType, because, becauseArgs); + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs new file mode 100644 index 0000000000..c1a4eebab5 --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs @@ -0,0 +1,14 @@ +using JsonApiDotNetCore.OpenApi.Client; +using Newtonsoft.Json; + +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; + +internal partial class NullableReferenceTypesDisabledClient : JsonApiClient +{ + partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) + { + SetSerializerSettingsForJsonApi(settings); + + settings.Formatting = Formatting.Indented; + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs new file mode 100644 index 0000000000..136c729537 --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using FluentAssertions; +using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; +using Xunit; + +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; + +public sealed class NullabilityTests +{ + [Fact] + public void Nullability_of_generated_types_is_as_expected() + { + PropertyInfo[] propertyInfos = typeof(ChickenAttributesInResponse).GetProperties(); + + PropertyInfo? propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(ChickenAttributesInResponse.Name)); + propertyInfo.Should().BeNullable(); + + propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(ChickenAttributesInResponse.NameOfCurrentFarm)); + propertyInfo.Should().BeNullable(); + + propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(ChickenAttributesInResponse.Age)); + propertyInfo.Should().BeNonNullable(); + + propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(ChickenAttributesInResponse.TimeAtCurrentFarmInDays)); + propertyInfo.Should().BeNullable(); + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs new file mode 100644 index 0000000000..3f6b203915 --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs @@ -0,0 +1,114 @@ +using System.Net; +using FluentAssertions; +using FluentAssertions.Specialized; +using JsonApiDotNetCore.Middleware; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; +using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; +using NullableReferenceTypesDisabledClient = OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode.NullableReferenceTypesDisabledClient; + +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; + +public sealed class RequiredAttributesTests +{ + private const string HostPrefix = "http://localhost/"; + + [Fact] + public async Task Partial_posting_resource_with_explicitly_omitting_required_fields_produces_expected_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + var requestDocument = new ChickenPostRequestDocument + { + Data = new ChickenDataInPostRequest + { + Attributes = new ChickenAttributesInPostRequest + { + HasProducedEggs = true + } + } + }; + + using (apiClient.RegisterAttributesForRequestDocument(requestDocument, + chicken => chicken.HasProducedEggs)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(HostPrefix + "chickens"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""chickens"", + ""attributes"": { + ""hasProducedEggs"": true + } + } +}"); + } + + [Fact] + public async Task Partial_posting_resource_without_explicitly_omitting_required_fields_fails() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + var requestDocument = new ChickenPostRequestDocument + { + Data = new ChickenDataInPostRequest + { + Attributes = new ChickenAttributesInPostRequest + { + Weight = 3 + } + } + }; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'nameOfCurrentFarm'. Property requires a value. Path 'data.attributes'."); + } + + [Fact] + public async Task Patching_resource_with_missing_id_fails() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + var requestDocument = new ChickenPatchRequestDocument + { + Data = new ChickenDataInPatchRequest + { + Attributes = new ChickenAttributesInPatchRequest + { + Age = 1 + } + } + }; + + Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchChickenAsync(1, requestDocument)); + + // Assert + await action.Should().ThrowAsync(); + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json new file mode 100644 index 0000000000..76771a1783 --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json @@ -0,0 +1,516 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenApiTests", + "version": "1.0" + }, + "paths": { + "/chickens": { + "get": { + "tags": [ + "chickens" + ], + "operationId": "getChickenCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "chickens" + ], + "operationId": "headChickenCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "chickens" + ], + "operationId": "postChicken", + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenPostRequestDocument" + } + } + } + }, + "responses": { + "201": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenPrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "Success" + } + } + } + }, + "/chickens/{id}": { + "get": { + "tags": [ + "chickens" + ], + "operationId": "getChicken", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenPrimaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "chickens" + ], + "operationId": "headChicken", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenPrimaryResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "chickens" + ], + "operationId": "patchChicken", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenPatchRequestDocument" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenPrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "Success" + } + } + }, + "delete": { + "tags": [ + "chickens" + ], + "operationId": "deleteChicken", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Success" + } + } + } + } + }, + "components": { + "schemas": { + "chickenAttributesInPatchRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "nameOfCurrentFarm": { + "type": "string" + }, + "age": { + "type": "integer", + "format": "int32" + }, + "weight": { + "type": "integer", + "format": "int32" + }, + "timeAtCurrentFarmInDays": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "hasProducedEggs": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "chickenAttributesInPostRequest": { + "required": [ + "hasProducedEggs", + "nameOfCurrentFarm", + "weight" + ], + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "nameOfCurrentFarm": { + "type": "string" + }, + "age": { + "type": "integer", + "format": "int32" + }, + "weight": { + "type": "integer", + "format": "int32" + }, + "timeAtCurrentFarmInDays": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "hasProducedEggs": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "chickenAttributesInResponse": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "nameOfCurrentFarm": { + "type": "string" + }, + "age": { + "type": "integer", + "format": "int32" + }, + "weight": { + "type": "integer", + "format": "int32" + }, + "timeAtCurrentFarmInDays": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "hasProducedEggs": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "chickenCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/chickenDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "chickenDataInPatchRequest": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/chickenResourceType" + }, + "id": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/chickenAttributesInPatchRequest" + } + }, + "additionalProperties": false + }, + "chickenDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/chickenResourceType" + }, + "attributes": { + "$ref": "#/components/schemas/chickenAttributesInPostRequest" + } + }, + "additionalProperties": false + }, + "chickenDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/chickenResourceType" + }, + "id": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/chickenAttributesInResponse" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "chickenPatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/chickenDataInPatchRequest" + } + }, + "additionalProperties": false + }, + "chickenPostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/chickenDataInPostRequest" + } + }, + "additionalProperties": false + }, + "chickenPrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/chickenDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "chickenResourceType": { + "enum": [ + "chickens" + ], + "type": "string" + }, + "jsonapiObject": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "profile": { + "type": "array", + "items": { + "type": "string" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "linksInResourceCollectionDocument": { + "required": [ + "first", + "self" + ], + "type": "object", + "properties": { + "self": { + "type": "string" + }, + "describedby": { + "type": "string" + }, + "first": { + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceDocument": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "type": "string" + }, + "describedby": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceObject": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } +} \ No newline at end of file diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs new file mode 100644 index 0000000000..17b822f56d --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs @@ -0,0 +1,14 @@ +using JsonApiDotNetCore.OpenApi.Client; +using Newtonsoft.Json; + +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.GeneratedCode; + +internal partial class NullableReferenceTypesEnabledClient : JsonApiClient +{ + partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) + { + SetSerializerSettingsForJsonApi(settings); + + settings.Formatting = Formatting.Indented; + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs new file mode 100644 index 0000000000..dfb1f0a934 --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using FluentAssertions; +using OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.GeneratedCode; +using Xunit; + +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled; + +public sealed class NullabilityTests +{ + [Fact] + public void Nullability_of_generated_types_is_as_expected() + { + PropertyInfo[] propertyInfos = typeof(CowAttributesInResponse).GetProperties(); + + PropertyInfo? propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(CowAttributesInResponse.Name)); + propertyInfo.Should().BeNonNullable(); + + propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(CowAttributesInResponse.NameOfPreviousFarm)); + propertyInfo.Should().BeNullable(); + + propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(CowAttributesInResponse.Age)); + propertyInfo.Should().BeNonNullable(); + + propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(CowAttributesInResponse.TimeAtCurrentFarmInDays)); + propertyInfo.Should().BeNullable(); + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs new file mode 100644 index 0000000000..5f35ec7f29 --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs @@ -0,0 +1,94 @@ +using System.Net; +using FluentAssertions; +using FluentAssertions.Specialized; +using JsonApiDotNetCore.Middleware; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; +using OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled; + +public sealed class RequiredAttributesTests +{ + private const string HostPrefix = "http://localhost/"; + + [Fact] + public async Task Partial_posting_resource_with_explicitly_omitting_required_fields_produces_expected_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + var requestDocument = new CowPostRequestDocument + { + Data = new CowDataInPostRequest + { + Attributes = new CowAttributesInPostRequest + { + HasProducedMilk = true, + Weight = 1100 + } + } + }; + + using (apiClient.RegisterAttributesForRequestDocument(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(HostPrefix + "cows"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""cows"", + ""attributes"": { + ""weight"": 1100, + ""hasProducedMilk"": true + } + } +}"); + } + + [Fact] + public async Task Partial_posting_resource_without_explicitly_omitting_required_fields_produces_expected_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + var requestDocument = new CowPostRequestDocument + { + Data = new CowDataInPostRequest + { + Attributes = new CowAttributesInPostRequest + { + Weight = 1100, + Age = 5, + Name = "Cow 1", + NameOfCurrentFarm = "123", + NameOfPreviousFarm = "123" + } + } + }; + + // Act + Func> + action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'nickname'. Property requires a value. Path 'data.attributes'."); + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json new file mode 100644 index 0000000000..78e6dc1a73 --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json @@ -0,0 +1,536 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenApiTests", + "version": "1.0" + }, + "paths": { + "/cows": { + "get": { + "tags": [ + "cows" + ], + "operationId": "getCowCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cows" + ], + "operationId": "headCowCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "cows" + ], + "operationId": "postCow", + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowPostRequestDocument" + } + } + } + }, + "responses": { + "201": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowPrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "Success" + } + } + } + }, + "/cows/{id}": { + "get": { + "tags": [ + "cows" + ], + "operationId": "getCow", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowPrimaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cows" + ], + "operationId": "headCow", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowPrimaryResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "cows" + ], + "operationId": "patchCow", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowPatchRequestDocument" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowPrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "Success" + } + } + }, + "delete": { + "tags": [ + "cows" + ], + "operationId": "deleteCow", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Success" + } + } + } + } + }, + "components": { + "schemas": { + "cowAttributesInPatchRequest": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "nameOfCurrentFarm": { + "type": "string" + }, + "nameOfPreviousFarm": { + "type": "string", + "nullable": true + }, + "nickname": { + "type": "string" + }, + "age": { + "type": "integer", + "format": "int32" + }, + "weight": { + "type": "integer", + "format": "int32" + }, + "timeAtCurrentFarmInDays": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "hasProducedMilk": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "cowAttributesInPostRequest": { + "required": [ + "hasProducedMilk", + "name", + "nameOfCurrentFarm", + "nickname", + "weight" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "nameOfCurrentFarm": { + "type": "string" + }, + "nameOfPreviousFarm": { + "type": "string", + "nullable": true + }, + "nickname": { + "type": "string" + }, + "age": { + "type": "integer", + "format": "int32" + }, + "weight": { + "type": "integer", + "format": "int32" + }, + "timeAtCurrentFarmInDays": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "hasProducedMilk": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "cowAttributesInResponse": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "nameOfCurrentFarm": { + "type": "string" + }, + "nameOfPreviousFarm": { + "type": "string", + "nullable": true + }, + "nickname": { + "type": "string" + }, + "age": { + "type": "integer", + "format": "int32" + }, + "weight": { + "type": "integer", + "format": "int32" + }, + "timeAtCurrentFarmInDays": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "hasProducedMilk": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "cowCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/cowDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "cowDataInPatchRequest": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/cowResourceType" + }, + "id": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/cowAttributesInPatchRequest" + } + }, + "additionalProperties": false + }, + "cowDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/cowResourceType" + }, + "attributes": { + "$ref": "#/components/schemas/cowAttributesInPostRequest" + } + }, + "additionalProperties": false + }, + "cowDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/cowResourceType" + }, + "id": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/cowAttributesInResponse" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "cowPatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/cowDataInPatchRequest" + } + }, + "additionalProperties": false + }, + "cowPostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/cowDataInPostRequest" + } + }, + "additionalProperties": false + }, + "cowPrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/cowDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "cowResourceType": { + "enum": [ + "cows" + ], + "type": "string" + }, + "jsonapiObject": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "profile": { + "type": "array", + "items": { + "type": "string" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "linksInResourceCollectionDocument": { + "required": [ + "first", + "self" + ], + "type": "object", + "properties": { + "self": { + "type": "string" + }, + "describedby": { + "type": "string" + }, + "first": { + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceDocument": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "type": "string" + }, + "describedby": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceObject": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } +} \ No newline at end of file diff --git a/test/OpenApiClientTests/TypeCategory.cs b/test/OpenApiClientTests/TypeCategory.cs new file mode 100644 index 0000000000..301e37c8f6 --- /dev/null +++ b/test/OpenApiClientTests/TypeCategory.cs @@ -0,0 +1,9 @@ +namespace OpenApiClientTests; + +internal enum TypeCategory +{ + NonNullableReferenceType, + NullableReferenceType, + ValueType, + NullableValueType +} From c55a2cba22f850bddf099d9600457d5b543b399e Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Tue, 6 Sep 2022 13:58:06 +0200 Subject: [PATCH 05/26] Use NullabilityInfoContext for nullability information --- .../MemberInfoExtensions.cs | 36 ----------------- .../ResourceFieldAttributeExtensions.cs | 31 ++++++++++++--- .../JsonApiSchemaGenerator.cs | 22 +++++------ .../ResourceFieldObjectSchemaBuilder.cs | 39 ++++++++++--------- src/JsonApiDotNetCore.OpenApi/TypeCategory.cs | 9 ----- .../MemberInfoExtensions.cs | 37 ------------------ .../PropertyInfoAssertionsExtension.cs | 15 +++---- .../RequiredAttributesTests.cs | 1 - test/OpenApiClientTests/TypeCategory.cs | 9 ----- 9 files changed, 65 insertions(+), 134 deletions(-) delete mode 100644 src/JsonApiDotNetCore.OpenApi/MemberInfoExtensions.cs delete mode 100644 src/JsonApiDotNetCore.OpenApi/TypeCategory.cs delete mode 100644 test/OpenApiClientTests/MemberInfoExtensions.cs delete mode 100644 test/OpenApiClientTests/TypeCategory.cs diff --git a/src/JsonApiDotNetCore.OpenApi/MemberInfoExtensions.cs b/src/JsonApiDotNetCore.OpenApi/MemberInfoExtensions.cs deleted file mode 100644 index a6f46ccb9d..0000000000 --- a/src/JsonApiDotNetCore.OpenApi/MemberInfoExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace JsonApiDotNetCore.OpenApi; - -internal static class MemberInfoExtensions -{ - public static TypeCategory GetTypeCategory(this MemberInfo source) - { - ArgumentGuard.NotNull(source, nameof(source)); - - Type memberType; - - if (source.MemberType.HasFlag(MemberTypes.Field)) - { - memberType = ((FieldInfo)source).FieldType; - } - else if (source.MemberType.HasFlag(MemberTypes.Property)) - { - memberType = ((PropertyInfo)source).PropertyType; - } - else - { - throw new NotSupportedException($"Member type '{source.MemberType}' must be a property or field."); - } - - if (memberType.IsValueType) - { - return Nullable.GetUnderlyingType(memberType) != null ? TypeCategory.NullableValueType : TypeCategory.ValueType; - } - - // Once we switch to .NET 6, we should rely instead on the built-in reflection APIs for nullability information. - // See https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-7/#libraries-reflection-apis-for-nullability-information. - return source.IsNonNullableReferenceType() ? TypeCategory.NonNullableReferenceType : TypeCategory.NullableReferenceType; - } -} diff --git a/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs b/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs index 32bf9f5187..d4b9c40fea 100644 --- a/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs +++ b/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Reflection; using JsonApiDotNetCore.Resources.Annotations; using Swashbuckle.AspNetCore.SwaggerGen; @@ -6,16 +7,34 @@ namespace JsonApiDotNetCore.OpenApi; internal static class ResourceFieldAttributeExtensions { + private static readonly NullabilityInfoContext NullabilityInfoContext = new(); + public static bool IsNullable(this ResourceFieldAttribute source) { - TypeCategory fieldTypeCategory = source.Property.GetTypeCategory(); bool hasRequiredAttribute = source.Property.HasAttribute(); - return fieldTypeCategory switch + if (hasRequiredAttribute) { - TypeCategory.NonNullableReferenceType or TypeCategory.ValueType => false, - TypeCategory.NullableReferenceType or TypeCategory.NullableValueType => !hasRequiredAttribute, - _ => throw new UnreachableCodeException() - }; + // Reflects the following cases, independent of NRT setting + // `[Required] int? Number` => not nullable + // `[Required] int Number` => not nullable + // `[Required] string Text` => not nullable + // `[Required] string? Text` => not nullable + // `[Required] string Text` => not nullable + return false; + } + + NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(source.Property); + + // Reflects the following cases: + // Independent of NRT: + // `int? Number` => nullable + // `int Number` => not nullable + // If NRT is enabled: + // `string? Text` => nullable + // `string Text` => not nullable + // If NRT is disabled: + // `string Text` => nullable + return nullabilityInfo.ReadState is not NullabilityState.NotNull; } } diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs index a0e7159a89..c5fe2b6a2d 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs @@ -26,6 +26,13 @@ internal sealed class JsonApiSchemaGenerator : ISchemaGenerator typeof(NullableToOneRelationshipInRequest<>) }; + private static readonly Type[] JsonApiDocumentWithNullableDataOpenTypes = + { + typeof(NullableSecondaryResourceResponseDocument<>), + typeof(NullableResourceIdentifierResponseDocument<>), + typeof(NullableToOneRelationshipInRequest<>) + }; + private readonly ISchemaGenerator _defaultSchemaGenerator; private readonly ResourceObjectSchemaGenerator _resourceObjectSchemaGenerator; private readonly NullableReferenceSchemaGenerator _nullableReferenceSchemaGenerator; @@ -58,7 +65,7 @@ public OpenApiSchema GenerateSchema(Type type, SchemaRepository schemaRepository { OpenApiSchema schema = GenerateJsonApiDocumentSchema(type); - if (IsDataPropertyNullable(type)) + if (IsDataPropertyNullableInDocument(type)) { SetDataObjectSchemaToNullable(schema); } @@ -98,18 +105,11 @@ private static bool IsManyDataDocument(Type documentType) return documentType.BaseType!.GetGenericTypeDefinition() == typeof(ManyData<>); } - private static bool IsDataPropertyNullable(Type type) + private static bool IsDataPropertyNullableInDocument(Type documentType) { - PropertyInfo? dataProperty = type.GetProperty(nameof(JsonApiObjectPropertyName.Data)); - - if (dataProperty == null) - { - throw new UnreachableCodeException(); - } - - TypeCategory typeCategory = dataProperty.GetTypeCategory(); + Type documentOpenType = documentType.GetGenericTypeDefinition(); - return typeCategory == TypeCategory.NullableReferenceType; + return JsonApiDocumentWithNullableDataOpenTypes.Contains(documentOpenType); } private void SetDataObjectSchemaToNullable(OpenApiSchema referenceSchemaForDocument) diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs index c56054c858..6dbf35dd1e 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs @@ -12,13 +12,21 @@ namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; internal sealed class ResourceFieldObjectSchemaBuilder { - private static readonly Type[] RelationshipInResponseOpenTypes = + private static readonly NullabilityInfoContext NullabilityInfoContext = new(); + + private static readonly Type[] RelationshipSchemaInResponseOpenTypes = { typeof(ToOneRelationshipInResponse<>), typeof(ToManyRelationshipInResponse<>), typeof(NullableToOneRelationshipInResponse<>) }; + private static readonly Type[] NullableRelationshipSchemaOpenTypes = + { + typeof(NullableToOneRelationshipInRequest<>), + typeof(NullableToOneRelationshipInResponse<>) + }; + private readonly ResourceTypeInfo _resourceTypeInfo; private readonly ISchemaRepositoryAccessor _schemaRepositoryAccessor; private readonly SchemaGenerator _defaultSchemaGenerator; @@ -111,15 +119,14 @@ private bool IsFieldRequired(ResourceFieldAttribute field) return false; } - TypeCategory fieldTypeCategory = field.Property.GetTypeCategory(); bool hasRequiredAttribute = field.Property.HasAttribute(); - return fieldTypeCategory switch + NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(field.Property); + + return field.Property.PropertyType.IsValueType switch { - TypeCategory.NonNullableReferenceType => hasRequiredAttribute || _options.ValidateModelState, - TypeCategory.ValueType => hasRequiredAttribute, - TypeCategory.NullableReferenceType or TypeCategory.NullableValueType => hasRequiredAttribute, - _ => throw new UnreachableCodeException() + true => hasRequiredAttribute, + false => _options.ValidateModelState ? nullabilityInfo.ReadState == NullabilityState.NotNull || hasRequiredAttribute : hasRequiredAttribute }; } @@ -197,7 +204,9 @@ private OpenApiSchema CreateRelationshipSchema(Type relationshipSchemaType) OpenApiSchema fullSchema = _schemaRepositoryAccessor.Current.Schemas[referenceSchema.Reference.Id]; - if (IsDataPropertyNullable(relationshipSchemaType)) + Console.WriteLine(relationshipSchemaType.FullName); + + if (IsDataPropertyNullableInRelationshipSchemaType(relationshipSchemaType)) { fullSchema.Properties[JsonApiObjectPropertyName.Data] = _nullableReferenceSchemaGenerator.GenerateSchema(fullSchema.Properties[JsonApiObjectPropertyName.Data]); @@ -215,18 +224,12 @@ private static bool IsRelationshipInResponseType(Type relationshipSchemaType) { Type relationshipSchemaOpenType = relationshipSchemaType.GetGenericTypeDefinition(); - return RelationshipInResponseOpenTypes.Contains(relationshipSchemaOpenType); + return RelationshipSchemaInResponseOpenTypes.Contains(relationshipSchemaOpenType); } - private static bool IsDataPropertyNullable(Type type) + private static bool IsDataPropertyNullableInRelationshipSchemaType(Type relationshipSchemaType) { - PropertyInfo? dataProperty = type.GetProperty(nameof(JsonApiObjectPropertyName.Data)); - - if (dataProperty == null) - { - throw new UnreachableCodeException(); - } - - return dataProperty.GetTypeCategory() == TypeCategory.NullableReferenceType; + Type relationshipSchemaOpenType = relationshipSchemaType.GetGenericTypeDefinition(); + return NullableRelationshipSchemaOpenTypes.Contains(relationshipSchemaOpenType); } } diff --git a/src/JsonApiDotNetCore.OpenApi/TypeCategory.cs b/src/JsonApiDotNetCore.OpenApi/TypeCategory.cs deleted file mode 100644 index 1641e31775..0000000000 --- a/src/JsonApiDotNetCore.OpenApi/TypeCategory.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace JsonApiDotNetCore.OpenApi; - -internal enum TypeCategory -{ - NonNullableReferenceType, - NullableReferenceType, - ValueType, - NullableValueType -} diff --git a/test/OpenApiClientTests/MemberInfoExtensions.cs b/test/OpenApiClientTests/MemberInfoExtensions.cs deleted file mode 100644 index 089570ccf2..0000000000 --- a/test/OpenApiClientTests/MemberInfoExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Reflection; -using JsonApiDotNetCore.OpenApi.Client; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace OpenApiClientTests; - -internal static class MemberInfoExtensions -{ - public static TypeCategory GetTypeCategory(this MemberInfo source) - { - ArgumentGuard.NotNull(source, nameof(source)); - - Type memberType; - - if (source.MemberType.HasFlag(MemberTypes.Field)) - { - memberType = ((FieldInfo)source).FieldType; - } - else if (source.MemberType.HasFlag(MemberTypes.Property)) - { - memberType = ((PropertyInfo)source).PropertyType; - } - else - { - throw new NotSupportedException($"Member type '{source.MemberType}' must be a property or field."); - } - - if (memberType.IsValueType) - { - return Nullable.GetUnderlyingType(memberType) != null ? TypeCategory.NullableValueType : TypeCategory.ValueType; - } - - // Once we switch to .NET 6, we should rely instead on the built-in reflection APIs for nullability information. - // See https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-7/#libraries-reflection-apis-for-nullability-information. - return source.IsNonNullableReferenceType() ? TypeCategory.NonNullableReferenceType : TypeCategory.NullableReferenceType; - } -} diff --git a/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs b/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs index 81992fd251..c3338a300b 100644 --- a/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs +++ b/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs @@ -6,24 +6,25 @@ namespace OpenApiClientTests; internal static class PropertyInfoAssertionsExtensions { + private static readonly NullabilityInfoContext NullabilityInfoContext = new(); + [CustomAssertion] public static void BeNullable(this PropertyInfoAssertions source, string because = "", params object[] becauseArgs) { - MemberInfo memberInfo = source.Subject; + PropertyInfo propertyInfo = source.Subject; - TypeCategory typeCategory = memberInfo.GetTypeCategory(); + NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(propertyInfo); - typeCategory.Should().Match(category => category == TypeCategory.NullableReferenceType || category == TypeCategory.NullableValueType, because, - becauseArgs); + nullabilityInfo.ReadState.Should().NotBe(NullabilityState.NotNull, because, becauseArgs); } [CustomAssertion] public static void BeNonNullable(this PropertyInfoAssertions source, string because = "", params object[] becauseArgs) { - MemberInfo memberInfo = source.Subject; + PropertyInfo propertyInfo = source.Subject; - TypeCategory typeCategory = memberInfo.GetTypeCategory(); + NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(propertyInfo); - typeCategory.Should().Match(category => category == TypeCategory.NonNullableReferenceType || category == TypeCategory.ValueType, because, becauseArgs); + nullabilityInfo.ReadState.Should().Be(NullabilityState.NotNull, because, becauseArgs); } } diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs index 3f6b203915..b97535f908 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs @@ -7,7 +7,6 @@ using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; using TestBuildingBlocks; using Xunit; -using NullableReferenceTypesDisabledClient = OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode.NullableReferenceTypesDisabledClient; namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; diff --git a/test/OpenApiClientTests/TypeCategory.cs b/test/OpenApiClientTests/TypeCategory.cs deleted file mode 100644 index 301e37c8f6..0000000000 --- a/test/OpenApiClientTests/TypeCategory.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace OpenApiClientTests; - -internal enum TypeCategory -{ - NonNullableReferenceType, - NullableReferenceType, - ValueType, - NullableValueType -} From fc159125a15d5206f697a2b5e06c6b9ee87857f1 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 8 Sep 2022 02:31:45 +0200 Subject: [PATCH 06/26] Post-merge fixes --- .../swagger.g.json | 17 ++++++++++++---- .../swagger.g.json | 20 +++++++++++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json index 76771a1783..d71423c997 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json @@ -58,7 +58,7 @@ }, "responses": { "201": { - "description": "Success", + "description": "Created", "content": { "application/vnd.api+json": { "schema": { @@ -68,7 +68,7 @@ } }, "204": { - "description": "Success" + "description": "No Content" } } } @@ -169,7 +169,7 @@ } }, "204": { - "description": "Success" + "description": "No Content" } } }, @@ -191,7 +191,7 @@ ], "responses": { "204": { - "description": "Success" + "description": "No Content" } } } @@ -207,6 +207,7 @@ "nullable": true }, "nameOfCurrentFarm": { + "minLength": 1, "type": "string" }, "age": { @@ -241,6 +242,7 @@ "nullable": true }, "nameOfCurrentFarm": { + "minLength": 1, "type": "string" }, "age": { @@ -270,6 +272,7 @@ "nullable": true }, "nameOfCurrentFarm": { + "minLength": 1, "type": "string" }, "age": { @@ -328,6 +331,7 @@ "$ref": "#/components/schemas/chickenResourceType" }, "id": { + "minLength": 1, "type": "string" }, "attributes": { @@ -363,6 +367,7 @@ "$ref": "#/components/schemas/chickenResourceType" }, "id": { + "minLength": 1, "type": "string" }, "attributes": { @@ -464,12 +469,14 @@ "type": "object", "properties": { "self": { + "minLength": 1, "type": "string" }, "describedby": { "type": "string" }, "first": { + "minLength": 1, "type": "string" }, "last": { @@ -491,6 +498,7 @@ "type": "object", "properties": { "self": { + "minLength": 1, "type": "string" }, "describedby": { @@ -506,6 +514,7 @@ "type": "object", "properties": { "self": { + "minLength": 1, "type": "string" } }, diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json index 78e6dc1a73..14669b5c84 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json @@ -58,7 +58,7 @@ }, "responses": { "201": { - "description": "Success", + "description": "Created", "content": { "application/vnd.api+json": { "schema": { @@ -68,7 +68,7 @@ } }, "204": { - "description": "Success" + "description": "No Content" } } } @@ -169,7 +169,7 @@ } }, "204": { - "description": "Success" + "description": "No Content" } } }, @@ -191,7 +191,7 @@ ], "responses": { "204": { - "description": "Success" + "description": "No Content" } } } @@ -206,6 +206,7 @@ "type": "string" }, "nameOfCurrentFarm": { + "minLength": 1, "type": "string" }, "nameOfPreviousFarm": { @@ -213,6 +214,7 @@ "nullable": true }, "nickname": { + "minLength": 1, "type": "string" }, "age": { @@ -248,6 +250,7 @@ "type": "string" }, "nameOfCurrentFarm": { + "minLength": 1, "type": "string" }, "nameOfPreviousFarm": { @@ -255,6 +258,7 @@ "nullable": true }, "nickname": { + "minLength": 1, "type": "string" }, "age": { @@ -283,6 +287,7 @@ "type": "string" }, "nameOfCurrentFarm": { + "minLength": 1, "type": "string" }, "nameOfPreviousFarm": { @@ -290,6 +295,7 @@ "nullable": true }, "nickname": { + "minLength": 1, "type": "string" }, "age": { @@ -348,6 +354,7 @@ "$ref": "#/components/schemas/cowResourceType" }, "id": { + "minLength": 1, "type": "string" }, "attributes": { @@ -383,6 +390,7 @@ "$ref": "#/components/schemas/cowResourceType" }, "id": { + "minLength": 1, "type": "string" }, "attributes": { @@ -484,12 +492,14 @@ "type": "object", "properties": { "self": { + "minLength": 1, "type": "string" }, "describedby": { "type": "string" }, "first": { + "minLength": 1, "type": "string" }, "last": { @@ -511,6 +521,7 @@ "type": "object", "properties": { "self": { + "minLength": 1, "type": "string" }, "describedby": { @@ -526,6 +537,7 @@ "type": "object", "properties": { "self": { + "minLength": 1, "type": "string" } }, From d7c8b4b04190cec2b900be40bd68e5c0746e272a Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 15 Dec 2022 11:40:56 +0100 Subject: [PATCH 07/26] Post-merge fixes --- .../SchemaProperties/ModelStateValidationDisabledStartup.cs | 4 ++-- .../NullableReferenceTypesDisabledDbContext.cs | 3 ++- .../NullableReferenceTypesEnabledDbContext.cs | 3 ++- test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs b/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs index 9895d7d61b..b2906ba460 100644 --- a/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs +++ b/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs @@ -1,13 +1,13 @@ using System.Text.Json.Serialization; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace OpenApiTests.SchemaProperties; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class ModelStateValidationDisabledStartup : OpenApiStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs index 1208a369ef..16bdc07e15 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class NullableReferenceTypesDisabledDbContext : DbContext +public sealed class NullableReferenceTypesDisabledDbContext : TestableDbContext { public DbSet Chicken => Set(); diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs index 01c61aa589..b7011e7d27 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class NullableReferenceTypesEnabledDbContext : DbContext +public sealed class NullableReferenceTypesEnabledDbContext : TestableDbContext { public DbSet Cow => Set(); diff --git a/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs b/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs index d4a1bf1d19..03f6531631 100644 --- a/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs +++ b/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs @@ -1,13 +1,13 @@ using System.Text.Json.Serialization; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace OpenApiTests.SchemaProperties; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class SchemaPropertiesStartup : OpenApiStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { From d72e43ee93cfe2f973086dc04925fecc4d5b6c6b Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 15 Dec 2022 17:37:21 +0100 Subject: [PATCH 08/26] Fixed: do not share NullabilityInfoContext, it is not thread-safe --- .../ResourceFieldAttributeExtensions.cs | 5 ++--- .../SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs | 5 ++--- .../OpenApiClientTests/PropertyInfoAssertionsExtension.cs | 8 ++++---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs b/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs index d4b9c40fea..b1274cbe97 100644 --- a/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs +++ b/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs @@ -7,8 +7,6 @@ namespace JsonApiDotNetCore.OpenApi; internal static class ResourceFieldAttributeExtensions { - private static readonly NullabilityInfoContext NullabilityInfoContext = new(); - public static bool IsNullable(this ResourceFieldAttribute source) { bool hasRequiredAttribute = source.Property.HasAttribute(); @@ -24,7 +22,8 @@ public static bool IsNullable(this ResourceFieldAttribute source) return false; } - NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(source.Property); + NullabilityInfoContext nullabilityContext = new(); + NullabilityInfo nullabilityInfo = nullabilityContext.Create(source.Property); // Reflects the following cases: // Independent of NRT: diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs index 9d068f3dc9..804fae41fc 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs @@ -12,8 +12,6 @@ namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; internal sealed class ResourceFieldObjectSchemaBuilder { - private static readonly NullabilityInfoContext NullabilityInfoContext = new(); - private static readonly Type[] RelationshipSchemaInResponseOpenTypes = { typeof(ToOneRelationshipInResponse<>), @@ -121,7 +119,8 @@ private bool IsFieldRequired(ResourceFieldAttribute field) bool hasRequiredAttribute = field.Property.HasAttribute(); - NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(field.Property); + NullabilityInfoContext nullabilityContext = new(); + NullabilityInfo nullabilityInfo = nullabilityContext.Create(field.Property); return field.Property.PropertyType.IsValueType switch { diff --git a/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs b/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs index c3338a300b..b29c635f8e 100644 --- a/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs +++ b/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs @@ -6,14 +6,13 @@ namespace OpenApiClientTests; internal static class PropertyInfoAssertionsExtensions { - private static readonly NullabilityInfoContext NullabilityInfoContext = new(); - [CustomAssertion] public static void BeNullable(this PropertyInfoAssertions source, string because = "", params object[] becauseArgs) { PropertyInfo propertyInfo = source.Subject; - NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(propertyInfo); + NullabilityInfoContext nullabilityContext = new(); + NullabilityInfo nullabilityInfo = nullabilityContext.Create(propertyInfo); nullabilityInfo.ReadState.Should().NotBe(NullabilityState.NotNull, because, becauseArgs); } @@ -23,7 +22,8 @@ public static void BeNonNullable(this PropertyInfoAssertions source, string beca { PropertyInfo propertyInfo = source.Subject; - NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(propertyInfo); + NullabilityInfoContext nullabilityContext = new(); + NullabilityInfo nullabilityInfo = nullabilityContext.Create(propertyInfo); nullabilityInfo.ReadState.Should().Be(NullabilityState.NotNull, because, becauseArgs); } From f8e640bd29668e7039d546efb3b0a431fad7a2da Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Mon, 19 Dec 2022 09:13:10 +0100 Subject: [PATCH 09/26] Review feedback OpenApiTests/SchemaProperties test collection: * Allow for usage of OpenApiStartup directly * Remove unneeded adding of JsonConverter * Replace null checks with .ShouldHaveCount() calls * Adjust test names Misc: * Reverted .editorconfig changes and fixed ApiException constructor instead * Remove Debug statement --- .editorconfig | 4 ---- .../ApiException.cs | 2 +- .../ResourceFieldObjectSchemaBuilder.cs | 2 -- test/OpenApiTests/OpenApiStartup.cs | 3 ++- .../ModelStateValidationDisabledStartup.cs | 3 +-- .../ModelStateValidationDisabledTests.cs | 8 ++++---- .../ModelStateValidationEnabledTests.cs | 13 ++++++------- .../NullabilityTests.cs | 14 +++++++------- .../ModelStateValidationDisabledTests.cs | 7 +++---- .../ModelStateValidationEnabledTests.cs | 13 ++++++------- .../NullabilityTests.cs | 14 +++++++------- .../SchemaPropertiesStartup.cs | 18 ------------------ 12 files changed, 37 insertions(+), 64 deletions(-) delete mode 100644 test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs diff --git a/.editorconfig b/.editorconfig index 7e3b5e1cf2..ca191cf90e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,10 +11,6 @@ insert_final_newline = true [*.{config,csproj,css,js,json,props,ruleset,xslt}] indent_size = 2 -[test/OpenApiClientTests/obj/*.{cs}] -# Ignore compiler warnings triggered by code auto-generated by NSWag -dotnet_diagnostic.CS8625.severity = suggestion - [*.{cs}] #### C#/.NET Coding Conventions #### diff --git a/src/JsonApiDotNetCore.OpenApi.Client/ApiException.cs b/src/JsonApiDotNetCore.OpenApi.Client/ApiException.cs index 1224308af9..cc7cdc8104 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client/ApiException.cs +++ b/src/JsonApiDotNetCore.OpenApi.Client/ApiException.cs @@ -15,7 +15,7 @@ public sealed class ApiException : Exception public IReadOnlyDictionary> Headers { get; } - public ApiException(string message, int statusCode, string? response, IReadOnlyDictionary> headers, Exception innerException) + public ApiException(string message, int statusCode, string? response, IReadOnlyDictionary> headers, Exception? innerException) : base($"{message}\n\nStatus: {statusCode}\nResponse: \n{response ?? "(null)"}", innerException) { StatusCode = statusCode; diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs index 804fae41fc..e4abf42549 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs @@ -203,8 +203,6 @@ private OpenApiSchema CreateRelationshipSchema(Type relationshipSchemaType) OpenApiSchema fullSchema = _schemaRepositoryAccessor.Current.Schemas[referenceSchema.Reference.Id]; - Console.WriteLine(relationshipSchemaType.FullName); - if (IsDataPropertyNullableInRelationshipSchemaType(relationshipSchemaType)) { fullSchema.Properties[JsonApiObjectPropertyName.Data] = diff --git a/test/OpenApiTests/OpenApiStartup.cs b/test/OpenApiTests/OpenApiStartup.cs index 46978e7f67..a1a46e4022 100644 --- a/test/OpenApiTests/OpenApiStartup.cs +++ b/test/OpenApiTests/OpenApiStartup.cs @@ -6,7 +6,7 @@ namespace OpenApiTests; -public abstract class OpenApiStartup : TestableStartup +public class OpenApiStartup : TestableStartup where TDbContext : TestableDbContext { public override void ConfigureServices(IServiceCollection services) @@ -26,3 +26,4 @@ public override void Configure(IApplicationBuilder app) app.UseEndpoints(endpoints => endpoints.MapControllers()); } } + diff --git a/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs b/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs index b2906ba460..ba9ea319d0 100644 --- a/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs +++ b/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs @@ -1,4 +1,3 @@ -using System.Text.Json.Serialization; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using TestBuildingBlocks; @@ -13,7 +12,7 @@ protected override void SetJsonApiOptions(JsonApiOptions options) { base.SetJsonApiOptions(options); - options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); options.ValidateModelState = false; } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs index 838e2a6bc2..209423df20 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs @@ -15,11 +15,12 @@ public ModelStateValidationDisabledTests( OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) { _testContext = testContext; + testContext.UseController(); } [Fact] - public async Task Resource_when_ModelStateValidation_is_disabled_produces_expected_required_property_in_schema() + public async Task Produces_expected_required_property_in_schema_for_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -28,13 +29,12 @@ public async Task Resource_when_ModelStateValidation_is_disabled_produces_expect document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(requiredElement => { var requiredAttributes = JsonSerializer.Deserialize>(requiredElement.GetRawText()); - requiredAttributes.ShouldNotBeNull(); + requiredAttributes.ShouldHaveCount(3); requiredAttributes.Should().Contain("nameOfCurrentFarm"); requiredAttributes.Should().Contain("weight"); requiredAttributes.Should().Contain("hasProducedEggs"); - - requiredAttributes.ShouldHaveCount(3); }); } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs index b1c39ca090..b028eb9cfa 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs @@ -6,12 +6,12 @@ namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; public sealed class ModelStateValidationEnabledTests - : IClassFixture, NullableReferenceTypesDisabledDbContext>> + : IClassFixture, NullableReferenceTypesDisabledDbContext>> { - private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; public ModelStateValidationEnabledTests( - OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) { _testContext = testContext; @@ -19,7 +19,7 @@ public ModelStateValidationEnabledTests( } [Fact] - public async Task Resource_when_ModelStateValidation_is_enabled_produces_expected_required_property_in_schema() + public async Task Produces_expected_required_property_in_schema_for_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -28,13 +28,12 @@ public async Task Resource_when_ModelStateValidation_is_enabled_produces_expecte document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(requiredElement => { var requiredAttributes = JsonSerializer.Deserialize>(requiredElement.GetRawText()); - requiredAttributes.ShouldNotBeNull(); + requiredAttributes.ShouldHaveCount(3); requiredAttributes.Should().Contain("nameOfCurrentFarm"); requiredAttributes.Should().Contain("weight"); requiredAttributes.Should().Contain("hasProducedEggs"); - - requiredAttributes.ShouldHaveCount(3); }); } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs index e13be25d25..4e8b216dbe 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs @@ -6,12 +6,11 @@ namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; public sealed class NullabilityTests - : IClassFixture, NullableReferenceTypesDisabledDbContext>> + : IClassFixture, NullableReferenceTypesDisabledDbContext>> { - private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; - public NullabilityTests( - OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + public NullabilityTests(OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) { _testContext = testContext; @@ -20,7 +19,7 @@ public NullabilityTests( } [Fact] - public async Task Resource_produces_expected_nullable_properties_in_schema() + public async Task Produces_expected_nullable_properties_in_schema_for_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -30,7 +29,7 @@ public async Task Resource_produces_expected_nullable_properties_in_schema() { propertiesElement.ShouldContainPath("name").With(propertyElement => { - propertyElement.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + propertyElement.ShouldContainPath("nullable").With(element => element.ValueKind.Should().Be(JsonValueKind.True)); }); propertiesElement.ShouldContainPath("nameOfCurrentFarm").With(propertyElement => @@ -50,7 +49,7 @@ public async Task Resource_produces_expected_nullable_properties_in_schema() propertiesElement.ShouldContainPath("timeAtCurrentFarmInDays").With(propertyElement => { - propertyElement.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + propertyElement.ShouldContainPath("nullable").With(element => element.ValueKind.Should().Be(JsonValueKind.True)); }); propertiesElement.ShouldContainPath("hasProducedEggs").With(propertyElement => @@ -60,3 +59,4 @@ public async Task Resource_produces_expected_nullable_properties_in_schema() }); } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs index f57c3bf4bc..06083f643c 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs @@ -20,7 +20,7 @@ public ModelStateValidationDisabledTests( } [Fact] - public async Task Resource_when_ModelStateValidation_is_disabled_produces_expected_required_property_in_schema() + public async Task Produces_expected_required_property_in_schema_for_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -29,14 +29,13 @@ public async Task Resource_when_ModelStateValidation_is_disabled_produces_expect document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(requiredElement => { var requiredAttributes = JsonSerializer.Deserialize>(requiredElement.GetRawText()); - requiredAttributes.ShouldNotBeNull(); + requiredAttributes.ShouldHaveCount(4); requiredAttributes.Should().Contain("nameOfCurrentFarm"); requiredAttributes.Should().Contain("nickname"); requiredAttributes.Should().Contain("weight"); requiredAttributes.Should().Contain("hasProducedMilk"); - - requiredAttributes.ShouldHaveCount(4); }); } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs index 7cb98fceda..034d49be49 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs @@ -6,12 +6,12 @@ namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; public sealed class ModelStateValidationEnabledTests - : IClassFixture, NullableReferenceTypesEnabledDbContext>> + : IClassFixture, NullableReferenceTypesEnabledDbContext>> { - private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; public ModelStateValidationEnabledTests( - OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) { _testContext = testContext; @@ -19,7 +19,7 @@ public ModelStateValidationEnabledTests( } [Fact] - public async Task Resource_when_ModelStateValidation_is_enabled_produces_expected_required_property_in_schema() + public async Task Produces_expected_required_property_in_schema_for_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -28,15 +28,14 @@ public async Task Resource_when_ModelStateValidation_is_enabled_produces_expecte document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(requiredElement => { var requiredAttributes = JsonSerializer.Deserialize>(requiredElement.GetRawText()); - requiredAttributes.ShouldNotBeNull(); + requiredAttributes.ShouldHaveCount(5); requiredAttributes.Should().Contain("name"); requiredAttributes.Should().Contain("nameOfCurrentFarm"); requiredAttributes.Should().Contain("nickname"); requiredAttributes.Should().Contain("weight"); requiredAttributes.Should().Contain("hasProducedMilk"); - - requiredAttributes.ShouldHaveCount(5); }); } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs index b69bb69d12..d9a7f4b6eb 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs @@ -6,12 +6,11 @@ namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; public sealed class NullabilityTests - : IClassFixture, NullableReferenceTypesEnabledDbContext>> + : IClassFixture, NullableReferenceTypesEnabledDbContext>> { - private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; - public NullabilityTests( - OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + public NullabilityTests(OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) { _testContext = testContext; @@ -20,7 +19,7 @@ public NullabilityTests( } [Fact] - public async Task Resource_produces_expected_nullable_properties_in_schema() + public async Task Produces_expected_nullable_properties_in_schema_for_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -40,7 +39,7 @@ public async Task Resource_produces_expected_nullable_properties_in_schema() propertiesElement.ShouldContainPath("nameOfPreviousFarm").With(propertyElement => { - propertyElement.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + propertyElement.ShouldContainPath("nullable").With(element => element.ValueKind.Should().Be(JsonValueKind.True)); }); propertiesElement.ShouldContainPath("nickname").With(propertyElement => @@ -60,7 +59,7 @@ public async Task Resource_produces_expected_nullable_properties_in_schema() propertiesElement.ShouldContainPath("timeAtCurrentFarmInDays").With(propertyElement => { - propertyElement.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + propertyElement.ShouldContainPath("nullable").With(element => element.ValueKind.Should().Be(JsonValueKind.True)); }); propertiesElement.ShouldContainPath("hasProducedMilk").With(propertyElement => @@ -70,3 +69,4 @@ public async Task Resource_produces_expected_nullable_properties_in_schema() }); } } + diff --git a/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs b/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs deleted file mode 100644 index 03f6531631..0000000000 --- a/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Text.Json.Serialization; -using JetBrains.Annotations; -using JsonApiDotNetCore.Configuration; -using TestBuildingBlocks; - -namespace OpenApiTests.SchemaProperties; - -[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class SchemaPropertiesStartup : OpenApiStartup - where TDbContext : TestableDbContext -{ - protected override void SetJsonApiOptions(JsonApiOptions options) - { - base.SetJsonApiOptions(options); - - options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); - } -} From 7266bc6f1f472a7df0fe48b1db4b53e8e17061ac Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Mon, 19 Dec 2022 10:16:54 +0100 Subject: [PATCH 10/26] remove redundant new lines in eof added by cleanupcode --- test/OpenApiTests/OpenApiStartup.cs | 1 - .../SchemaProperties/ModelStateValidationDisabledStartup.cs | 1 - .../ModelStateValidationDisabledTests.cs | 1 - .../ModelStateValidationEnabledTests.cs | 1 - .../NullableReferenceTypesDisabled/NullabilityTests.cs | 1 - .../ModelStateValidationDisabledTests.cs | 1 - .../ModelStateValidationEnabledTests.cs | 1 - .../NullableReferenceTypesEnabled/NullabilityTests.cs | 1 - 8 files changed, 8 deletions(-) diff --git a/test/OpenApiTests/OpenApiStartup.cs b/test/OpenApiTests/OpenApiStartup.cs index a1a46e4022..e34ed1894c 100644 --- a/test/OpenApiTests/OpenApiStartup.cs +++ b/test/OpenApiTests/OpenApiStartup.cs @@ -26,4 +26,3 @@ public override void Configure(IApplicationBuilder app) app.UseEndpoints(endpoints => endpoints.MapControllers()); } } - diff --git a/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs b/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs index ba9ea319d0..1b4dfe7c62 100644 --- a/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs +++ b/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs @@ -15,4 +15,3 @@ protected override void SetJsonApiOptions(JsonApiOptions options) options.ValidateModelState = false; } } - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs index 209423df20..a48b3241b5 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs @@ -37,4 +37,3 @@ public async Task Produces_expected_required_property_in_schema_for_resource() }); } } - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs index b028eb9cfa..bbf087eb81 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs @@ -36,4 +36,3 @@ public async Task Produces_expected_required_property_in_schema_for_resource() }); } } - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs index 4e8b216dbe..907cae4400 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs @@ -59,4 +59,3 @@ public async Task Produces_expected_nullable_properties_in_schema_for_resource() }); } } - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs index 06083f643c..48fed662f6 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs @@ -38,4 +38,3 @@ public async Task Produces_expected_required_property_in_schema_for_resource() }); } } - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs index 034d49be49..7a1092ad33 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs @@ -38,4 +38,3 @@ public async Task Produces_expected_required_property_in_schema_for_resource() }); } } - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs index d9a7f4b6eb..007376e462 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs @@ -69,4 +69,3 @@ public async Task Produces_expected_nullable_properties_in_schema_for_resource() }); } } - From 945a8035ed88a6e5a991a0c64a64bdaddd1d89f9 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Mon, 19 Dec 2022 19:59:16 +0100 Subject: [PATCH 11/26] Improved naming in OpenApiTests/SchemaProperties --- .../ModelStateValidationDisabledTests.cs | 6 ++-- .../ModelStateValidationEnabledTests.cs | 6 ++-- .../NullabilityTests.cs | 26 +++++++------- .../ModelStateValidationDisabledTests.cs | 6 ++-- .../ModelStateValidationEnabledTests.cs | 4 +-- .../NullabilityTests.cs | 34 +++++++++---------- 6 files changed, 41 insertions(+), 41 deletions(-) diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs index a48b3241b5..3a53522397 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs @@ -20,15 +20,15 @@ public ModelStateValidationDisabledTests( } [Fact] - public async Task Produces_expected_required_property_in_schema_for_resource() + public async Task Produces_expected_required_property_set_in_schema_for_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(requiredElement => + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => { - var requiredAttributes = JsonSerializer.Deserialize>(requiredElement.GetRawText()); + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); requiredAttributes.ShouldHaveCount(3); requiredAttributes.Should().Contain("nameOfCurrentFarm"); diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs index bbf087eb81..1eb3562084 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs @@ -19,15 +19,15 @@ public ModelStateValidationEnabledTests( } [Fact] - public async Task Produces_expected_required_property_in_schema_for_resource() + public async Task Produces_expected_required_property_set_in_schema_for_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(requiredElement => + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => { - var requiredAttributes = JsonSerializer.Deserialize>(requiredElement.GetRawText()); + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); requiredAttributes.ShouldHaveCount(3); requiredAttributes.Should().Contain("nameOfCurrentFarm"); diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs index 907cae4400..f2ffecc549 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs @@ -25,36 +25,36 @@ public async Task Produces_expected_nullable_properties_in_schema_for_resource() JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(propertiesElement => + document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => { - propertiesElement.ShouldContainPath("name").With(propertyElement => + schemaProperties.ShouldContainPath("name").With(schemaProperty => { - propertyElement.ShouldContainPath("nullable").With(element => element.ValueKind.Should().Be(JsonValueKind.True)); + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); }); - propertiesElement.ShouldContainPath("nameOfCurrentFarm").With(propertyElement => + schemaProperties.ShouldContainPath("nameOfCurrentFarm").With(schemaProperty => { - propertyElement.ShouldNotContainPath("nullable"); + schemaProperty.ShouldNotContainPath("nullable"); }); - propertiesElement.ShouldContainPath("age").With(propertyElement => + schemaProperties.ShouldContainPath("age").With(schemaProperty => { - propertyElement.ShouldNotContainPath("nullable"); + schemaProperty.ShouldNotContainPath("nullable"); }); - propertiesElement.ShouldContainPath("weight").With(propertyElement => + schemaProperties.ShouldContainPath("weight").With(schemaProperty => { - propertyElement.ShouldNotContainPath("nullable"); + schemaProperty.ShouldNotContainPath("nullable"); }); - propertiesElement.ShouldContainPath("timeAtCurrentFarmInDays").With(propertyElement => + schemaProperties.ShouldContainPath("timeAtCurrentFarmInDays").With(schemaProperty => { - propertyElement.ShouldContainPath("nullable").With(element => element.ValueKind.Should().Be(JsonValueKind.True)); + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); }); - propertiesElement.ShouldContainPath("hasProducedEggs").With(propertyElement => + schemaProperties.ShouldContainPath("hasProducedEggs").With(schemaProperty => { - propertyElement.ShouldNotContainPath("nullable"); + schemaProperty.ShouldNotContainPath("nullable"); }); }); } diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs index 48fed662f6..2f46585b65 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs @@ -20,15 +20,15 @@ public ModelStateValidationDisabledTests( } [Fact] - public async Task Produces_expected_required_property_in_schema_for_resource() + public async Task Produces_expected_required_property_set_in_schema_for_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(requiredElement => + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => { - var requiredAttributes = JsonSerializer.Deserialize>(requiredElement.GetRawText()); + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); requiredAttributes.ShouldHaveCount(4); requiredAttributes.Should().Contain("nameOfCurrentFarm"); diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs index 7a1092ad33..fd5d56ef97 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs @@ -25,9 +25,9 @@ public async Task Produces_expected_required_property_in_schema_for_resource() JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(requiredElement => + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => { - var requiredAttributes = JsonSerializer.Deserialize>(requiredElement.GetRawText()); + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); requiredAttributes.ShouldHaveCount(5); requiredAttributes.Should().Contain("name"); diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs index 007376e462..4cd5d62ecf 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs @@ -25,46 +25,46 @@ public async Task Produces_expected_nullable_properties_in_schema_for_resource() JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(propertiesElement => + document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => { - propertiesElement.ShouldContainPath("name").With(propertyElement => + schemaProperties.ShouldContainPath("name").With(schemaProperty => { - propertyElement.ShouldNotContainPath("nullable"); + schemaProperty.ShouldNotContainPath("nullable"); }); - propertiesElement.ShouldContainPath("nameOfCurrentFarm").With(propertyElement => + schemaProperties.ShouldContainPath("nameOfCurrentFarm").With(schemaProperty => { - propertyElement.ShouldNotContainPath("nullable"); + schemaProperty.ShouldNotContainPath("nullable"); }); - propertiesElement.ShouldContainPath("nameOfPreviousFarm").With(propertyElement => + schemaProperties.ShouldContainPath("nameOfPreviousFarm").With(schemaProperty => { - propertyElement.ShouldContainPath("nullable").With(element => element.ValueKind.Should().Be(JsonValueKind.True)); + schemaProperty.ShouldContainPath("nullable").With(element => element.ValueKind.Should().Be(JsonValueKind.True)); }); - propertiesElement.ShouldContainPath("nickname").With(propertyElement => + schemaProperties.ShouldContainPath("nickname").With(schemaProperty => { - propertyElement.ShouldNotContainPath("nullable"); + schemaProperty.ShouldNotContainPath("nullable"); }); - propertiesElement.ShouldContainPath("age").With(propertyElement => + schemaProperties.ShouldContainPath("age").With(schemaProperty => { - propertyElement.ShouldNotContainPath("nullable"); + schemaProperty.ShouldNotContainPath("nullable"); }); - propertiesElement.ShouldContainPath("weight").With(propertyElement => + schemaProperties.ShouldContainPath("weight").With(schemaProperty => { - propertyElement.ShouldNotContainPath("nullable"); + schemaProperty.ShouldNotContainPath("nullable"); }); - propertiesElement.ShouldContainPath("timeAtCurrentFarmInDays").With(propertyElement => + schemaProperties.ShouldContainPath("timeAtCurrentFarmInDays").With(schemaProperty => { - propertyElement.ShouldContainPath("nullable").With(element => element.ValueKind.Should().Be(JsonValueKind.True)); + schemaProperty.ShouldContainPath("nullable").With(element => element.ValueKind.Should().Be(JsonValueKind.True)); }); - propertiesElement.ShouldContainPath("hasProducedMilk").With(propertyElement => + schemaProperties.ShouldContainPath("hasProducedMilk").With(schemaProperty => { - propertyElement.ShouldNotContainPath("nullable"); + schemaProperty.ShouldNotContainPath("nullable"); }); }); } From f5bb7fc1dbf80b4d3e275f28ac26a682134730f9 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Tue, 20 Dec 2022 13:24:12 +0100 Subject: [PATCH 12/26] Review feedback: NullabilityTests --- .../PropertyInfoAssertionsExtension.cs | 16 +----- .../NullabilityTests.cs | 26 ++++----- .../NullabilityTests.cs | 28 +++++---- .../ModelStateValidationDisabledTests.cs | 42 +++++++++++--- .../ModelStateValidationEnabledTests.cs | 42 +++++++++++--- .../NullabilityTests.cs | 43 +++++++------- .../ModelStateValidationDisabledTests.cs | 44 +++++++++++--- .../ModelStateValidationEnabledTests.cs | 47 +++++++++++---- .../NullabilityTests.cs | 57 ++++++++----------- 9 files changed, 213 insertions(+), 132 deletions(-) diff --git a/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs b/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs index b29c635f8e..e49bef1a0d 100644 --- a/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs +++ b/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs @@ -7,24 +7,14 @@ namespace OpenApiClientTests; internal static class PropertyInfoAssertionsExtensions { [CustomAssertion] - public static void BeNullable(this PropertyInfoAssertions source, string because = "", params object[] becauseArgs) + public static void HaveNullabilityState(this PropertyInfoAssertions source, NullabilityState expected, string because = "", params object[] becauseArgs) { PropertyInfo propertyInfo = source.Subject; NullabilityInfoContext nullabilityContext = new(); NullabilityInfo nullabilityInfo = nullabilityContext.Create(propertyInfo); - nullabilityInfo.ReadState.Should().NotBe(NullabilityState.NotNull, because, becauseArgs); - } - - [CustomAssertion] - public static void BeNonNullable(this PropertyInfoAssertions source, string because = "", params object[] becauseArgs) - { - PropertyInfo propertyInfo = source.Subject; - - NullabilityInfoContext nullabilityContext = new(); - NullabilityInfo nullabilityInfo = nullabilityContext.Create(propertyInfo); - - nullabilityInfo.ReadState.Should().Be(NullabilityState.NotNull, because, becauseArgs); + nullabilityInfo.ReadState.Should().Be(expected, because, becauseArgs); + nullabilityInfo.WriteState.Should().Be(expected, because, becauseArgs); } } diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs index 136c729537..76c80cbf73 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs @@ -7,21 +7,17 @@ namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; public sealed class NullabilityTests { - [Fact] - public void Nullability_of_generated_types_is_as_expected() + [Theory] + [InlineData(nameof(ChickenAttributesInResponse.Name), NullabilityState.Unknown)] + [InlineData(nameof(ChickenAttributesInResponse.NameOfCurrentFarm), NullabilityState.Unknown)] + [InlineData(nameof(ChickenAttributesInResponse.Age), NullabilityState.NotNull)] + [InlineData(nameof(ChickenAttributesInResponse.Weight), NullabilityState.NotNull)] + [InlineData(nameof(ChickenAttributesInResponse.TimeAtCurrentFarmInDays), NullabilityState.Nullable)] + [InlineData(nameof(ChickenAttributesInResponse.HasProducedEggs), NullabilityState.NotNull)] + public void Nullability_of_generated_property_is_as_expected(string propertyName, NullabilityState expectedState) { - PropertyInfo[] propertyInfos = typeof(ChickenAttributesInResponse).GetProperties(); - - PropertyInfo? propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(ChickenAttributesInResponse.Name)); - propertyInfo.Should().BeNullable(); - - propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(ChickenAttributesInResponse.NameOfCurrentFarm)); - propertyInfo.Should().BeNullable(); - - propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(ChickenAttributesInResponse.Age)); - propertyInfo.Should().BeNonNullable(); - - propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(ChickenAttributesInResponse.TimeAtCurrentFarmInDays)); - propertyInfo.Should().BeNullable(); + PropertyInfo[] properties = typeof(ChickenAttributesInResponse).GetProperties(); + PropertyInfo property = properties.Single(property => property.Name == propertyName); + property.Should().HaveNullabilityState(expectedState); } } diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs index dfb1f0a934..d351867b8b 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs @@ -7,21 +7,19 @@ namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled; public sealed class NullabilityTests { - [Fact] - public void Nullability_of_generated_types_is_as_expected() + [Theory] + [InlineData(nameof(CowAttributesInResponse.Name), NullabilityState.NotNull)] + [InlineData(nameof(CowAttributesInResponse.NameOfCurrentFarm), NullabilityState.NotNull)] + [InlineData(nameof(CowAttributesInResponse.NameOfPreviousFarm), NullabilityState.Nullable)] + [InlineData(nameof(CowAttributesInResponse.Nickname), NullabilityState.NotNull)] + [InlineData(nameof(CowAttributesInResponse.Age), NullabilityState.NotNull)] + [InlineData(nameof(CowAttributesInResponse.Weight), NullabilityState.NotNull)] + [InlineData(nameof(CowAttributesInResponse.TimeAtCurrentFarmInDays), NullabilityState.Nullable)] + [InlineData(nameof(CowAttributesInResponse.HasProducedMilk), NullabilityState.NotNull)] + public void Nullability_of_generated_property_is_as_expected(string propertyName, NullabilityState expectedState) { - PropertyInfo[] propertyInfos = typeof(CowAttributesInResponse).GetProperties(); - - PropertyInfo? propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(CowAttributesInResponse.Name)); - propertyInfo.Should().BeNonNullable(); - - propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(CowAttributesInResponse.NameOfPreviousFarm)); - propertyInfo.Should().BeNullable(); - - propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(CowAttributesInResponse.Age)); - propertyInfo.Should().BeNonNullable(); - - propertyInfo = propertyInfos.FirstOrDefault(property => property.Name == nameof(CowAttributesInResponse.TimeAtCurrentFarmInDays)); - propertyInfo.Should().BeNullable(); + PropertyInfo[] properties = typeof(CowAttributesInResponse).GetProperties(); + PropertyInfo property = properties.Single(property => property.Name == propertyName); + property.Should().HaveNullabilityState(expectedState); } } diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs index 3a53522397..0149a07e32 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs @@ -19,8 +19,29 @@ public ModelStateValidationDisabledTests( testContext.UseController(); } - [Fact] - public async Task Produces_expected_required_property_set_in_schema_for_resource() + [Theory] + [InlineData("nameOfCurrentFarm")] + [InlineData("weight")] + [InlineData("hasProducedEggs")] + public async Task Property_in_schema_for_attributes_in_POST_request_should_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain(propertyName); + }); + } + + [Theory] + [InlineData("name")] + [InlineData("age")] + [InlineData("timeAtCurrentFarmInDays")] + public async Task Property_in_schema_for_attributes_in_POST_request_should_not_be_required(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -28,12 +49,19 @@ public async Task Produces_expected_required_property_set_in_schema_for_resource // Assert document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - requiredAttributes.ShouldHaveCount(3); + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - requiredAttributes.Should().Contain("nameOfCurrentFarm"); - requiredAttributes.Should().Contain("weight"); - requiredAttributes.Should().Contain("hasProducedEggs"); + requiredProperties.Should().NotContain(propertyName); }); } + + [Fact] + public async Task Schema_for_attributes_in_PATCH_request_should_have_no_required_properties() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); + } } diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs index 1eb3562084..e200fe365c 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs @@ -18,8 +18,29 @@ public ModelStateValidationEnabledTests( testContext.UseController(); } - [Fact] - public async Task Produces_expected_required_property_set_in_schema_for_resource() + [Theory] + [InlineData("nameOfCurrentFarm")] + [InlineData("weight")] + [InlineData("hasProducedEggs")] + public async Task Property_in_schema_for_attributes_in_POST_request_should_be_required(string attributeName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain(attributeName); + }); + } + + [Theory] + [InlineData("name")] + [InlineData("age")] + [InlineData("timeAtCurrentFarmInDays")] + public async Task Property_in_schema_for_attributes_in_POST_request_should_not_be_required(string attributeName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -27,12 +48,19 @@ public async Task Produces_expected_required_property_set_in_schema_for_resource // Assert document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - requiredAttributes.ShouldHaveCount(3); + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - requiredAttributes.Should().Contain("nameOfCurrentFarm"); - requiredAttributes.Should().Contain("weight"); - requiredAttributes.Should().Contain("hasProducedEggs"); + requiredProperties.Should().NotContain(attributeName); }); } + + [Fact] + public async Task Schema_for_attributes_in_PATCH_request_should_have_no_required_properties() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); + } } diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs index f2ffecc549..a5485de6e5 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs @@ -18,8 +18,10 @@ public NullabilityTests(OpenApiTestContext { - schemaProperties.ShouldContainPath("name").With(schemaProperty => + schemaProperties.ShouldContainPath(propertyName).With(schemaProperty => { schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); }); + }); + } - schemaProperties.ShouldContainPath("nameOfCurrentFarm").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - - schemaProperties.ShouldContainPath("age").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - - schemaProperties.ShouldContainPath("weight").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - - schemaProperties.ShouldContainPath("timeAtCurrentFarmInDays").With(schemaProperty => - { - schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); - }); + [Theory] + [InlineData("nameOfCurrentFarm")] + [InlineData("age")] + [InlineData("weight")] + [InlineData("hasProducedEggs")] + public async Task Property_in_schema_for_resource_should_not_be_nullable(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - schemaProperties.ShouldContainPath("hasProducedEggs").With(schemaProperty => + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath(propertyName).With(schemaProperty => { schemaProperty.ShouldNotContainPath("nullable"); }); diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs index 2f46585b65..988fe06896 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs @@ -19,8 +19,30 @@ public ModelStateValidationDisabledTests( testContext.UseController(); } - [Fact] - public async Task Produces_expected_required_property_set_in_schema_for_resource() + [Theory] + [InlineData("nameOfCurrentFarm")] + [InlineData("nickname")] + [InlineData("weight")] + [InlineData("hasProducedMilk")] + public async Task Property_in_schema_for_attributes_in_POST_request_should_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain(propertyName); + }); + } + + [Theory] + [InlineData("name")] + [InlineData("age")] + [InlineData("timeAtCurrentFarmInDays")] + public async Task Property_in_schema_for_attributes_in_POST_request_should_not_be_required(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -28,13 +50,19 @@ public async Task Produces_expected_required_property_set_in_schema_for_resource // Assert document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - requiredAttributes.ShouldHaveCount(4); + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - requiredAttributes.Should().Contain("nameOfCurrentFarm"); - requiredAttributes.Should().Contain("nickname"); - requiredAttributes.Should().Contain("weight"); - requiredAttributes.Should().Contain("hasProducedMilk"); + requiredProperties.Should().NotContain(propertyName); }); } + + [Fact] + public async Task Schema_for_attributes_in_PATCH_request_should_have_no_required_properties() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); + } } diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs index fd5d56ef97..70e4b3f7a4 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs @@ -18,8 +18,13 @@ public ModelStateValidationEnabledTests( testContext.UseController(); } - [Fact] - public async Task Produces_expected_required_property_in_schema_for_resource() + [Theory] + [InlineData("name")] + [InlineData("nameOfCurrentFarm")] + [InlineData("nickname")] + [InlineData("weight")] + [InlineData("hasProducedMilk")] + public async Task Property_in_schema_for_attributes_in_POST_request_should_be_required(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -27,14 +32,36 @@ public async Task Produces_expected_required_property_in_schema_for_resource() // Assert document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - requiredAttributes.ShouldHaveCount(5); - - requiredAttributes.Should().Contain("name"); - requiredAttributes.Should().Contain("nameOfCurrentFarm"); - requiredAttributes.Should().Contain("nickname"); - requiredAttributes.Should().Contain("weight"); - requiredAttributes.Should().Contain("hasProducedMilk"); + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain(propertyName); }); } + + [Theory] + [InlineData("age")] + [InlineData("timeAtCurrentFarmInDays")] + public async Task Property_in_schema_for_attributes_in_POST_request_should_not_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain(propertyName); + }); + } + + [Fact] + public async Task Schema_for_attributes_in_PATCH_request_should_have_no_required_properties() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); + } } diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs index 4cd5d62ecf..3b8d0597d7 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs @@ -18,8 +18,10 @@ public NullabilityTests(OpenApiTestContext { - schemaProperties.ShouldContainPath("name").With(schemaProperty => + schemaProperties.ShouldContainPath(propertyName).With(schemaProperty => { - schemaProperty.ShouldNotContainPath("nullable"); - }); - - schemaProperties.ShouldContainPath("nameOfCurrentFarm").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - - schemaProperties.ShouldContainPath("nameOfPreviousFarm").With(schemaProperty => - { - schemaProperty.ShouldContainPath("nullable").With(element => element.ValueKind.Should().Be(JsonValueKind.True)); - }); - - schemaProperties.ShouldContainPath("nickname").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - - schemaProperties.ShouldContainPath("age").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - - schemaProperties.ShouldContainPath("weight").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); }); + }); + } - schemaProperties.ShouldContainPath("timeAtCurrentFarmInDays").With(schemaProperty => - { - schemaProperty.ShouldContainPath("nullable").With(element => element.ValueKind.Should().Be(JsonValueKind.True)); - }); + [Theory] + [InlineData("name")] + [InlineData("nameOfCurrentFarm")] + [InlineData("nickname")] + [InlineData("age")] + [InlineData("weight")] + [InlineData("hasProducedMilk")] + public async Task Property_in_schema_for_resource_should_not_be_nullable(string attributeName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - schemaProperties.ShouldContainPath("hasProducedMilk").With(schemaProperty => + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath(attributeName).With(schemaProperty => { schemaProperty.ShouldNotContainPath("nullable"); }); From 919a3f8161424b75bcaaabc2e94aedefe804f7f9 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Tue, 20 Dec 2022 13:41:10 +0100 Subject: [PATCH 13/26] Improved JsonApiClient and testing SchemaProperty Tests: * More rigorous test suite, see PR description IJsonApiClient: * Renamed registration method to a more functionally descriptive name. * Improved documentation to contain most relevant information on top instead of at the end, and removed ambiguigity in wording. JsonApiClient * Fix bug: disallow omitting members that are explicitly required by the OAS description * Renamed AttributeNamesContainer to AttributesObjectContext because it was more than just a container * Misc: better variable naming --- .../IJsonApiClient.cs | 19 +- .../JsonApiClient.cs | 234 ++++++++++++---- .../UnreachableCodeException.cs | 9 + ...questDocumentRegistrationLifetimeTests.cs} | 44 +-- .../LegacyClient/RequestTests.cs | 4 +- test/OpenApiClientTests/ObjectExtensions.cs | 20 ++ .../RequiredAttributesTests.cs | 249 +++++++++++++++-- .../RequiredAttributesTests.cs | 251 ++++++++++++++++-- 8 files changed, 711 insertions(+), 119 deletions(-) create mode 100644 src/JsonApiDotNetCore.OpenApi.Client/UnreachableCodeException.cs rename test/OpenApiClientTests/LegacyClient/{ClientAttributeRegistrationLifeTimeTests.cs => RequestDocumentRegistrationLifetimeTests.cs} (79%) create mode 100644 test/OpenApiClientTests/ObjectExtensions.cs diff --git a/src/JsonApiDotNetCore.OpenApi.Client/IJsonApiClient.cs b/src/JsonApiDotNetCore.OpenApi.Client/IJsonApiClient.cs index a994d78a0e..18c90fdfd2 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client/IJsonApiClient.cs +++ b/src/JsonApiDotNetCore.OpenApi.Client/IJsonApiClient.cs @@ -5,14 +5,19 @@ namespace JsonApiDotNetCore.OpenApi.Client; public interface IJsonApiClient { /// - /// Ensures correct serialization of attributes in a POST/PATCH Resource request body. In JSON:API, an omitted attribute indicates to ignore it, while an - /// attribute that is set to "null" means to clear it. This poses a problem because the serializer cannot distinguish between "you have explicitly set - /// this .NET property to null" vs "you didn't touch it, so it is null by default" when converting an instance to JSON. Therefore, calling this method - /// treats all attributes that contain their default value (null for reference types, 0 for integers, false for booleans, etc) as - /// omitted unless explicitly listed to include them using . + /// + /// Calling this method ensures that attributes containing a default value (null for reference types, 0 for integers, false for + /// booleans, etc) are omitted during serialization, except for those explicitly marked for inclusion in + /// . + /// + /// + /// This is sometimes required to ensure correct serialization of attributes during a POST/PATCH request. In JSON:API, an omitted attribute indicates to + /// ignore it, while an attribute that is set to "null" means to clear it. This poses a problem because the serializer cannot distinguish between "you + /// have explicitly set this .NET property to null" vs "you didn't touch it, so it is null by default" when converting an instance to JSON. + /// /// /// - /// The request document instance for which this registration applies. + /// The request document instance for which default values should be omitted. /// /// /// Optional. A list of expressions to indicate which properties to unconditionally include in the JSON request body. For example: @@ -30,7 +35,7 @@ public interface IJsonApiClient /// An to clear the current registration. For efficient memory usage, it is recommended to wrap calls to this method in a /// using statement, so the registrations are cleaned up after executing the request. /// - IDisposable RegisterAttributesForRequestDocument(TRequestDocument requestDocument, + IDisposable OmitDefaultValuesForAttributesInRequestDocument(TRequestDocument requestDocument, params Expression>[] alwaysIncludedAttributeSelectors) where TRequestDocument : class; } diff --git a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs index 219ac04353..03ea71e8e0 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs +++ b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using SysNotNull = System.Diagnostics.CodeAnalysis.NotNullAttribute; namespace JsonApiDotNetCore.OpenApi.Client; @@ -23,7 +24,7 @@ protected void SetSerializerSettingsForJsonApi(JsonSerializerSettings settings) } /// - public IDisposable RegisterAttributesForRequestDocument(TRequestDocument requestDocument, + public IDisposable OmitDefaultValuesForAttributesInRequestDocument(TRequestDocument requestDocument, params Expression>[] alwaysIncludedAttributeSelectors) where TRequestDocument : class { @@ -43,9 +44,10 @@ public IDisposable RegisterAttributesForRequestDocument _alwaysIncludedAttributesPerRequestDocumentInstance = new(); - private readonly Dictionary> _requestDocumentInstancesPerRequestDocumentType = new(); - private bool _isSerializing; + private readonly Dictionary _attributesObjectInfoByRequestDocument = new(); + private readonly Dictionary> _requestDocumentsByType = new(); + private SerializationScope? _serializationScope; public override bool CanRead => false; - public void RegisterRequestDocument(object requestDocument, AttributeNamesContainer attributes) + public void RegisterRequestDocumentForAttributesOmission(object requestDocument, AttributesObjectInfo attributesObjectInfo) { - _alwaysIncludedAttributesPerRequestDocumentInstance[requestDocument] = attributes; + _attributesObjectInfoByRequestDocument[requestDocument] = attributesObjectInfo; Type requestDocumentType = requestDocument.GetType(); - if (!_requestDocumentInstancesPerRequestDocumentType.ContainsKey(requestDocumentType)) + if (!_requestDocumentsByType.ContainsKey(requestDocumentType)) { - _requestDocumentInstancesPerRequestDocumentType[requestDocumentType] = new HashSet(); + _requestDocumentsByType[requestDocumentType] = new HashSet(); } - _requestDocumentInstancesPerRequestDocumentType[requestDocumentType].Add(requestDocument); + _requestDocumentsByType[requestDocumentType].Add(requestDocument); } - public void RemoveAttributeRegistration(object requestDocument) + public void RemoveRegistration(object requestDocument) { - if (_alwaysIncludedAttributesPerRequestDocumentInstance.ContainsKey(requestDocument)) + if (_attributesObjectInfoByRequestDocument.ContainsKey(requestDocument)) { - _alwaysIncludedAttributesPerRequestDocumentInstance.Remove(requestDocument); + _attributesObjectInfoByRequestDocument.Remove(requestDocument); Type requestDocumentType = requestDocument.GetType(); - _requestDocumentInstancesPerRequestDocumentType[requestDocumentType].Remove(requestDocument); + _requestDocumentsByType[requestDocumentType].Remove(requestDocument); - if (!_requestDocumentInstancesPerRequestDocumentType[requestDocumentType].Any()) + if (!_requestDocumentsByType[requestDocumentType].Any()) { - _requestDocumentInstancesPerRequestDocumentType.Remove(requestDocumentType); + _requestDocumentsByType.Remove(requestDocumentType); } } } @@ -107,71 +109,195 @@ public override bool CanConvert(Type objectType) { ArgumentGuard.NotNull(objectType); - return !_isSerializing && _requestDocumentInstancesPerRequestDocumentType.ContainsKey(objectType); + if (_serializationScope == null) + { + return _requestDocumentsByType.ContainsKey(objectType); + } + + return _serializationScope.ShouldConvertAsAttributesObject(objectType); } public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { - throw new Exception("This code should not be reachable."); + throw new UnreachableCodeException(); } public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { ArgumentGuard.NotNull(writer); + ArgumentGuard.NotNull(value); ArgumentGuard.NotNull(serializer); - if (value != null) + if (_serializationScope == null) { - if (_alwaysIncludedAttributesPerRequestDocumentInstance.ContainsKey(value)) - { - AttributeNamesContainer attributeNamesContainer = _alwaysIncludedAttributesPerRequestDocumentInstance[value]; - serializer.ContractResolver = new JsonApiDocumentContractResolver(attributeNamesContainer); - } + AssertObjectIsRequestDocument(value); - try - { - _isSerializing = true; - serializer.Serialize(writer, value); - } - finally + SerializeRequestDocument(writer, value, serializer); + } + else + { + AttributesObjectInfo? attributesObjectInfo = _serializationScope.AttributesObjectInScope; + + AssertObjectMatchesSerializationScope(attributesObjectInfo, value); + + SerializeAttributesObject(attributesObjectInfo, writer, value, serializer); + } + } + + private void AssertObjectIsRequestDocument(object value) + { + Type objectType = value.GetType(); + + if (!_requestDocumentsByType.ContainsKey(objectType)) + { + throw new UnreachableCodeException(); + } + } + + private void SerializeRequestDocument(JsonWriter writer, object value, JsonSerializer serializer) + { + _serializationScope = new SerializationScope(); + + if (_attributesObjectInfoByRequestDocument.TryGetValue(value, out AttributesObjectInfo? attributesObjectInfo)) + { + _serializationScope.AttributesObjectInScope = attributesObjectInfo; + } + + try + { + serializer.Serialize(writer, value); + } + finally + { + _serializationScope = null; + } + } + + private static void AssertObjectMatchesSerializationScope([SysNotNull] AttributesObjectInfo? attributesObjectInfo, object value) + { + Type objectType = value.GetType(); + + if (attributesObjectInfo == null || !attributesObjectInfo.MatchesType(objectType)) + { + throw new UnreachableCodeException(); + } + } + + private static void SerializeAttributesObject(AttributesObjectInfo alwaysIncludedAttributes, JsonWriter writer, object value, JsonSerializer serializer) + { + AssertRequiredPropertiesAreNotExcluded(value, alwaysIncludedAttributes); + + serializer.ContractResolver = new JsonApiAttributeContractResolver(alwaysIncludedAttributes); + serializer.Serialize(writer, value); + } + + private static void AssertRequiredPropertiesAreNotExcluded(object value, AttributesObjectInfo alwaysIncludedAttributes) + { + PropertyInfo[] propertyInfos = value.GetType().GetProperties(); + + foreach (PropertyInfo attributesPropertyInfo in propertyInfos) + { + bool isExplicitlyIncluded = alwaysIncludedAttributes.IsAttributeMarkedForInclusion(attributesPropertyInfo.Name); + + if (isExplicitlyIncluded) { - _isSerializing = false; + return; } + + AssertRequiredPropertyIsNotIgnored(value, attributesPropertyInfo); + } + } + + private static void AssertRequiredPropertyIsNotIgnored(object value, PropertyInfo attribute) + { + JsonPropertyAttribute jsonPropertyForAttribute = attribute.GetCustomAttributes().Single(); + + if (jsonPropertyForAttribute.Required != Required.Always) + { + return; + } + + bool isPropertyIgnored = DefaultValueEqualsCurrentValue(attribute, value); + + if (isPropertyIgnored) + { + throw new JsonSerializationException( + $"Ignored property '{jsonPropertyForAttribute.PropertyName}' must have a value because it is required. Path 'data.attributes'."); } } + + private static bool DefaultValueEqualsCurrentValue(PropertyInfo propertyInfo, object instance) + { + object? currentValue = propertyInfo.GetValue(instance); + object? defaultValue = GetDefaultValue(propertyInfo.PropertyType); + + if (defaultValue == null) + { + return currentValue == null; + } + + return defaultValue.Equals(currentValue); + } + + private static object? GetDefaultValue(Type type) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + } + + private sealed class SerializationScope + { + private bool _isFirstAttemptToConvertAttributes = true; + public AttributesObjectInfo? AttributesObjectInScope { get; set; } + + public bool ShouldConvertAsAttributesObject(Type type) + { + if (!_isFirstAttemptToConvertAttributes || AttributesObjectInScope == null) + { + return false; + } + + if (!AttributesObjectInScope.MatchesType(type)) + { + return false; + } + + _isFirstAttemptToConvertAttributes = false; + return true; + } } - private sealed class AttributeNamesContainer + private sealed class AttributesObjectInfo { - private readonly ISet _attributeNames; - private readonly Type _containerType; + private readonly ISet _attributesMarkedForInclusion; + private readonly Type _attributesObjectType; - public AttributeNamesContainer(ISet attributeNames, Type containerType) + public AttributesObjectInfo(ISet attributesMarkedForInclusion, Type attributesObjectType) { - ArgumentGuard.NotNull(attributeNames); - ArgumentGuard.NotNull(containerType); + ArgumentGuard.NotNull(attributesMarkedForInclusion); + ArgumentGuard.NotNull(attributesObjectType); - _attributeNames = attributeNames; - _containerType = containerType; + _attributesMarkedForInclusion = attributesMarkedForInclusion; + _attributesObjectType = attributesObjectType; } - public bool ContainsAttribute(string name) + public bool IsAttributeMarkedForInclusion(string name) { - return _attributeNames.Contains(name); + return _attributesMarkedForInclusion.Contains(name); } - public bool ContainerMatchesType(Type type) + public bool MatchesType(Type type) { - return _containerType == type; + return _attributesObjectType == type; } } - private sealed class AttributesRegistrationScope : IDisposable + private sealed class RequestDocumentRegistrationScope : IDisposable { private readonly JsonApiJsonConverter _jsonApiJsonConverter; private readonly object _requestDocument; - public AttributesRegistrationScope(JsonApiJsonConverter jsonApiJsonConverter, object requestDocument) + public RequestDocumentRegistrationScope(JsonApiJsonConverter jsonApiJsonConverter, object requestDocument) { ArgumentGuard.NotNull(jsonApiJsonConverter); ArgumentGuard.NotNull(requestDocument); @@ -182,28 +308,28 @@ public AttributesRegistrationScope(JsonApiJsonConverter jsonApiJsonConverter, ob public void Dispose() { - _jsonApiJsonConverter.RemoveAttributeRegistration(_requestDocument); + _jsonApiJsonConverter.RemoveRegistration(_requestDocument); } } - private sealed class JsonApiDocumentContractResolver : DefaultContractResolver + private sealed class JsonApiAttributeContractResolver : DefaultContractResolver { - private readonly AttributeNamesContainer _attributeNamesContainer; + private readonly AttributesObjectInfo _attributesObjectInfo; - public JsonApiDocumentContractResolver(AttributeNamesContainer attributeNamesContainer) + public JsonApiAttributeContractResolver(AttributesObjectInfo attributesObjectInfo) { - ArgumentGuard.NotNull(attributeNamesContainer); + ArgumentGuard.NotNull(attributesObjectInfo); - _attributeNamesContainer = attributeNamesContainer; + _attributesObjectInfo = attributesObjectInfo; } protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); - if (_attributeNamesContainer.ContainerMatchesType(property.DeclaringType!)) + if (_attributesObjectInfo.MatchesType(property.DeclaringType!)) { - if (_attributeNamesContainer.ContainsAttribute(property.UnderlyingName!)) + if (_attributesObjectInfo.IsAttributeMarkedForInclusion(property.UnderlyingName!)) { property.NullValueHandling = NullValueHandling.Include; property.DefaultValueHandling = DefaultValueHandling.Include; diff --git a/src/JsonApiDotNetCore.OpenApi.Client/UnreachableCodeException.cs b/src/JsonApiDotNetCore.OpenApi.Client/UnreachableCodeException.cs new file mode 100644 index 0000000000..f1821329d0 --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi.Client/UnreachableCodeException.cs @@ -0,0 +1,9 @@ +namespace JsonApiDotNetCore.OpenApi.Client; + +internal sealed class UnreachableCodeException : Exception +{ + public UnreachableCodeException() + : base("This code should not be reachable.") + { + } +} diff --git a/test/OpenApiClientTests/LegacyClient/ClientAttributeRegistrationLifeTimeTests.cs b/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs similarity index 79% rename from test/OpenApiClientTests/LegacyClient/ClientAttributeRegistrationLifeTimeTests.cs rename to test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs index 4582f9578d..73e134fa2d 100644 --- a/test/OpenApiClientTests/LegacyClient/ClientAttributeRegistrationLifeTimeTests.cs +++ b/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs @@ -6,10 +6,10 @@ namespace OpenApiClientTests.LegacyClient; -public sealed class ClientAttributeRegistrationLifetimeTests +public sealed class RequestDocumentRegistrationLifetimeTests { [Fact] - public async Task Disposed_attribute_registration_for_document_does_not_affect_request() + public async Task Disposed_request_document_registration_does_not_affect_request() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); @@ -27,7 +27,7 @@ public async Task Disposed_attribute_registration_for_document_does_not_affect_r } }; - using (apiClient.RegisterAttributesForRequestDocument(requestDocument, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, airplane => airplane.AirtimeInHours)) { _ = await ApiResponse.TranslateAsync(async () => await apiClient.PatchAirplaneAsync(airplaneId, requestDocument)); @@ -51,7 +51,7 @@ public async Task Disposed_attribute_registration_for_document_does_not_affect_r } [Fact] - public async Task Attribute_registration_can_be_used_for_multiple_requests() + public async Task Request_document_registration_can_be_used_for_multiple_requests() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); @@ -72,7 +72,7 @@ public async Task Attribute_registration_can_be_used_for_multiple_requests() } }; - using (apiClient.RegisterAttributesForRequestDocument(requestDocument, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, airplane => airplane.AirtimeInHours)) { _ = await ApiResponse.TranslateAsync(async () => await apiClient.PatchAirplaneAsync(airplaneId, requestDocument)); @@ -98,7 +98,7 @@ public async Task Attribute_registration_can_be_used_for_multiple_requests() } [Fact] - public async Task Request_is_unaffected_by_attribute_registration_for_different_document_of_same_type() + public async Task Request_is_unaffected_by_request_document_registration_of_different_request_document_of_same_type() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); @@ -128,10 +128,10 @@ public async Task Request_is_unaffected_by_attribute_registration_for_different_ } }; - using (apiClient.RegisterAttributesForRequestDocument(requestDocument1, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument1, airplane => airplane.AirtimeInHours)) { - using (apiClient.RegisterAttributesForRequestDocument(requestDocument2, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument2, airplane => airplane.SerialNumber)) { } @@ -153,7 +153,7 @@ public async Task Request_is_unaffected_by_attribute_registration_for_different_ } [Fact] - public async Task Attribute_values_can_be_changed_after_attribute_registration() + public async Task Attribute_values_can_be_changed_after_request_document_registration() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); @@ -174,7 +174,7 @@ public async Task Attribute_values_can_be_changed_after_attribute_registration() } }; - using (apiClient.RegisterAttributesForRequestDocument(requestDocument, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, airplane => airplane.IsInMaintenance)) { requestDocument.Data.Attributes.IsInMaintenance = false; @@ -196,7 +196,7 @@ public async Task Attribute_values_can_be_changed_after_attribute_registration() } [Fact] - public async Task Attribute_registration_is_unaffected_by_successive_attribute_registration_for_document_of_different_type() + public async Task Request_document_registration_is_unaffected_by_successive_registration_of_request_document_of_different_type() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); @@ -223,10 +223,10 @@ public async Task Attribute_registration_is_unaffected_by_successive_attribute_r } }; - using (apiClient.RegisterAttributesForRequestDocument(requestDocument1, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument1, airplane => airplane.IsInMaintenance)) { - using (apiClient.RegisterAttributesForRequestDocument(requestDocument2, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument2, airplane => airplane.AirtimeInHours)) { // Act @@ -247,7 +247,7 @@ public async Task Attribute_registration_is_unaffected_by_successive_attribute_r } [Fact] - public async Task Attribute_registration_is_unaffected_by_preceding_disposed_attribute_registration_for_different_document_of_same_type() + public async Task Request_document_registration_is_unaffected_by_preceding_disposed_registration_of_different_request_document_of_same_type() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); @@ -265,7 +265,7 @@ public async Task Attribute_registration_is_unaffected_by_preceding_disposed_att } }; - using (apiClient.RegisterAttributesForRequestDocument(requestDocument1, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument1, airplane => airplane.AirtimeInHours)) { _ = await ApiResponse.TranslateAsync(async () => await apiClient.PatchAirplaneAsync(airplaneId1, requestDocument1)); @@ -288,7 +288,7 @@ public async Task Attribute_registration_is_unaffected_by_preceding_disposed_att wrapper.ChangeResponse(HttpStatusCode.NoContent, null); - using (apiClient.RegisterAttributesForRequestDocument(requestDocument2, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument2, airplane => airplane.SerialNumber)) { // Act @@ -309,7 +309,7 @@ public async Task Attribute_registration_is_unaffected_by_preceding_disposed_att } [Fact] - public async Task Attribute_registration_is_unaffected_by_preceding_disposed_attribute_registration_for_document_of_different_type() + public async Task Request_document_registration_is_unaffected_by_preceding_disposed_registration_of_request_document_of_different_type() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); @@ -324,7 +324,7 @@ public async Task Attribute_registration_is_unaffected_by_preceding_disposed_att } }; - using (apiClient.RegisterAttributesForRequestDocument(requestDocument1, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument1, airplane => airplane.AirtimeInHours)) { _ = await ApiResponse.TranslateAsync(async () => await apiClient.PostAirplaneAsync(requestDocument1)); @@ -347,7 +347,7 @@ public async Task Attribute_registration_is_unaffected_by_preceding_disposed_att wrapper.ChangeResponse(HttpStatusCode.NoContent, null); - using (apiClient.RegisterAttributesForRequestDocument(requestDocument2, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument2, airplane => airplane.SerialNumber)) { // Act @@ -368,7 +368,7 @@ public async Task Attribute_registration_is_unaffected_by_preceding_disposed_att } [Fact] - public async Task Attribute_registration_is_unaffected_by_preceding_attribute_registration_for_different_document_of_same_type() + public async Task Request_document_registration_is_unaffected_by_preceding_registration_of_different_request_document_of_same_type() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); @@ -398,10 +398,10 @@ public async Task Attribute_registration_is_unaffected_by_preceding_attribute_re } }; - using (apiClient.RegisterAttributesForRequestDocument(requestDocument1, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument1, airplane => airplane.SerialNumber)) { - using (apiClient.RegisterAttributesForRequestDocument(requestDocument2, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument2, airplane => airplane.IsInMaintenance, airplane => airplane.AirtimeInHours)) { // Act diff --git a/test/OpenApiClientTests/LegacyClient/RequestTests.cs b/test/OpenApiClientTests/LegacyClient/RequestTests.cs index e03e8f1015..8bb0c3f350 100644 --- a/test/OpenApiClientTests/LegacyClient/RequestTests.cs +++ b/test/OpenApiClientTests/LegacyClient/RequestTests.cs @@ -152,7 +152,7 @@ public async Task Partial_posting_resource_produces_expected_request() } }; - using (apiClient.RegisterAttributesForRequestDocument(requestDocument, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, airplane => airplane.SerialNumber)) { // Act @@ -203,7 +203,7 @@ public async Task Partial_patching_resource_produces_expected_request() } }; - using (apiClient.RegisterAttributesForRequestDocument(requestDocument, + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, airplane => airplane.SerialNumber, airplane => airplane.LastServicedAt, airplane => airplane.IsInMaintenance, airplane => airplane.AirtimeInHours)) { // Act diff --git a/test/OpenApiClientTests/ObjectExtensions.cs b/test/OpenApiClientTests/ObjectExtensions.cs new file mode 100644 index 0000000000..e3790eaa15 --- /dev/null +++ b/test/OpenApiClientTests/ObjectExtensions.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using JsonApiDotNetCore.OpenApi.Client; + +namespace OpenApiClientTests; + +internal static class ObjectExtensions +{ + public static void SetPropertyToDefaultValue(this object target, string propertyName) + { + ArgumentGuard.NotNull(target); + ArgumentGuard.NotNull(propertyName); + + Type declaringType = target.GetType(); + + PropertyInfo property = declaringType.GetProperties().Single(property => property.Name == propertyName); + object? defaultValue = declaringType.IsValueType ? Activator.CreateInstance(declaringType) : null; + + property.SetValue(target, defaultValue); + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs index b97535f908..f43f74543a 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs @@ -12,10 +12,10 @@ namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; public sealed class RequiredAttributesTests { - private const string HostPrefix = "http://localhost/"; + private const string ChickenUrl = "http://localhost/chickens"; [Fact] - public async Task Partial_posting_resource_with_explicitly_omitting_required_fields_produces_expected_request() + public async Task Can_exclude_optional_attributes() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); @@ -27,13 +27,14 @@ public async Task Partial_posting_resource_with_explicitly_omitting_required_fie { Attributes = new ChickenAttributesInPostRequest { + NameOfCurrentFarm = "Cow and Chicken Farm", + Weight = 30, HasProducedEggs = true } } }; - using (apiClient.RegisterAttributesForRequestDocument(requestDocument, - chicken => chicken.HasProducedEggs)) + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) { // Act await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); @@ -43,7 +44,7 @@ public async Task Partial_posting_resource_with_explicitly_omitting_required_fie wrapper.Request.ShouldNotBeNull(); wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(HostPrefix + "chickens"); + wrapper.Request.RequestUri.Should().Be(ChickenUrl); wrapper.Request.Content.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); @@ -52,43 +53,109 @@ public async Task Partial_posting_resource_with_explicitly_omitting_required_fie ""data"": { ""type"": ""chickens"", ""attributes"": { + ""nameOfCurrentFarm"": ""Cow and Chicken Farm"", + ""weight"": 30, ""hasProducedEggs"": true } } }"); } - [Fact] - public async Task Partial_posting_resource_without_explicitly_omitting_required_fields_fails() + [Theory] + [InlineData(nameof(ChickenAttributesInResponse.NameOfCurrentFarm), "nameOfCurrentFarm")] + [InlineData(nameof(ChickenAttributesInResponse.Weight), "weight")] + [InlineData(nameof(ChickenAttributesInResponse.HasProducedEggs), "hasProducedEggs")] + public async Task Cannot_exclude_required_attribute_when_performing_POST(string propertyName, string jsonName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + var attributesInPostRequest = new ChickenAttributesInPostRequest + { + Name = "Chicken", + NameOfCurrentFarm = "Cow and Chicken Farm", + Age = 10, + Weight = 30, + TimeAtCurrentFarmInDays = 100, + HasProducedEggs = true + }; + + attributesInPostRequest.SetPropertyToDefaultValue(propertyName); + var requestDocument = new ChickenPostRequestDocument { Data = new ChickenDataInPostRequest { - Attributes = new ChickenAttributesInPostRequest + Attributes = attributesInPostRequest + } + }; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.attributes'."); + } + } + + [Fact] + public async Task Can_exclude_attributes_that_are_required_for_POST_when_performing_PATCH() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + var requestDocument = new ChickenPatchRequestDocument + { + Data = new ChickenDataInPatchRequest + { + Id = "1", + Attributes = new ChickenAttributesInPatchRequest { - Weight = 3 + Name = "Chicken", + Age = 10, + TimeAtCurrentFarmInDays = 100 } } }; // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + await ApiResponse.TranslateAsync(async () => await apiClient.PatchChickenAsync(1, requestDocument)); + } // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Patch); + wrapper.Request.RequestUri.Should().Be(ChickenUrl + "/1"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - exception.Message.Should().Be("Cannot write a null value for property 'nameOfCurrentFarm'. Property requires a value. Path 'data.attributes'."); + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""chickens"", + ""id"": ""1"", + ""attributes"": { + ""name"": ""Chicken"", + ""age"": 10, + ""timeAtCurrentFarmInDays"": 100 + } + } +}"); } [Fact] - public async Task Patching_resource_with_missing_id_fails() + public async Task Cannot_exclude_id_when_performing_PATCH() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); @@ -100,14 +167,164 @@ public async Task Patching_resource_with_missing_id_fails() { Attributes = new ChickenAttributesInPatchRequest { - Age = 1 + Name = "Chicken", + NameOfCurrentFarm = "Cow and Chicken Farm", + Age = 10, + Weight = 30, + TimeAtCurrentFarmInDays = 100, + HasProducedEggs = true } } }; + // Act Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchChickenAsync(1, requestDocument)); // Assert await action.Should().ThrowAsync(); + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'id'. Property requires a value. Path 'data'."); + } + + [Fact] + public async Task Can_clear_nullable_attributes() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + var requestDocument = new ChickenPostRequestDocument + { + Data = new ChickenDataInPostRequest + { + Attributes = new ChickenAttributesInPostRequest + { + Name = null, + TimeAtCurrentFarmInDays = null, + NameOfCurrentFarm = "Cow and Chicken Farm", + Age = 10, + Weight = 30, + HasProducedEggs = true + } + } + }; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + chicken => chicken.Name, chicken => chicken.TimeAtCurrentFarmInDays)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(ChickenUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""chickens"", + ""attributes"": { + ""name"": null, + ""nameOfCurrentFarm"": ""Cow and Chicken Farm"", + ""age"": 10, + ""weight"": 30, + ""timeAtCurrentFarmInDays"": null, + ""hasProducedEggs"": true + } + } +}"); + } + + [Fact] + public async Task Cannot_clear_required_attribute_when_performing_POST() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + var requestDocument = new ChickenPostRequestDocument + { + Data = new ChickenDataInPostRequest + { + Attributes = new ChickenAttributesInPostRequest + { + Name = "Chicken", + NameOfCurrentFarm = null, + Age = 10, + Weight = 30, + TimeAtCurrentFarmInDays = 100, + HasProducedEggs = true + } + } + }; + + Func> action; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + chicken => chicken.NameOfCurrentFarm)) + { + // Act + action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + } + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'nameOfCurrentFarm'. Property requires a value. Path 'data.attributes'."); + } + + [Fact] + public async Task Can_set_default_value_to_ValueType_attributes() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + var requestDocument = new ChickenPostRequestDocument + { + Data = new ChickenDataInPostRequest + { + Attributes = new ChickenAttributesInPostRequest + { + Name = "Chicken", + NameOfCurrentFarm = "Cow and Chicken Farm", + TimeAtCurrentFarmInDays = 100 + } + } + }; + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(ChickenUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""chickens"", + ""attributes"": { + ""name"": ""Chicken"", + ""nameOfCurrentFarm"": ""Cow and Chicken Farm"", + ""age"": 0, + ""weight"": 0, + ""timeAtCurrentFarmInDays"": 100, + ""hasProducedEggs"": false + } + } +}"); } } diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs index 5f35ec7f29..aa3e4b512c 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs @@ -12,10 +12,10 @@ namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled; public sealed class RequiredAttributesTests { - private const string HostPrefix = "http://localhost/"; + private const string CowUrl = "http://localhost/cows"; [Fact] - public async Task Partial_posting_resource_with_explicitly_omitting_required_fields_produces_expected_request() + public async Task Can_exclude_optional_attributes() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); @@ -27,13 +27,16 @@ public async Task Partial_posting_resource_with_explicitly_omitting_required_fie { Attributes = new CowAttributesInPostRequest { - HasProducedMilk = true, - Weight = 1100 + Name = "Cow", + NameOfCurrentFarm = "Cow and Chicken Farm", + Nickname = "Cow", + Weight = 30, + HasProducedMilk = true } } }; - using (apiClient.RegisterAttributesForRequestDocument(requestDocument)) + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) { // Act await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); @@ -43,7 +46,7 @@ public async Task Partial_posting_resource_with_explicitly_omitting_required_fie wrapper.Request.ShouldNotBeNull(); wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(HostPrefix + "cows"); + wrapper.Request.RequestUri.Should().Be(CowUrl); wrapper.Request.Content.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); @@ -52,43 +55,255 @@ public async Task Partial_posting_resource_with_explicitly_omitting_required_fie ""data"": { ""type"": ""cows"", ""attributes"": { - ""weight"": 1100, + ""name"": ""Cow"", + ""nameOfCurrentFarm"": ""Cow and Chicken Farm"", + ""nickname"": ""Cow"", + ""weight"": 30, ""hasProducedMilk"": true } } }"); } - [Fact] - public async Task Partial_posting_resource_without_explicitly_omitting_required_fields_produces_expected_request() + [Theory] + [InlineData(nameof(CowAttributesInResponse.Name), "name")] + [InlineData(nameof(CowAttributesInResponse.NameOfCurrentFarm), "nameOfCurrentFarm")] + [InlineData(nameof(CowAttributesInResponse.Nickname), "nickname")] + [InlineData(nameof(CowAttributesInResponse.Weight), "weight")] + [InlineData(nameof(CowAttributesInResponse.HasProducedMilk), "hasProducedMilk")] + public async Task Cannot_exclude_required_attribute_when_performing_POST(string propertyName, string jsonName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + var attributesInPostRequest = new CowAttributesInPostRequest + { + Name = "Cow", + NameOfCurrentFarm = "Cow and Chicken Farm", + NameOfPreviousFarm = "Animal Farm", + Nickname = "Cow", + Age = 10, + Weight = 30, + TimeAtCurrentFarmInDays = 100, + HasProducedMilk = true + }; + + attributesInPostRequest.SetPropertyToDefaultValue(propertyName); + var requestDocument = new CowPostRequestDocument { Data = new CowDataInPostRequest { - Attributes = new CowAttributesInPostRequest + Attributes = attributesInPostRequest + } + }; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.attributes'."); + } + } + + [Fact] + public async Task Can_exclude_attributes_that_are_required_for_POST_when_performing_PATCH() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + var requestDocument = new CowPatchRequestDocument + { + Data = new CowDataInPatchRequest + { + Id = "1", + Attributes = new CowAttributesInPatchRequest + { + NameOfPreviousFarm = "Animal Farm", + Age = 10, + TimeAtCurrentFarmInDays = 100 + } + } + }; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowAsync(1, requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Patch); + wrapper.Request.RequestUri.Should().Be(CowUrl + "/1"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""cows"", + ""id"": ""1"", + ""attributes"": { + ""nameOfPreviousFarm"": ""Animal Farm"", + ""age"": 10, + ""timeAtCurrentFarmInDays"": 100 + } + } +}"); + } + + [Fact] + public async Task Cannot_exclude_id_when_performing_PATCH() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + var requestDocument = new CowPatchRequestDocument + { + Data = new CowDataInPatchRequest + { + Attributes = new CowAttributesInPatchRequest { - Weight = 1100, - Age = 5, - Name = "Cow 1", - NameOfCurrentFarm = "123", - NameOfPreviousFarm = "123" + Name = "Cow", + NameOfCurrentFarm = "Cow and Chicken Farm", + NameOfPreviousFarm = "Animal Farm", + Nickname = "Cow", + Age = 10, + Weight = 30, + TimeAtCurrentFarmInDays = 100, + HasProducedMilk = true } } }; // Act - Func> - action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowAsync(1, requestDocument)); // Assert + await action.Should().ThrowAsync(); ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); JsonSerializationException exception = assertion.Subject.Single(); - exception.Message.Should().Be("Cannot write a null value for property 'nickname'. Property requires a value. Path 'data.attributes'."); + exception.Message.Should().Be("Cannot write a null value for property 'id'. Property requires a value. Path 'data'."); + } + + [Fact] + public async Task Can_clear_nullable_attributes() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + var requestDocument = new CowPostRequestDocument + { + Data = new CowDataInPostRequest + { + Attributes = new CowAttributesInPostRequest + { + NameOfPreviousFarm = null, + TimeAtCurrentFarmInDays = null, + Name = "Cow", + NameOfCurrentFarm = "Cow and Chicken Farm", + Nickname = "Cow", + Age = 10, + Weight = 30, + HasProducedMilk = true + } + } + }; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + cow => cow.NameOfPreviousFarm, cow => cow.TimeAtCurrentFarmInDays)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(CowUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""cows"", + ""attributes"": { + ""name"": ""Cow"", + ""nameOfCurrentFarm"": ""Cow and Chicken Farm"", + ""nameOfPreviousFarm"": null, + ""nickname"": ""Cow"", + ""age"": 10, + ""weight"": 30, + ""timeAtCurrentFarmInDays"": null, + ""hasProducedMilk"": true + } + } +}"); + } + + [Fact] + public async Task Can_set_default_value_to_ValueType_attributes() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + var requestDocument = new CowPostRequestDocument + { + Data = new CowDataInPostRequest + { + Attributes = new CowAttributesInPostRequest + { + Name = "Cow", + NameOfCurrentFarm = "Cow and Chicken Farm", + NameOfPreviousFarm = "Animal Farm", + Nickname = "Cow", + TimeAtCurrentFarmInDays = 100 + } + } + }; + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(CowUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""cows"", + ""attributes"": { + ""name"": ""Cow"", + ""nameOfCurrentFarm"": ""Cow and Chicken Farm"", + ""nameOfPreviousFarm"": ""Animal Farm"", + ""nickname"": ""Cow"", + ""age"": 0, + ""weight"": 0, + ""timeAtCurrentFarmInDays"": 100, + ""hasProducedMilk"": false + } + } +}"); } } From 87aea3e668d96059aaf482f3ee3d0a3d8c101d06 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Tue, 20 Dec 2022 13:56:51 +0100 Subject: [PATCH 14/26] Fix test: should not omit required field in test request body --- .../LegacyClient/RequestDocumentRegistrationLifetimeTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs b/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs index 73e134fa2d..55e882fedf 100644 --- a/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs +++ b/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs @@ -320,7 +320,10 @@ public async Task Request_document_registration_is_unaffected_by_preceding_dispo Data = new AirplaneDataInPostRequest { Type = AirplaneResourceType.Airplanes, - Attributes = new AirplaneAttributesInPostRequest() + Attributes = new AirplaneAttributesInPostRequest + { + Name = "Jay Jay the Jet Plane" + } } }; From 2ff06fce5dd49125506fad6036944f9bbf6f3bb1 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Tue, 20 Dec 2022 14:06:56 +0100 Subject: [PATCH 15/26] Temp enable CI buid for current branch --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 433d391b16..bbd35ed160 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,6 +14,7 @@ environment: branches: only: + - openapi-required-and-nullable-properties # TODO: remove - master - openapi - develop From 932367e4f98a655abf0af22133e78d994f996cf2 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Tue, 20 Dec 2022 16:23:35 +0100 Subject: [PATCH 16/26] Rename test files: it no longer only concerns required attributes, but more generally request behaviour --- .../{RequiredAttributesTests.cs => RequestTests.cs} | 2 +- .../{RequiredAttributesTests.cs => RequestTests.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/{RequiredAttributesTests.cs => RequestTests.cs} (99%) rename test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/{RequiredAttributesTests.cs => RequestTests.cs} (99%) diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs similarity index 99% rename from test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs rename to test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs index f43f74543a..b624a756ef 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs @@ -10,7 +10,7 @@ namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; -public sealed class RequiredAttributesTests +public sealed class RequestTests { private const string ChickenUrl = "http://localhost/chickens"; diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs similarity index 99% rename from test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs rename to test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs index aa3e4b512c..50fdf9c355 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs @@ -10,7 +10,7 @@ namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled; -public sealed class RequiredAttributesTests +public sealed class RequestTests { private const string CowUrl = "http://localhost/cows"; From 061f4266c610ccb4fa52f3ca8f18862694dab5c3 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 22 Dec 2022 18:14:10 +0100 Subject: [PATCH 17/26] Changes and tests for support of nullable and required for relationships --- .../JsonApiClient.cs | 11 +- .../ResourceFieldObjectSchemaBuilder.cs | 17 +- .../OpenApiClientTests.csproj | 14 + .../NullableReferenceTypesDisabledClient.cs | 14 + .../RelationshipsObject/RequestTests.cs | 558 +++++ .../RelationshipsObject/RequestTestsAlt.cs | 648 ++++++ .../RelationshipsObject/swagger.g.json | 1632 ++++++++++++++ .../NullableReferenceTypesEnabledClient.cs | 14 + .../RelationshipsObject/RequestTests.cs | 487 ++++ .../RelationshipsObject/swagger.g.json | 1954 +++++++++++++++++ test/OpenApiTests/JsonElementExtensions.cs | 19 +- .../ModelStateValidationDisabledTests.cs | 66 + .../ModelStateValidationEnabledTests.cs | 65 + .../RelationshipsObject/NrtDisabledModel.cs | 18 + .../RelationshipsObject/NullabilityTests.cs | 56 + ...NullableReferenceTypesDisabledDbContext.cs | 33 + .../RelationshipsObject/RelationshipModel.cs | 10 + .../NullabilityTests.cs | 5 +- .../ModelStateValidationDisabledTests.cs | 68 + .../ModelStateValidationEnabledTests.cs | 67 + .../RelationshipsObject/NrtEnabledModel.cs | 32 + .../RelationshipsObject/NullabilityTests.cs | 58 + .../NullableReferenceTypesEnabledDbContext.cs | 43 + .../RelationshipsObject/RelationshipModel.cs | 9 + 24 files changed, 5885 insertions(+), 13 deletions(-) create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesDisabledClient.cs create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTests.cs create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTestsAlt.cs create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/swagger.g.json create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesEnabledClient.cs create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RequestTests.cs create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/swagger.g.json create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationDisabledTests.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationEnabledTests.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NrtDisabledModel.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullabilityTests.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullableReferenceTypesDisabledDbContext.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RelationshipModel.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationDisabledTests.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationEnabledTests.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NrtEnabledModel.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullabilityTests.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullableReferenceTypesEnabledDbContext.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RelationshipModel.cs diff --git a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs index 03ea71e8e0..79a25e7deb 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs +++ b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs @@ -185,13 +185,13 @@ private static void AssertObjectMatchesSerializationScope([SysNotNull] Attribute private static void SerializeAttributesObject(AttributesObjectInfo alwaysIncludedAttributes, JsonWriter writer, object value, JsonSerializer serializer) { - AssertRequiredPropertiesAreNotExcluded(value, alwaysIncludedAttributes); + AssertRequiredPropertiesAreNotExcluded(value, alwaysIncludedAttributes, writer); serializer.ContractResolver = new JsonApiAttributeContractResolver(alwaysIncludedAttributes); serializer.Serialize(writer, value); } - private static void AssertRequiredPropertiesAreNotExcluded(object value, AttributesObjectInfo alwaysIncludedAttributes) + private static void AssertRequiredPropertiesAreNotExcluded(object value, AttributesObjectInfo alwaysIncludedAttributes, JsonWriter jsonWriter) { PropertyInfo[] propertyInfos = value.GetType().GetProperties(); @@ -204,11 +204,11 @@ private static void AssertRequiredPropertiesAreNotExcluded(object value, Attribu return; } - AssertRequiredPropertyIsNotIgnored(value, attributesPropertyInfo); + AssertRequiredPropertyIsNotIgnored(value, attributesPropertyInfo, jsonWriter.Path); } } - private static void AssertRequiredPropertyIsNotIgnored(object value, PropertyInfo attribute) + private static void AssertRequiredPropertyIsNotIgnored(object value, PropertyInfo attribute, string path) { JsonPropertyAttribute jsonPropertyForAttribute = attribute.GetCustomAttributes().Single(); @@ -222,7 +222,7 @@ private static void AssertRequiredPropertyIsNotIgnored(object value, PropertyInf if (isPropertyIgnored) { throw new JsonSerializationException( - $"Ignored property '{jsonPropertyForAttribute.PropertyName}' must have a value because it is required. Path 'data.attributes'."); + $"Ignored property '{jsonPropertyForAttribute.PropertyName}' must have a value because it is required. Path '{path}'."); } } @@ -345,3 +345,4 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ } } } + diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs index e4abf42549..14cdfcc953 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs @@ -112,20 +112,28 @@ private void ExposeSchema(OpenApiReference openApiReference, Type typeRepresente private bool IsFieldRequired(ResourceFieldAttribute field) { - if (field is HasManyAttribute || _resourceTypeInfo.ResourceObjectOpenType != typeof(ResourceObjectInPostRequest<>)) + if (_resourceTypeInfo.ResourceObjectOpenType != typeof(ResourceObjectInPostRequest<>)) { return false; } - bool hasRequiredAttribute = field.Property.HasAttribute(); + if (field.Property.HasAttribute()) + { + return true; + } + + if (field is HasManyAttribute) + { + return false; + } NullabilityInfoContext nullabilityContext = new(); NullabilityInfo nullabilityInfo = nullabilityContext.Create(field.Property); return field.Property.PropertyType.IsValueType switch { - true => hasRequiredAttribute, - false => _options.ValidateModelState ? nullabilityInfo.ReadState == NullabilityState.NotNull || hasRequiredAttribute : hasRequiredAttribute + true => false, + false => _options.ValidateModelState && nullabilityInfo.ReadState == NullabilityState.NotNull }; } @@ -230,3 +238,4 @@ private static bool IsDataPropertyNullableInRelationshipSchemaType(Type relation return NullableRelationshipSchemaOpenTypes.Contains(relationshipSchemaOpenType); } } + diff --git a/test/OpenApiClientTests/OpenApiClientTests.csproj b/test/OpenApiClientTests/OpenApiClientTests.csproj index badeeaeae7..143668412b 100644 --- a/test/OpenApiClientTests/OpenApiClientTests.csproj +++ b/test/OpenApiClientTests/OpenApiClientTests.csproj @@ -66,5 +66,19 @@ NSwagCSharp /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false + + OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject.GeneratedCode + NullableReferenceTypesEnabledClientRelationshipsObject + NullableReferenceTypesEnabledClientRelationshipsObject.cs + NSwagCSharp + /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true + + + OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject.GeneratedCode + NullableReferenceTypesDisabledClientRelationshipsObject + NullableReferenceTypesDisabledClientRelationshipsObject.cs + NSwagCSharp + /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false + \ No newline at end of file diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesDisabledClient.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesDisabledClient.cs new file mode 100644 index 0000000000..40ab429277 --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesDisabledClient.cs @@ -0,0 +1,14 @@ +using JsonApiDotNetCore.OpenApi.Client; +using Newtonsoft.Json; + +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject.GeneratedCode; + +internal partial class NullableReferenceTypesDisabledClientRelationshipsObject : JsonApiClient +{ + partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) + { + SetSerializerSettingsForJsonApi(settings); + + settings.Formatting = Formatting.Indented; + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTests.cs new file mode 100644 index 0000000000..1dea915b88 --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTests.cs @@ -0,0 +1,558 @@ +using System.Net; +using System.Reflection; +using FluentAssertions; +using FluentAssertions.Specialized; +using JsonApiDotNetCore.Middleware; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; +using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; + +public sealed class RequestTests +{ + private const string NrtDisabledModelUrl = "http://localhost/nrtDisabledModels"; + + [Fact] + public async Task Can_exclude_optional_relationships() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + var requestDocument = new NrtDisabledModelPostRequestDocument() + { + Data = new NrtDisabledModelDataInPostRequest + { + Relationships = new NrtDisabledModelRelationshipsInPostRequest + { + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(NrtDisabledModelUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""nrtDisabledModels"", + ""relationships"": { + ""requiredHasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }, + ""requiredHasMany"": { + ""data"": [ + { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + ] + } + } + } +}"); + } + + [Theory] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + }; + + relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new NrtDisabledModelPostRequestDocument + { + Data = new NrtDisabledModelDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + Func> action; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + } + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); + } + + [Theory] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + }; + + relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new NrtDisabledModelPostRequestDocument + { + Data = new NrtDisabledModelDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Cannot write a null value for property '{jsonName}'. Property requires a value. Path 'data.relationships'."); + } + + [Fact] + public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + var requestDocument = new NrtDisabledModelPatchRequestDocument() + { + Data = new NrtDisabledModelDataInPatchRequest + { + Id = "1", + Type = NrtDisabledModelResourceType.NrtDisabledModels, + Relationships = new NrtDisabledModelRelationshipsInPatchRequest() + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PatchNrtDisabledModelAsync(1, requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Patch); + wrapper.Request.RequestUri.Should().Be(NrtDisabledModelUrl + "/1"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""nrtDisabledModels"", + ""id"": ""1"", + ""relationships"": { + ""hasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }, + ""hasMany"": { + ""data"": [ + { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + ] + } + } + } +}"); + } + + [Fact] + public async Task Can_clear_nullable_relationship() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + var requestDocument = new NrtDisabledModelPostRequestDocument() + { + Data = new NrtDisabledModelDataInPostRequest + { + Relationships = new NrtDisabledModelRelationshipsInPostRequest + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = null + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(NrtDisabledModelUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""nrtDisabledModels"", + ""relationships"": { + ""hasOne"": { + ""data"": null + }, + ""requiredHasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }, + ""hasMany"": { + ""data"": [ + { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + ] + }, + ""requiredHasMany"": { + ""data"": [ + { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + ] + } + } + } +}"); + } + + [Theory] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.HasMany), "hasMany")] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + public async Task Cannot_clear_non_nullable_relationships_with_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + }; + + PropertyInfo relationshipToClearPropertyInfo = relationshipsInPostDocument.GetType().GetProperties().Single(property => property.Name == propertyName); + object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; + relationshipToClear.SetPropertyToDefaultValue("Data"); + + var requestDocument = new NrtDisabledModelPostRequestDocument + { + Data = new NrtDisabledModelDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + Func> action; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, model => model.RequiredHasOne, model => model.HasMany, model => model.RequiredHasMany)) + { + // Act + action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + } + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); + } + + [Theory] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.HasMany), "hasMany")] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + public async Task Cannot_clear_non_nullable_relationships_without_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + }; + + PropertyInfo relationshipToClearPropertyInfo = relationshipsInPostDocument.GetType().GetProperties().Single(property => property.Name == propertyName); + object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; + relationshipToClear.SetPropertyToDefaultValue("Data"); + + var requestDocument = new NrtDisabledModelPostRequestDocument + { + Data = new NrtDisabledModelDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTestsAlt.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTestsAlt.cs new file mode 100644 index 0000000000..8dd29c3fb7 --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTestsAlt.cs @@ -0,0 +1,648 @@ +using System.Net; +using System.Reflection; +using FluentAssertions; +using FluentAssertions.Specialized; +using JsonApiDotNetCore.Middleware; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; +using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; + +public sealed class RequestTestsAlt +{ + private const string NrtDisabledModelUrl = "http://localhost/nrtDisabledModels"; + + private readonly Dictionary _partials = new() + { + { + nameof(NrtDisabledModelRelationshipsInPostRequest.HasOne), @"""hasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }" + }, + { + nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), @"""requiredHasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }" + }, + + { + nameof(NrtDisabledModelRelationshipsInPostRequest.HasMany), @"""hasMany"": { + ""data"": [ + { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + ] + }" + }, + + { + nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), @"""requiredHasMany"": { + ""data"": [ + { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + ] + }" + } + }; + + [Theory] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.HasOne))] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.HasMany))] + public async Task Can_exclude_optional_relationships(string propertyName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + NrtDisabledModelRelationshipsInPostRequest relationshipsObject = new() + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + }; + + relationshipsObject.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new NrtDisabledModelPostRequestDocument + { + Data = new NrtDisabledModelDataInPostRequest + { + Relationships = relationshipsObject + } + }; + + + await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(NrtDisabledModelUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + string body = GetRelationshipsObjectWithSinglePropertyOmitted(propertyName); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""nrtDisabledModels"", + ""relationships"": "+ body +@" + } +}"); + } + + [Theory] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + }; + + relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new NrtDisabledModelPostRequestDocument + { + Data = new NrtDisabledModelDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + Func> action; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + } + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); + } + + [Theory] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + }; + + relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new NrtDisabledModelPostRequestDocument + { + Data = new NrtDisabledModelDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Cannot write a null value for property '{jsonName}'. Property requires a value. Path 'data.relationships'."); + } + + [Theory] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne))] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany))] + public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH(string propertyName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + var relationshipsObject = new NrtDisabledModelRelationshipsInPatchRequest() + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + }; + + relationshipsObject.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new NrtDisabledModelPatchRequestDocument() + { + Data = new NrtDisabledModelDataInPatchRequest + { + Id = "1", + Type = NrtDisabledModelResourceType.NrtDisabledModels, + Relationships = relationshipsObject + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PatchNrtDisabledModelAsync(1, requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Patch); + wrapper.Request.RequestUri.Should().Be(NrtDisabledModelUrl + "/1"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + string serializedRelationshipsObject = GetRelationshipsObjectWithSinglePropertyOmitted(propertyName); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""nrtDisabledModels"", + ""id"": ""1"", + ""relationships"": "+ serializedRelationshipsObject +@" + } +}"); + } + + [Fact] + public async Task Can_clear_nullable_relationship() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + var requestDocument = new NrtDisabledModelPostRequestDocument() + { + Data = new NrtDisabledModelDataInPostRequest + { + Relationships = new NrtDisabledModelRelationshipsInPostRequest + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = null + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(NrtDisabledModelUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""nrtDisabledModels"", + ""relationships"": { + ""hasOne"": { + ""data"": null + }, + ""requiredHasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }, + ""hasMany"": { + ""data"": [ + { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + ] + }, + ""requiredHasMany"": { + ""data"": [ + { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + ] + } + } + } +}"); + } + + [Theory] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.HasMany), "hasMany")] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + public async Task Cannot_clear_non_nullable_relationships_with_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + }; + + PropertyInfo relationshipToClearPropertyInfo = relationshipsInPostDocument.GetType().GetProperties().Single(property => property.Name == propertyName); + object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; + relationshipToClear.SetPropertyToDefaultValue("Data"); + + var requestDocument = new NrtDisabledModelPostRequestDocument + { + Data = new NrtDisabledModelDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, model => model.RequiredHasOne, model => model.HasMany, model => model.RequiredHasMany)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); + } + } + + [Theory] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.HasMany), "hasMany")] + [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + public async Task Cannot_clear_non_nullable_relationships_without_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + + NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + { + HasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + }; + + PropertyInfo relationshipToClearPropertyInfo = relationshipsInPostDocument.GetType().GetProperties().Single(property => property.Name == propertyName); + object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; + relationshipToClear.SetPropertyToDefaultValue("Data"); + + var requestDocument = new NrtDisabledModelPostRequestDocument + { + Data = new NrtDisabledModelDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); + } + + private string GetRelationshipsObjectWithSinglePropertyOmitted(string excludeProperty) + { + string partial = ""; + + foreach ((string key, string relationshipJsonPartial) in _partials) + { + if (excludeProperty == key) + { + continue; + } + + if (partial.Length > 0) + { + partial += ",\n "; + } + + partial += relationshipJsonPartial; + } + + return @"{ + " + partial + @" + }"; + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/swagger.g.json b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/swagger.g.json new file mode 100644 index 0000000000..2d92b99038 --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/swagger.g.json @@ -0,0 +1,1632 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenApiTests", + "version": "1.0" + }, + "paths": { + "/nrtDisabledModels": { + "get": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "getNrtDisabledModelCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtDisabledModelCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "headNrtDisabledModelCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtDisabledModelCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "postNrtDisabledModel", + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtDisabledModelPostRequestDocument" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtDisabledModelPrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "No Content" + } + } + } + }, + "/nrtDisabledModels/{id}": { + "get": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "getNrtDisabledModel", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtDisabledModelPrimaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "headNrtDisabledModel", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtDisabledModelPrimaryResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "patchNrtDisabledModel", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtDisabledModelPatchRequestDocument" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtDisabledModelPrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "deleteNrtDisabledModel", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/nrtDisabledModels/{id}/hasMany": { + "get": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "getNrtDisabledModelHasMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "headNrtDisabledModelHasMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" + } + } + } + } + } + } + }, + "/nrtDisabledModels/{id}/relationships/hasMany": { + "get": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "getNrtDisabledModelHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "headNrtDisabledModelHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "postNrtDisabledModelHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "patchNrtDisabledModelHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "deleteNrtDisabledModelHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/nrtDisabledModels/{id}/hasOne": { + "get": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "getNrtDisabledModelHasOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableRelationshipModelSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "headNrtDisabledModelHasOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableRelationshipModelSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/nrtDisabledModels/{id}/relationships/hasOne": { + "get": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "getNrtDisabledModelHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableRelationshipModelIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "headNrtDisabledModelHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableRelationshipModelIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "patchNrtDisabledModelHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableToOneRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/nrtDisabledModels/{id}/requiredHasMany": { + "get": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "getNrtDisabledModelRequiredHasMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "headNrtDisabledModelRequiredHasMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" + } + } + } + } + } + } + }, + "/nrtDisabledModels/{id}/relationships/requiredHasMany": { + "get": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "getNrtDisabledModelRequiredHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "headNrtDisabledModelRequiredHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "postNrtDisabledModelRequiredHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "patchNrtDisabledModelRequiredHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "deleteNrtDisabledModelRequiredHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/nrtDisabledModels/{id}/requiredHasOne": { + "get": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "getNrtDisabledModelRequiredHasOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "headNrtDisabledModelRequiredHasOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/nrtDisabledModels/{id}/relationships/requiredHasOne": { + "get": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "getNrtDisabledModelRequiredHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "headNrtDisabledModelRequiredHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "nrtDisabledModels" + ], + "operationId": "patchNrtDisabledModelRequiredHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toOneRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "components": { + "schemas": { + "jsonapiObject": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "profile": { + "type": "array", + "items": { + "type": "string" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "linksInRelationshipObject": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceCollectionDocument": { + "required": [ + "first", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "first": { + "minLength": 1, + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceDocument": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceIdentifierCollectionDocument": { + "required": [ + "first", + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + }, + "first": { + "minLength": 1, + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceIdentifierDocument": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceObject": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "nrtDisabledModelCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/nrtDisabledModelDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "nrtDisabledModelDataInPatchRequest": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/nrtDisabledModelResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "relationships": { + "$ref": "#/components/schemas/nrtDisabledModelRelationshipsInPatchRequest" + } + }, + "additionalProperties": false + }, + "nrtDisabledModelDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/nrtDisabledModelResourceType" + }, + "relationships": { + "$ref": "#/components/schemas/nrtDisabledModelRelationshipsInPostRequest" + } + }, + "additionalProperties": false + }, + "nrtDisabledModelDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/nrtDisabledModelResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "relationships": { + "$ref": "#/components/schemas/nrtDisabledModelRelationshipsInResponse" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "nrtDisabledModelPatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/nrtDisabledModelDataInPatchRequest" + } + }, + "additionalProperties": false + }, + "nrtDisabledModelPostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/nrtDisabledModelDataInPostRequest" + } + }, + "additionalProperties": false + }, + "nrtDisabledModelPrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/nrtDisabledModelDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "nrtDisabledModelRelationshipsInPatchRequest": { + "type": "object", + "properties": { + "hasOne": { + "$ref": "#/components/schemas/nullableToOneRelationshipModelInRequest" + }, + "requiredHasOne": { + "$ref": "#/components/schemas/toOneRelationshipModelInRequest" + }, + "hasMany": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + }, + "requiredHasMany": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + }, + "additionalProperties": false + }, + "nrtDisabledModelRelationshipsInPostRequest": { + "required": [ + "requiredHasMany", + "requiredHasOne" + ], + "type": "object", + "properties": { + "hasOne": { + "$ref": "#/components/schemas/nullableToOneRelationshipModelInRequest" + }, + "requiredHasOne": { + "$ref": "#/components/schemas/toOneRelationshipModelInRequest" + }, + "hasMany": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + }, + "requiredHasMany": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + }, + "additionalProperties": false + }, + "nrtDisabledModelRelationshipsInResponse": { + "type": "object", + "properties": { + "hasOne": { + "$ref": "#/components/schemas/nullableToOneRelationshipModelInResponse" + }, + "requiredHasOne": { + "$ref": "#/components/schemas/toOneRelationshipModelInResponse" + }, + "hasMany": { + "$ref": "#/components/schemas/toManyRelationshipModelInResponse" + }, + "requiredHasMany": { + "$ref": "#/components/schemas/toManyRelationshipModelInResponse" + } + }, + "additionalProperties": false + }, + "nrtDisabledModelResourceType": { + "enum": [ + "nrtDisabledModels" + ], + "type": "string" + }, + "nullValue": { + "not": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "object" + }, + { + "type": "array" + } + ], + "items": { } + }, + "nullable": true + }, + "nullableRelationshipModelIdentifierResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/relationshipModelIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + }, + "additionalProperties": false + }, + "nullableRelationshipModelSecondaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/relationshipModelDataInResponse" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "nullableToOneRelationshipModelInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/relationshipModelIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + } + }, + "additionalProperties": false + }, + "nullableToOneRelationshipModelInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/relationshipModelIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "relationshipModelCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/relationshipModelDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "relationshipModelDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/relationshipModelResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "relationshipModelIdentifier": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/relationshipModelResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "relationshipModelIdentifierCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/relationshipModelIdentifier" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + } + }, + "additionalProperties": false + }, + "relationshipModelIdentifierResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/relationshipModelIdentifier" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + }, + "additionalProperties": false + }, + "relationshipModelResourceType": { + "enum": [ + "relationshipModels" + ], + "type": "string" + }, + "relationshipModelSecondaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/relationshipModelDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "toManyRelationshipModelInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/relationshipModelIdentifier" + } + } + }, + "additionalProperties": false + }, + "toManyRelationshipModelInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/relationshipModelIdentifier" + } + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "toOneRelationshipModelInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/relationshipModelIdentifier" + } + }, + "additionalProperties": false + }, + "toOneRelationshipModelInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/relationshipModelIdentifier" + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + } + } + } +} \ No newline at end of file diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesEnabledClient.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesEnabledClient.cs new file mode 100644 index 0000000000..a4d64afdbe --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesEnabledClient.cs @@ -0,0 +1,14 @@ +using JsonApiDotNetCore.OpenApi.Client; +using Newtonsoft.Json; + +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject.GeneratedCode; + +internal partial class NullableReferenceTypesEnabledClientRelationshipsObject : JsonApiClient +{ + partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) + { + SetSerializerSettingsForJsonApi(settings); + + settings.Formatting = Formatting.Indented; + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RequestTests.cs new file mode 100644 index 0000000000..a1d331c40a --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RequestTests.cs @@ -0,0 +1,487 @@ +using System.Net; +using FluentAssertions; +using FluentAssertions.Specialized; +using JsonApiDotNetCore.Middleware; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; +using OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; + +public sealed class RequestTests +{ + private const string NrtEnabledModelUrl = "http://localhost/nrtEnabledModels"; + + [Fact] + public async Task Can_exclude_optional_relationships() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClientRelationshipsObject(wrapper.HttpClient); + + var requestDocument = new NrtEnabledModelPostRequestDocument() + { + Data = new NrtEnabledModelDataInPostRequest + { + Relationships = new NrtEnabledModelRelationshipsInPostRequest + { + HasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + NullableRequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtEnabledModelAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(NrtEnabledModelUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""nrtEnabledModels"", + ""relationships"": { + ""hasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }, + ""requiredHasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }, + ""nullableRequiredHasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }, + ""requiredHasMany"": { + ""data"": [ + { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + ] + } + } + } +}"); + } + + [Theory] + [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.HasOne), "hasOne")] + [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] + [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.NullableRequiredHasOne), "nullableRequiredHasOne")] + [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClientRelationshipsObject(wrapper.HttpClient); + + NrtEnabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + { + HasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + NullableRequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + }; + + relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new NrtEnabledModelPostRequestDocument + { + Data = new NrtEnabledModelDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + Func> action; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtEnabledModelAsync(requestDocument)); + } + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); + } + + [Theory] + [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.HasOne), "hasOne")] + [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] + [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.NullableRequiredHasOne), "nullableRequiredHasOne")] + [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClientRelationshipsObject(wrapper.HttpClient); + + NrtEnabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + { + HasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + NullableHasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + NullableRequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + }; + + relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new NrtEnabledModelPostRequestDocument + { + Data = new NrtEnabledModelDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtEnabledModelAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Cannot write a null value for property '{jsonName}'. Property requires a value. Path 'data.relationships'."); + } + + [Fact] + public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClientRelationshipsObject(wrapper.HttpClient); + + var requestDocument = new NrtEnabledModelPatchRequestDocument() + { + Data = new NrtEnabledModelDataInPatchRequest + { + Id = "1", + Type = NrtEnabledModelResourceType.NrtEnabledModels, + Relationships = new NrtEnabledModelRelationshipsInPatchRequest() + { + NullableHasOne = new NullableToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PatchNrtEnabledModelAsync(1, requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Patch); + wrapper.Request.RequestUri.Should().Be(NrtEnabledModelUrl + "/1"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""nrtEnabledModels"", + ""id"": ""1"", + ""relationships"": { + ""nullableHasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }, + ""hasMany"": { + ""data"": [ + { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + ] + } + } + } +}"); + } + + [Fact] + public async Task Can_clear_nullable_relationship() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClientRelationshipsObject(wrapper.HttpClient); + + var requestDocument = new NrtEnabledModelPostRequestDocument() + { + Data = new NrtEnabledModelDataInPostRequest + { + Relationships = new NrtEnabledModelRelationshipsInPostRequest + { + NullableHasOne = new NullableToOneRelationshipModelInRequest + { + Data = null + }, + HasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + RequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + NullableRequiredHasOne = new ToOneRelationshipModelInRequest + { + Data = new RelationshipModelIdentifier + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + }, + HasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + }, + RequiredHasMany = new ToManyRelationshipModelInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = RelationshipModelResourceType.RelationshipModels + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtEnabledModelAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(NrtEnabledModelUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""nrtEnabledModels"", + ""relationships"": { + ""hasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }, + ""requiredHasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }, + ""nullableHasOne"": { + ""data"": null + }, + ""nullableRequiredHasOne"": { + ""data"": { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + }, + ""hasMany"": { + ""data"": [ + { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + ] + }, + ""requiredHasMany"": { + ""data"": [ + { + ""type"": ""relationshipModels"", + ""id"": ""1"" + } + ] + } + } + } +}"); + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/swagger.g.json b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/swagger.g.json new file mode 100644 index 0000000000..7522cd4fee --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/swagger.g.json @@ -0,0 +1,1954 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenApiTests", + "version": "1.0" + }, + "paths": { + "/nrtEnabledModels": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtEnabledModelCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtEnabledModelCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "postNrtEnabledModel", + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtEnabledModelPostRequestDocument" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtEnabledModelPrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "No Content" + } + } + } + }, + "/nrtEnabledModels/{id}": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModel", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtEnabledModelPrimaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModel", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtEnabledModelPrimaryResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "patchNrtEnabledModel", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtEnabledModelPatchRequestDocument" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nrtEnabledModelPrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "deleteNrtEnabledModel", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/nrtEnabledModels/{id}/hasMany": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelHasMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelHasMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" + } + } + } + } + } + } + }, + "/nrtEnabledModels/{id}/relationships/hasMany": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "postNrtEnabledModelHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "patchNrtEnabledModelHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "deleteNrtEnabledModelHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/nrtEnabledModels/{id}/hasOne": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelHasOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelHasOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/nrtEnabledModels/{id}/relationships/hasOne": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "patchNrtEnabledModelHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toOneRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/nrtEnabledModels/{id}/nullableHasOne": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelNullableHasOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableRelationshipModelSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelNullableHasOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableRelationshipModelSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/nrtEnabledModels/{id}/relationships/nullableHasOne": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelNullableHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableRelationshipModelIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelNullableHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableRelationshipModelIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "patchNrtEnabledModelNullableHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableToOneRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/nrtEnabledModels/{id}/nullableRequiredHasOne": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelNullableRequiredHasOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelNullableRequiredHasOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/nrtEnabledModels/{id}/relationships/nullableRequiredHasOne": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelNullableRequiredHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelNullableRequiredHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "patchNrtEnabledModelNullableRequiredHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toOneRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/nrtEnabledModels/{id}/requiredHasMany": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelRequiredHasMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelRequiredHasMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" + } + } + } + } + } + } + }, + "/nrtEnabledModels/{id}/relationships/requiredHasMany": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelRequiredHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelRequiredHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "postNrtEnabledModelRequiredHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "patchNrtEnabledModelRequiredHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "deleteNrtEnabledModelRequiredHasManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/nrtEnabledModels/{id}/requiredHasOne": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelRequiredHasOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelRequiredHasOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/nrtEnabledModels/{id}/relationships/requiredHasOne": { + "get": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "getNrtEnabledModelRequiredHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "headNrtEnabledModelRequiredHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "nrtEnabledModels" + ], + "operationId": "patchNrtEnabledModelRequiredHasOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toOneRelationshipModelInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "components": { + "schemas": { + "jsonapiObject": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "profile": { + "type": "array", + "items": { + "type": "string" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "linksInRelationshipObject": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceCollectionDocument": { + "required": [ + "first", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "first": { + "minLength": 1, + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceDocument": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceIdentifierCollectionDocument": { + "required": [ + "first", + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + }, + "first": { + "minLength": 1, + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceIdentifierDocument": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceObject": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "nrtEnabledModelCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/nrtEnabledModelDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "nrtEnabledModelDataInPatchRequest": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/nrtEnabledModelResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "relationships": { + "$ref": "#/components/schemas/nrtEnabledModelRelationshipsInPatchRequest" + } + }, + "additionalProperties": false + }, + "nrtEnabledModelDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/nrtEnabledModelResourceType" + }, + "relationships": { + "$ref": "#/components/schemas/nrtEnabledModelRelationshipsInPostRequest" + } + }, + "additionalProperties": false + }, + "nrtEnabledModelDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/nrtEnabledModelResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "relationships": { + "$ref": "#/components/schemas/nrtEnabledModelRelationshipsInResponse" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "nrtEnabledModelPatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/nrtEnabledModelDataInPatchRequest" + } + }, + "additionalProperties": false + }, + "nrtEnabledModelPostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/nrtEnabledModelDataInPostRequest" + } + }, + "additionalProperties": false + }, + "nrtEnabledModelPrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/nrtEnabledModelDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "nrtEnabledModelRelationshipsInPatchRequest": { + "type": "object", + "properties": { + "hasOne": { + "$ref": "#/components/schemas/toOneRelationshipModelInRequest" + }, + "requiredHasOne": { + "$ref": "#/components/schemas/toOneRelationshipModelInRequest" + }, + "nullableHasOne": { + "$ref": "#/components/schemas/nullableToOneRelationshipModelInRequest" + }, + "nullableRequiredHasOne": { + "$ref": "#/components/schemas/toOneRelationshipModelInRequest" + }, + "hasMany": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + }, + "requiredHasMany": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + }, + "additionalProperties": false + }, + "nrtEnabledModelRelationshipsInPostRequest": { + "required": [ + "hasOne", + "nullableRequiredHasOne", + "requiredHasMany", + "requiredHasOne" + ], + "type": "object", + "properties": { + "hasOne": { + "$ref": "#/components/schemas/toOneRelationshipModelInRequest" + }, + "requiredHasOne": { + "$ref": "#/components/schemas/toOneRelationshipModelInRequest" + }, + "nullableHasOne": { + "$ref": "#/components/schemas/nullableToOneRelationshipModelInRequest" + }, + "nullableRequiredHasOne": { + "$ref": "#/components/schemas/toOneRelationshipModelInRequest" + }, + "hasMany": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + }, + "requiredHasMany": { + "$ref": "#/components/schemas/toManyRelationshipModelInRequest" + } + }, + "additionalProperties": false + }, + "nrtEnabledModelRelationshipsInResponse": { + "type": "object", + "properties": { + "hasOne": { + "$ref": "#/components/schemas/toOneRelationshipModelInResponse" + }, + "requiredHasOne": { + "$ref": "#/components/schemas/toOneRelationshipModelInResponse" + }, + "nullableHasOne": { + "$ref": "#/components/schemas/nullableToOneRelationshipModelInResponse" + }, + "nullableRequiredHasOne": { + "$ref": "#/components/schemas/toOneRelationshipModelInResponse" + }, + "hasMany": { + "$ref": "#/components/schemas/toManyRelationshipModelInResponse" + }, + "requiredHasMany": { + "$ref": "#/components/schemas/toManyRelationshipModelInResponse" + } + }, + "additionalProperties": false + }, + "nrtEnabledModelResourceType": { + "enum": [ + "nrtEnabledModels" + ], + "type": "string" + }, + "nullValue": { + "not": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "object" + }, + { + "type": "array" + } + ], + "items": { } + }, + "nullable": true + }, + "nullableRelationshipModelIdentifierResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/relationshipModelIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + }, + "additionalProperties": false + }, + "nullableRelationshipModelSecondaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/relationshipModelDataInResponse" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "nullableToOneRelationshipModelInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/relationshipModelIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + } + }, + "additionalProperties": false + }, + "nullableToOneRelationshipModelInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/relationshipModelIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "relationshipModelCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/relationshipModelDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "relationshipModelDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/relationshipModelResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "relationshipModelIdentifier": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/relationshipModelResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "relationshipModelIdentifierCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/relationshipModelIdentifier" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + } + }, + "additionalProperties": false + }, + "relationshipModelIdentifierResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/relationshipModelIdentifier" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + }, + "additionalProperties": false + }, + "relationshipModelResourceType": { + "enum": [ + "relationshipModels" + ], + "type": "string" + }, + "relationshipModelSecondaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/relationshipModelDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "toManyRelationshipModelInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/relationshipModelIdentifier" + } + } + }, + "additionalProperties": false + }, + "toManyRelationshipModelInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/relationshipModelIdentifier" + } + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "toOneRelationshipModelInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/relationshipModelIdentifier" + } + }, + "additionalProperties": false + }, + "toOneRelationshipModelInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/relationshipModelIdentifier" + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + } + } + } +} \ No newline at end of file diff --git a/test/OpenApiTests/JsonElementExtensions.cs b/test/OpenApiTests/JsonElementExtensions.cs index 6f1ffe1432..93d903baac 100644 --- a/test/OpenApiTests/JsonElementExtensions.cs +++ b/test/OpenApiTests/JsonElementExtensions.cs @@ -2,6 +2,7 @@ using BlushingPenguin.JsonPath; using FluentAssertions; using FluentAssertions.Execution; +using JetBrains.Annotations; using TestBuildingBlocks; namespace OpenApiTests; @@ -33,6 +34,14 @@ public static void ShouldBeString(this JsonElement source, string value) } public static SchemaReferenceIdContainer ShouldBeSchemaReferenceId(this JsonElement source, string value) + { + string schemaReferenceId = GetSchemaReferenceId(source); + schemaReferenceId.Should().Be(value); + + return new SchemaReferenceIdContainer(value); + } + + public static string GetSchemaReferenceId(this JsonElement source) { source.ValueKind.Should().Be(JsonValueKind.String); @@ -40,9 +49,14 @@ public static SchemaReferenceIdContainer ShouldBeSchemaReferenceId(this JsonElem jsonElementValue.ShouldNotBeNull(); string schemaReferenceId = jsonElementValue.Split('/').Last(); - schemaReferenceId.Should().Be(value); + return schemaReferenceId; + } - return new SchemaReferenceIdContainer(value); + public static void WithSchemaReferenceId(this JsonElement subject, [InstantHandle] Action continuation) + { + string schemaReferenceId = GetSchemaReferenceId(subject); + + continuation(schemaReferenceId); } public sealed class SchemaReferenceIdContainer @@ -84,3 +98,4 @@ public void ContainProperty(string propertyName) } } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationDisabledTests.cs new file mode 100644 index 0000000000..f34db02848 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationDisabledTests.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; + +public sealed class ModelStateValidationDisabledTests + : IClassFixture, NullableReferenceTypesDisabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> + _testContext; + + public ModelStateValidationDisabledTests( + OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Theory] + [InlineData("requiredHasOne")] + [InlineData("requiredHasMany")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.nrtDisabledModelRelationshipsInPostRequest.required").With(propertySet => + { + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredAttributes.Should().Contain(propertyName); + }); + } + + [Theory] + [InlineData("hasOne")] + [InlineData("hasMany")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.nrtDisabledModelRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain(propertyName); + }); + } + + [Fact] + public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.nrtDisabledModelRelationshipsInPatchRequest.required"); + } +} + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationEnabledTests.cs new file mode 100644 index 0000000000..d24f7e63c8 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationEnabledTests.cs @@ -0,0 +1,65 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; + +public sealed class ModelStateValidationEnabledTests + : IClassFixture, NullableReferenceTypesDisabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; + + public ModelStateValidationEnabledTests( + OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Theory] + [InlineData("requiredHasOne")] + [InlineData("requiredHasMany")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.nrtDisabledModelRelationshipsInPostRequest.required").With(propertySet => + { + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredAttributes.Should().Contain(propertyName); + }); + } + + [Theory] + [InlineData("hasOne")] + [InlineData("hasMany")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.nrtDisabledModelRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain(propertyName); + }); + } + + [Fact] + public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.nrtDisabledModelRelationshipsInPatchRequest.required"); + } +} + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NrtDisabledModel.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NrtDisabledModel.cs new file mode 100644 index 0000000000..b23e1e10ff --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NrtDisabledModel.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +#nullable disable + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] +public sealed class NrtDisabledModel : Identifiable +{ + [HasOne] public RelationshipModel HasOne { get; set; } + [Required] [HasOne] public RelationshipModel RequiredHasOne { get; set; } + [HasMany] public ICollection HasMany { get; set; } = new HashSet(); + [Required] [HasMany] public ICollection RequiredHasMany { get; set; } = new HashSet(); +} \ No newline at end of file diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullabilityTests.cs new file mode 100644 index 0000000000..24f31d855a --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullabilityTests.cs @@ -0,0 +1,56 @@ +using System.Text.Json; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; + +public sealed class NullabilityTests + : IClassFixture, NullableReferenceTypesDisabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; + + public NullabilityTests(OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject"; + } + + [Theory] + [InlineData("hasOne")] + public async Task Property_in_schema_for_relationship_of_resource_should_be_nullable(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.nrtDisabledModelRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + }); + }); + } + + [Theory] + [InlineData("hasMany")] + [InlineData("requiredHasOne")] + [InlineData("requiredHasMany")] + public async Task Property_in_schema_for_relationship_of_resource_should_not_be_nullable(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.nrtDisabledModelRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } +} + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullableReferenceTypesDisabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullableReferenceTypesDisabledDbContext.cs new file mode 100644 index 0000000000..e0a777d8a5 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullableReferenceTypesDisabledDbContext.cs @@ -0,0 +1,33 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; + +// @formatter:wrap_chained_method_calls chop_always + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class NullableReferenceTypesDisabledDbContext : TestableDbContext +{ + public DbSet NrtDisabledModel => Set(); + public DbSet RelationshipModel => Set(); + + public NullableReferenceTypesDisabledDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(resource => resource.HasOne); + builder.Entity() + .HasOne(resource => resource.RequiredHasOne); + builder.Entity() + .HasMany(resource => resource.HasMany); + builder.Entity() + .HasMany(resource => resource.RequiredHasMany); + + base.OnModelCreating(builder); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RelationshipModel.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RelationshipModel.cs new file mode 100644 index 0000000000..5bb9e96563 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RelationshipModel.cs @@ -0,0 +1,10 @@ +#nullable disable +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; + +[Resource] +public class RelationshipModel : Identifiable +{ +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs index 3b8d0597d7..be3c930744 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs @@ -21,7 +21,7 @@ public NullabilityTests(OpenApiTestContext, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> + _testContext; + + public ModelStateValidationDisabledTests( + OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Theory] + [InlineData("requiredHasOne")] + [InlineData("requiredHasMany")] + [InlineData("nullableRequiredHasOne")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.nrtEnabledModelRelationshipsInPostRequest.required").With(propertySet => + { + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredAttributes.Should().Contain(propertyName); + }); + } + + [Theory] + [InlineData("hasOne")] + [InlineData("hasMany")] + [InlineData("nullableHasOne")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.nrtEnabledModelRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain(propertyName); + }); + } + + [Fact] + public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.nrtEnabledModelRelationshipsInPatchRequest.required"); + } +} + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationEnabledTests.cs new file mode 100644 index 0000000000..174bac59aa --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationEnabledTests.cs @@ -0,0 +1,67 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; + +public sealed class ModelStateValidationEnabledTests + : IClassFixture, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; + + public ModelStateValidationEnabledTests( + OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Theory] + [InlineData("hasOne")] + [InlineData("requiredHasOne")] + [InlineData("requiredHasMany")] + [InlineData("nullableRequiredHasOne")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.nrtEnabledModelRelationshipsInPostRequest.required").With(propertySet => + { + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredAttributes.Should().Contain(propertyName); + }); + } + + [Theory] + [InlineData("hasMany")] + [InlineData("nullableHasOne")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.nrtEnabledModelRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain(propertyName); + }); + } + + [Fact] + public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.nrtEnabledModelRelationshipsInPatchRequest.required"); + } +} + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NrtEnabledModel.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NrtEnabledModel.cs new file mode 100644 index 0000000000..109d4510c2 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NrtEnabledModel.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] +public sealed class NrtEnabledModel : Identifiable +{ + [HasOne] + public RelationshipModel HasOne { get; set; } = null!; + + [Required] + [HasOne] + public RelationshipModel RequiredHasOne { get; set; } = null!; + + [HasOne] + public RelationshipModel? NullableHasOne { get; set; } + + [Required] + [HasOne] + public RelationshipModel? NullableRequiredHasOne { get; set; } + + [HasMany] + public ICollection HasMany { get; set; } = new HashSet(); + + [Required] + [HasMany] + public ICollection RequiredHasMany { get; set; } = new HashSet(); +} \ No newline at end of file diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullabilityTests.cs new file mode 100644 index 0000000000..ed710bbf41 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullabilityTests.cs @@ -0,0 +1,58 @@ +using System.Text.Json; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; + +public sealed class NullabilityTests + : IClassFixture, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; + + public NullabilityTests(OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject"; + } + + [Theory] + [InlineData("nullableHasOne")] + public async Task Property_in_schema_for_relationship_of_resource_should_be_nullable(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.nrtEnabledModelRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + }); + }); + } + + [Theory] + [InlineData("hasOne")] + [InlineData("requiredHasOne")] + [InlineData("hasMany")] + [InlineData("requiredHasMany")] + [InlineData("nullableRequiredHasOne")] + public async Task Property_in_schema_for_relationship_of_resource_should_not_be_nullable(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.nrtEnabledModelRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } +} + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullableReferenceTypesEnabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullableReferenceTypesEnabledDbContext.cs new file mode 100644 index 0000000000..0979fe4e9a --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullableReferenceTypesEnabledDbContext.cs @@ -0,0 +1,43 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; + +// @formatter:wrap_chained_method_calls chop_always + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class NullableReferenceTypesEnabledDbContext : TestableDbContext +{ + public DbSet NrtEnabledModel => Set(); + public DbSet RelationshipModel => Set(); + + public NullableReferenceTypesEnabledDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(resource => resource.HasOne); + + builder.Entity() + .HasOne(resource => resource.RequiredHasOne); + + builder.Entity() + .HasMany(resource => resource.HasMany); + + builder.Entity() + .HasMany(resource => resource.RequiredHasMany); + + builder.Entity() + .HasOne(resource => resource.NullableHasOne); + + builder.Entity() + .HasOne(resource => resource.NullableRequiredHasOne); + + base.OnModelCreating(builder); + } +} + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RelationshipModel.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RelationshipModel.cs new file mode 100644 index 0000000000..119cb1e9e3 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RelationshipModel.cs @@ -0,0 +1,9 @@ +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; + +[Resource] +public class RelationshipModel : Identifiable +{ +} From 90970a844d75eb368718f80a182d6ae0f1d7c964 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Sat, 24 Dec 2022 19:00:28 +0100 Subject: [PATCH 18/26] - Rename placeholder model names and properties to examples consisent with existing test suite - Use existing DbContext instead of temporary one --- .../OpenApiClientTests.csproj | 14 - .../NullableReferenceTypesDisabledClient.cs | 1 + .../NullableReferenceTypesDisabledClient.cs | 14 - .../RelationshipsObject/RequestTests.cs | 315 +-- .../RelationshipsObject/RequestTestsAlt.cs | 351 ++- .../RelationshipsObject/swagger.g.json | 1632 -------------- .../RequestTests.cs | 3 +- .../swagger.g.json | 1542 ++++++++++++- .../NullableReferenceTypesEnabledClient.cs | 14 - .../RelationshipsObject/RequestTests.cs | 288 +-- .../RelationshipsObject/swagger.g.json | 1954 ----------------- .../swagger.g.json | 1856 +++++++++++++++- .../NullabilityTests.cs | 2 + ...NullableReferenceTypesDisabledDbContext.cs | 21 + .../RelationshipsObject/HenHouse.cs | 27 + .../ModelStateValidationDisabledTests.cs | 17 +- .../ModelStateValidationEnabledTests.cs | 17 +- .../RelationshipsObject/NrtDisabledModel.cs | 18 - .../RelationshipsObject/NullabilityTests.cs | 16 +- ...NullableReferenceTypesDisabledDbContext.cs | 33 - .../RelationshipsObject/RelationshipModel.cs | 10 - .../NullableReferenceTypesEnabled/Cow.cs | 2 + .../NullabilityTests.cs | 2 + .../NullableReferenceTypesEnabledDbContext.cs | 28 + .../RelationshipsObject/CowStable.cs | 32 + .../ModelStateValidationDisabledTests.cs | 21 +- .../ModelStateValidationEnabledTests.cs | 21 +- .../RelationshipsObject/NrtEnabledModel.cs | 32 - .../RelationshipsObject/NullabilityTests.cs | 20 +- .../NullableReferenceTypesEnabledDbContext.cs | 43 - .../RelationshipsObject/RelationshipModel.cs | 9 - 31 files changed, 3990 insertions(+), 4365 deletions(-) delete mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesDisabledClient.cs delete mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/swagger.g.json delete mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesEnabledClient.cs delete mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/swagger.g.json create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/HenHouse.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NrtDisabledModel.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullableReferenceTypesDisabledDbContext.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RelationshipModel.cs create mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/CowStable.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NrtEnabledModel.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullableReferenceTypesEnabledDbContext.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RelationshipModel.cs diff --git a/test/OpenApiClientTests/OpenApiClientTests.csproj b/test/OpenApiClientTests/OpenApiClientTests.csproj index 143668412b..badeeaeae7 100644 --- a/test/OpenApiClientTests/OpenApiClientTests.csproj +++ b/test/OpenApiClientTests/OpenApiClientTests.csproj @@ -66,19 +66,5 @@ NSwagCSharp /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false - - OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject.GeneratedCode - NullableReferenceTypesEnabledClientRelationshipsObject - NullableReferenceTypesEnabledClientRelationshipsObject.cs - NSwagCSharp - /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true - - - OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject.GeneratedCode - NullableReferenceTypesDisabledClientRelationshipsObject - NullableReferenceTypesDisabledClientRelationshipsObject.cs - NSwagCSharp - /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false - \ No newline at end of file diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs index c1a4eebab5..6670a0566f 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs @@ -12,3 +12,4 @@ partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) settings.Formatting = Formatting.Indented; } } + diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesDisabledClient.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesDisabledClient.cs deleted file mode 100644 index 40ab429277..0000000000 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesDisabledClient.cs +++ /dev/null @@ -1,14 +0,0 @@ -using JsonApiDotNetCore.OpenApi.Client; -using Newtonsoft.Json; - -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject.GeneratedCode; - -internal partial class NullableReferenceTypesDisabledClientRelationshipsObject : JsonApiClient -{ - partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) - { - SetSerializerSettingsForJsonApi(settings); - - settings.Formatting = Formatting.Indented; - } -} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTests.cs index 1dea915b88..9ccda82859 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTests.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTests.cs @@ -5,45 +5,45 @@ using JsonApiDotNetCore.Middleware; using Microsoft.Net.Http.Headers; using Newtonsoft.Json; -using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject.GeneratedCode; +using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; using TestBuildingBlocks; using Xunit; -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; public sealed class RequestTests { - private const string NrtDisabledModelUrl = "http://localhost/nrtDisabledModels"; + private const string HenHouseUrl = "http://localhost/henHouses"; [Fact] public async Task Can_exclude_optional_relationships() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - var requestDocument = new NrtDisabledModelPostRequestDocument() + var requestDocument = new HenHousePostRequestDocument { - Data = new NrtDisabledModelDataInPostRequest + Data = new HenHouseDataInPostRequest { - Relationships = new NrtDisabledModelRelationshipsInPostRequest + Relationships = new HenHouseRelationshipsInPostRequest { - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -51,31 +51,31 @@ public async Task Can_exclude_optional_relationships() } }; - await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); // Assert wrapper.Request.ShouldNotBeNull(); wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(NrtDisabledModelUrl); + wrapper.Request.RequestUri.Should().Be(HenHouseUrl); wrapper.Request.Content.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); wrapper.RequestBody.Should().BeJson(@"{ ""data"": { - ""type"": ""nrtDisabledModels"", + ""type"": ""henHouses"", ""relationships"": { - ""requiredHasOne"": { + ""firstChicken"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } }, - ""requiredHasMany"": { + ""chickensReadyForLaying"": { ""data"": [ { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } ] @@ -86,51 +86,51 @@ public async Task Can_exclude_optional_relationships() } [Theory] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -138,75 +138,74 @@ public async Task Cannot_exclude_required_relationship_when_performing_POST_with relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - var requestDocument = new NrtDisabledModelPostRequestDocument + var requestDocument = new HenHousePostRequestDocument { - Data = new NrtDisabledModelDataInPostRequest + Data = new HenHouseDataInPostRequest { Relationships = relationshipsInPostDocument } }; - Func> action; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) { // Act - action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); - } - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); - exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); + exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); + } } [Theory] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -214,17 +213,17 @@ public async Task Cannot_exclude_required_relationship_when_performing_POST_with relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - var requestDocument = new NrtDisabledModelPostRequestDocument + var requestDocument = new HenHousePostRequestDocument { - Data = new NrtDisabledModelDataInPostRequest + Data = new HenHouseDataInPostRequest { Relationships = relationshipsInPostDocument } }; // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); // Assert ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); @@ -238,32 +237,32 @@ public async Task Can_exclude_relationships_that_are_required_for_POST_when_perf { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - var requestDocument = new NrtDisabledModelPatchRequestDocument() + var requestDocument = new HenHousePatchRequestDocument { - Data = new NrtDisabledModelDataInPatchRequest + Data = new HenHouseDataInPatchRequest { Id = "1", - Type = NrtDisabledModelResourceType.NrtDisabledModels, - Relationships = new NrtDisabledModelRelationshipsInPatchRequest() + Type = HenHouseResourceType.HenHouses, + Relationships = new HenHouseRelationshipsInPatchRequest { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -271,32 +270,32 @@ public async Task Can_exclude_relationships_that_are_required_for_POST_when_perf } }; - await ApiResponse.TranslateAsync(async () => await apiClient.PatchNrtDisabledModelAsync(1, requestDocument)); + await ApiResponse.TranslateAsync(async () => await apiClient.PatchHenHouseAsync(1, requestDocument)); // Assert wrapper.Request.ShouldNotBeNull(); wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be(NrtDisabledModelUrl + "/1"); + wrapper.Request.RequestUri.Should().Be(HenHouseUrl + "/1"); wrapper.Request.Content.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); wrapper.RequestBody.Should().BeJson(@"{ ""data"": { - ""type"": ""nrtDisabledModels"", + ""type"": ""henHouses"", ""id"": ""1"", ""relationships"": { - ""hasOne"": { + ""oldestChicken"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } }, - ""hasMany"": { + ""allChickens"": { ""data"": [ { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } ] @@ -311,45 +310,45 @@ public async Task Can_clear_nullable_relationship() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - var requestDocument = new NrtDisabledModelPostRequestDocument() + var requestDocument = new HenHousePostRequestDocument { - Data = new NrtDisabledModelDataInPostRequest + Data = new HenHouseDataInPostRequest { - Relationships = new NrtDisabledModelRelationshipsInPostRequest + Relationships = new HenHouseRelationshipsInPostRequest { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { Data = null }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -357,42 +356,42 @@ public async Task Can_clear_nullable_relationship() } }; - await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); // Assert wrapper.Request.ShouldNotBeNull(); wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(NrtDisabledModelUrl); + wrapper.Request.RequestUri.Should().Be(HenHouseUrl); wrapper.Request.Content.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); wrapper.RequestBody.Should().BeJson(@"{ ""data"": { - ""type"": ""nrtDisabledModels"", + ""type"": ""henHouses"", ""relationships"": { - ""hasOne"": { + ""oldestChicken"": { ""data"": null }, - ""requiredHasOne"": { + ""firstChicken"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } }, - ""hasMany"": { + ""allChickens"": { ""data"": [ { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } ] }, - ""requiredHasMany"": { + ""chickensReadyForLaying"": { ""data"": [ { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } ] @@ -403,52 +402,52 @@ public async Task Can_clear_nullable_relationship() } [Theory] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.HasMany), "hasMany")] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens), "allChickens")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] public async Task Cannot_clear_non_nullable_relationships_with_document_registration(string propertyName, string jsonName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -458,76 +457,77 @@ public async Task Cannot_clear_non_nullable_relationships_with_document_registra object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; relationshipToClear.SetPropertyToDefaultValue("Data"); - var requestDocument = new NrtDisabledModelPostRequestDocument + var requestDocument = new HenHousePostRequestDocument { - Data = new NrtDisabledModelDataInPostRequest + Data = new HenHouseDataInPostRequest { Relationships = relationshipsInPostDocument } }; - Func> action; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, model => model.RequiredHasOne, model => model.HasMany, model => model.RequiredHasMany)) + Func> action; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + model => model.FirstChicken, model => model.AllChickens, model => model.ChickensReadyForLaying)) { // Act - action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); } - + // Assert ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); JsonSerializationException exception = assertion.Subject.Single(); exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); } - + [Theory] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.HasMany), "hasMany")] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens), "allChickens")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] public async Task Cannot_clear_non_nullable_relationships_without_document_registration(string propertyName, string jsonName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -537,17 +537,17 @@ public async Task Cannot_clear_non_nullable_relationships_without_document_regis object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; relationshipToClear.SetPropertyToDefaultValue("Data"); - var requestDocument = new NrtDisabledModelPostRequestDocument + var requestDocument = new HenHousePostRequestDocument { - Data = new NrtDisabledModelDataInPostRequest + Data = new HenHouseDataInPostRequest { Relationships = relationshipsInPostDocument } }; - + // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); // Assert ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); @@ -556,3 +556,4 @@ public async Task Cannot_clear_non_nullable_relationships_without_document_regis exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); } } + diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTestsAlt.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTestsAlt.cs index 8dd29c3fb7..870bb548fd 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTestsAlt.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTestsAlt.cs @@ -5,40 +5,39 @@ using JsonApiDotNetCore.Middleware; using Microsoft.Net.Http.Headers; using Newtonsoft.Json; -using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject.GeneratedCode; +using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; using TestBuildingBlocks; using Xunit; -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; public sealed class RequestTestsAlt { - private const string NrtDisabledModelUrl = "http://localhost/nrtDisabledModels"; + private const string HenHouseUrl = "http://localhost/henHouses"; private readonly Dictionary _partials = new() { { - nameof(NrtDisabledModelRelationshipsInPostRequest.HasOne), @"""hasOne"": { + nameof(HenHouseRelationshipsInPostRequest.OldestChicken), @"""oldestChicken"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } }" }, { - nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), @"""requiredHasOne"": { + nameof(HenHouseRelationshipsInPostRequest.FirstChicken), @"""firstChicken"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } }" }, - { - nameof(NrtDisabledModelRelationshipsInPostRequest.HasMany), @"""hasMany"": { + nameof(HenHouseRelationshipsInPostRequest.AllChickens), @"""allChickens"": { ""data"": [ { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } ] @@ -46,10 +45,10 @@ public sealed class RequestTestsAlt }, { - nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), @"""requiredHasMany"": { + nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), @"""chickensReadyForLaying"": { ""data"": [ { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } ] @@ -58,51 +57,51 @@ public sealed class RequestTestsAlt }; [Theory] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.HasOne))] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.HasMany))] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.OldestChicken))] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens))] public async Task Can_exclude_optional_relationships(string propertyName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - NrtDisabledModelRelationshipsInPostRequest relationshipsObject = new() + HenHouseRelationshipsInPostRequest relationshipsObject = new() { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -110,22 +109,21 @@ public async Task Can_exclude_optional_relationships(string propertyName) relationshipsObject.SetPropertyToDefaultValue(propertyName); - var requestDocument = new NrtDisabledModelPostRequestDocument + var requestDocument = new HenHousePostRequestDocument { - Data = new NrtDisabledModelDataInPostRequest + Data = new HenHouseDataInPostRequest { Relationships = relationshipsObject } }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); // Assert wrapper.Request.ShouldNotBeNull(); wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(NrtDisabledModelUrl); + wrapper.Request.RequestUri.Should().Be(HenHouseUrl); wrapper.Request.Content.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); @@ -134,58 +132,58 @@ public async Task Can_exclude_optional_relationships(string propertyName) wrapper.RequestBody.Should().BeJson(@"{ ""data"": { - ""type"": ""nrtDisabledModels"", - ""relationships"": "+ body +@" + ""type"": ""henHouses"", + ""relationships"": " + body + @" } }"); } [Theory] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -193,75 +191,74 @@ public async Task Cannot_exclude_required_relationship_when_performing_POST_with relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - var requestDocument = new NrtDisabledModelPostRequestDocument + var requestDocument = new HenHousePostRequestDocument { - Data = new NrtDisabledModelDataInPostRequest + Data = new HenHouseDataInPostRequest { Relationships = relationshipsInPostDocument } }; - Func> action; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) { // Act - action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); - } + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); - exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); + exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); + } } [Theory] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -269,17 +266,17 @@ public async Task Cannot_exclude_required_relationship_when_performing_POST_with relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - var requestDocument = new NrtDisabledModelPostRequestDocument + var requestDocument = new HenHousePostRequestDocument { - Data = new NrtDisabledModelDataInPostRequest + Data = new HenHouseDataInPostRequest { Relationships = relationshipsInPostDocument } }; // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); // Assert ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); @@ -289,51 +286,51 @@ public async Task Cannot_exclude_required_relationship_when_performing_POST_with } [Theory] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne))] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany))] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken))] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying))] public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH(string propertyName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - var relationshipsObject = new NrtDisabledModelRelationshipsInPatchRequest() + var relationshipsObject = new HenHouseRelationshipsInPatchRequest { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -341,23 +338,23 @@ public async Task Can_exclude_relationships_that_are_required_for_POST_when_perf relationshipsObject.SetPropertyToDefaultValue(propertyName); - var requestDocument = new NrtDisabledModelPatchRequestDocument() + var requestDocument = new HenHousePatchRequestDocument { - Data = new NrtDisabledModelDataInPatchRequest + Data = new HenHouseDataInPatchRequest { Id = "1", - Type = NrtDisabledModelResourceType.NrtDisabledModels, + Type = HenHouseResourceType.HenHouses, Relationships = relationshipsObject } }; - await ApiResponse.TranslateAsync(async () => await apiClient.PatchNrtDisabledModelAsync(1, requestDocument)); + await ApiResponse.TranslateAsync(async () => await apiClient.PatchHenHouseAsync(1, requestDocument)); // Assert wrapper.Request.ShouldNotBeNull(); wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be(NrtDisabledModelUrl + "/1"); + wrapper.Request.RequestUri.Should().Be(HenHouseUrl + "/1"); wrapper.Request.Content.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); @@ -366,9 +363,9 @@ public async Task Can_exclude_relationships_that_are_required_for_POST_when_perf wrapper.RequestBody.Should().BeJson(@"{ ""data"": { - ""type"": ""nrtDisabledModels"", + ""type"": ""henHouses"", ""id"": ""1"", - ""relationships"": "+ serializedRelationshipsObject +@" + ""relationships"": " + serializedRelationshipsObject + @" } }"); } @@ -378,45 +375,45 @@ public async Task Can_clear_nullable_relationship() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - var requestDocument = new NrtDisabledModelPostRequestDocument() + var requestDocument = new HenHousePostRequestDocument { - Data = new NrtDisabledModelDataInPostRequest + Data = new HenHouseDataInPostRequest { - Relationships = new NrtDisabledModelRelationshipsInPostRequest + Relationships = new HenHouseRelationshipsInPostRequest { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { Data = null }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -424,42 +421,42 @@ public async Task Can_clear_nullable_relationship() } }; - await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); // Assert wrapper.Request.ShouldNotBeNull(); wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(NrtDisabledModelUrl); + wrapper.Request.RequestUri.Should().Be(HenHouseUrl); wrapper.Request.Content.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); wrapper.RequestBody.Should().BeJson(@"{ ""data"": { - ""type"": ""nrtDisabledModels"", + ""type"": ""henHouses"", ""relationships"": { - ""hasOne"": { + ""oldestChicken"": { ""data"": null }, - ""requiredHasOne"": { + ""firstChicken"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } }, - ""hasMany"": { + ""allChickens"": { ""data"": [ { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } ] }, - ""requiredHasMany"": { + ""chickensReadyForLaying"": { ""data"": [ { - ""type"": ""relationshipModels"", + ""type"": ""chickens"", ""id"": ""1"" } ] @@ -470,52 +467,52 @@ public async Task Can_clear_nullable_relationship() } [Theory] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.HasMany), "hasMany")] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens), "allChickens")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] public async Task Cannot_clear_non_nullable_relationships_with_document_registration(string propertyName, string jsonName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -525,19 +522,20 @@ public async Task Cannot_clear_non_nullable_relationships_with_document_registra object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; relationshipToClear.SetPropertyToDefaultValue("Data"); - var requestDocument = new NrtDisabledModelPostRequestDocument + var requestDocument = new HenHousePostRequestDocument { - Data = new NrtDisabledModelDataInPostRequest + Data = new HenHouseDataInPostRequest { Relationships = relationshipsInPostDocument } }; - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, model => model.RequiredHasOne, model => model.HasMany, model => model.RequiredHasMany)) + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + model => model.FirstChicken, model => model.AllChickens, model => model.ChickensReadyForLaying)) { // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); // Assert ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); @@ -546,54 +544,54 @@ public async Task Cannot_clear_non_nullable_relationships_with_document_registra exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); } } - + [Theory] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.HasMany), "hasMany")] - [InlineData(nameof(NrtDisabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens), "allChickens")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] public async Task Cannot_clear_non_nullable_relationships_without_document_registration(string propertyName, string jsonName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - NrtDisabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() { - HasOne = new NullableToOneRelationshipModelInRequest + OldestChicken = new NullableToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstChicken = new ToOneChickenInRequest { - Data = new RelationshipModelIdentifier + Data = new ChickenIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } }, - HasMany = new ToManyRelationshipModelInRequest + AllChickens = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + ChickensReadyForLaying = new ToManyChickenInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = ChickenResourceType.Chickens } } } @@ -603,17 +601,17 @@ public async Task Cannot_clear_non_nullable_relationships_without_document_regis object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; relationshipToClear.SetPropertyToDefaultValue("Data"); - var requestDocument = new NrtDisabledModelPostRequestDocument + var requestDocument = new HenHousePostRequestDocument { - Data = new NrtDisabledModelDataInPostRequest + Data = new HenHouseDataInPostRequest { Relationships = relationshipsInPostDocument } }; - + // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtDisabledModelAsync(requestDocument)); + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); // Assert ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); @@ -621,7 +619,7 @@ public async Task Cannot_clear_non_nullable_relationships_without_document_regis exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); } - + private string GetRelationshipsObjectWithSinglePropertyOmitted(string excludeProperty) { string partial = ""; @@ -631,7 +629,7 @@ private string GetRelationshipsObjectWithSinglePropertyOmitted(string excludePro if (excludeProperty == key) { continue; - } + } if (partial.Length > 0) { @@ -646,3 +644,4 @@ private string GetRelationshipsObjectWithSinglePropertyOmitted(string excludePro }"; } } + diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/swagger.g.json b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/swagger.g.json deleted file mode 100644 index 2d92b99038..0000000000 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/swagger.g.json +++ /dev/null @@ -1,1632 +0,0 @@ -{ - "openapi": "3.0.1", - "info": { - "title": "OpenApiTests", - "version": "1.0" - }, - "paths": { - "/nrtDisabledModels": { - "get": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "getNrtDisabledModelCollection", - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtDisabledModelCollectionResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "headNrtDisabledModelCollection", - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtDisabledModelCollectionResponseDocument" - } - } - } - } - } - }, - "post": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "postNrtDisabledModel", - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtDisabledModelPostRequestDocument" - } - } - } - }, - "responses": { - "201": { - "description": "Created", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtDisabledModelPrimaryResponseDocument" - } - } - } - }, - "204": { - "description": "No Content" - } - } - } - }, - "/nrtDisabledModels/{id}": { - "get": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "getNrtDisabledModel", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtDisabledModelPrimaryResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "headNrtDisabledModel", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtDisabledModelPrimaryResponseDocument" - } - } - } - } - } - }, - "patch": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "patchNrtDisabledModel", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtDisabledModelPatchRequestDocument" - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtDisabledModelPrimaryResponseDocument" - } - } - } - }, - "204": { - "description": "No Content" - } - } - }, - "delete": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "deleteNrtDisabledModel", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "204": { - "description": "No Content" - } - } - } - }, - "/nrtDisabledModels/{id}/hasMany": { - "get": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "getNrtDisabledModelHasMany", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "headNrtDisabledModelHasMany", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" - } - } - } - } - } - } - }, - "/nrtDisabledModels/{id}/relationships/hasMany": { - "get": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "getNrtDisabledModelHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "headNrtDisabledModelHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" - } - } - } - } - } - }, - "post": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "postNrtDisabledModelHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - }, - "patch": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "patchNrtDisabledModelHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - }, - "delete": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "deleteNrtDisabledModelHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - } - }, - "/nrtDisabledModels/{id}/hasOne": { - "get": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "getNrtDisabledModelHasOne", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nullableRelationshipModelSecondaryResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "headNrtDisabledModelHasOne", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nullableRelationshipModelSecondaryResponseDocument" - } - } - } - } - } - } - }, - "/nrtDisabledModels/{id}/relationships/hasOne": { - "get": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "getNrtDisabledModelHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nullableRelationshipModelIdentifierResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "headNrtDisabledModelHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nullableRelationshipModelIdentifierResponseDocument" - } - } - } - } - } - }, - "patch": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "patchNrtDisabledModelHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nullableToOneRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - } - }, - "/nrtDisabledModels/{id}/requiredHasMany": { - "get": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "getNrtDisabledModelRequiredHasMany", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "headNrtDisabledModelRequiredHasMany", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" - } - } - } - } - } - } - }, - "/nrtDisabledModels/{id}/relationships/requiredHasMany": { - "get": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "getNrtDisabledModelRequiredHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "headNrtDisabledModelRequiredHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" - } - } - } - } - } - }, - "post": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "postNrtDisabledModelRequiredHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - }, - "patch": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "patchNrtDisabledModelRequiredHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - }, - "delete": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "deleteNrtDisabledModelRequiredHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - } - }, - "/nrtDisabledModels/{id}/requiredHasOne": { - "get": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "getNrtDisabledModelRequiredHasOne", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "headNrtDisabledModelRequiredHasOne", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" - } - } - } - } - } - } - }, - "/nrtDisabledModels/{id}/relationships/requiredHasOne": { - "get": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "getNrtDisabledModelRequiredHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "headNrtDisabledModelRequiredHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" - } - } - } - } - } - }, - "patch": { - "tags": [ - "nrtDisabledModels" - ], - "operationId": "patchNrtDisabledModelRequiredHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toOneRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - } - } - }, - "components": { - "schemas": { - "jsonapiObject": { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "ext": { - "type": "array", - "items": { - "type": "string" - } - }, - "profile": { - "type": "array", - "items": { - "type": "string" - } - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "linksInRelationshipObject": { - "required": [ - "related", - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "related": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceCollectionDocument": { - "required": [ - "first", - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "describedby": { - "type": "string" - }, - "first": { - "minLength": 1, - "type": "string" - }, - "last": { - "type": "string" - }, - "prev": { - "type": "string" - }, - "next": { - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceDocument": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "describedby": { - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceIdentifierCollectionDocument": { - "required": [ - "first", - "related", - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "describedby": { - "type": "string" - }, - "related": { - "minLength": 1, - "type": "string" - }, - "first": { - "minLength": 1, - "type": "string" - }, - "last": { - "type": "string" - }, - "prev": { - "type": "string" - }, - "next": { - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceIdentifierDocument": { - "required": [ - "related", - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "describedby": { - "type": "string" - }, - "related": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceObject": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, - "nrtDisabledModelCollectionResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/nrtDisabledModelDataInResponse" - } - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" - } - }, - "additionalProperties": false - }, - "nrtDisabledModelDataInPatchRequest": { - "required": [ - "id", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/nrtDisabledModelResourceType" - }, - "id": { - "minLength": 1, - "type": "string" - }, - "relationships": { - "$ref": "#/components/schemas/nrtDisabledModelRelationshipsInPatchRequest" - } - }, - "additionalProperties": false - }, - "nrtDisabledModelDataInPostRequest": { - "required": [ - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/nrtDisabledModelResourceType" - }, - "relationships": { - "$ref": "#/components/schemas/nrtDisabledModelRelationshipsInPostRequest" - } - }, - "additionalProperties": false - }, - "nrtDisabledModelDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/nrtDisabledModelResourceType" - }, - "id": { - "minLength": 1, - "type": "string" - }, - "relationships": { - "$ref": "#/components/schemas/nrtDisabledModelRelationshipsInResponse" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceObject" - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "nrtDisabledModelPatchRequestDocument": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/nrtDisabledModelDataInPatchRequest" - } - }, - "additionalProperties": false - }, - "nrtDisabledModelPostRequestDocument": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/nrtDisabledModelDataInPostRequest" - } - }, - "additionalProperties": false - }, - "nrtDisabledModelPrimaryResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/nrtDisabledModelDataInResponse" - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceDocument" - } - }, - "additionalProperties": false - }, - "nrtDisabledModelRelationshipsInPatchRequest": { - "type": "object", - "properties": { - "hasOne": { - "$ref": "#/components/schemas/nullableToOneRelationshipModelInRequest" - }, - "requiredHasOne": { - "$ref": "#/components/schemas/toOneRelationshipModelInRequest" - }, - "hasMany": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - }, - "requiredHasMany": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - }, - "additionalProperties": false - }, - "nrtDisabledModelRelationshipsInPostRequest": { - "required": [ - "requiredHasMany", - "requiredHasOne" - ], - "type": "object", - "properties": { - "hasOne": { - "$ref": "#/components/schemas/nullableToOneRelationshipModelInRequest" - }, - "requiredHasOne": { - "$ref": "#/components/schemas/toOneRelationshipModelInRequest" - }, - "hasMany": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - }, - "requiredHasMany": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - }, - "additionalProperties": false - }, - "nrtDisabledModelRelationshipsInResponse": { - "type": "object", - "properties": { - "hasOne": { - "$ref": "#/components/schemas/nullableToOneRelationshipModelInResponse" - }, - "requiredHasOne": { - "$ref": "#/components/schemas/toOneRelationshipModelInResponse" - }, - "hasMany": { - "$ref": "#/components/schemas/toManyRelationshipModelInResponse" - }, - "requiredHasMany": { - "$ref": "#/components/schemas/toManyRelationshipModelInResponse" - } - }, - "additionalProperties": false - }, - "nrtDisabledModelResourceType": { - "enum": [ - "nrtDisabledModels" - ], - "type": "string" - }, - "nullValue": { - "not": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "object" - }, - { - "type": "array" - } - ], - "items": { } - }, - "nullable": true - }, - "nullableRelationshipModelIdentifierResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "oneOf": [ - { - "$ref": "#/components/schemas/relationshipModelIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" - } - ] - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" - } - }, - "additionalProperties": false - }, - "nullableRelationshipModelSecondaryResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "oneOf": [ - { - "$ref": "#/components/schemas/relationshipModelDataInResponse" - }, - { - "$ref": "#/components/schemas/nullValue" - } - ] - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceDocument" - } - }, - "additionalProperties": false - }, - "nullableToOneRelationshipModelInRequest": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "oneOf": [ - { - "$ref": "#/components/schemas/relationshipModelIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" - } - ] - } - }, - "additionalProperties": false - }, - "nullableToOneRelationshipModelInResponse": { - "required": [ - "links" - ], - "type": "object", - "properties": { - "data": { - "oneOf": [ - { - "$ref": "#/components/schemas/relationshipModelIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" - } - ] - }, - "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "relationshipModelCollectionResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/relationshipModelDataInResponse" - } - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" - } - }, - "additionalProperties": false - }, - "relationshipModelDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/relationshipModelResourceType" - }, - "id": { - "minLength": 1, - "type": "string" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceObject" - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "relationshipModelIdentifier": { - "required": [ - "id", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/relationshipModelResourceType" - }, - "id": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, - "relationshipModelIdentifierCollectionResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/relationshipModelIdentifier" - } - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" - } - }, - "additionalProperties": false - }, - "relationshipModelIdentifierResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/relationshipModelIdentifier" - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" - } - }, - "additionalProperties": false - }, - "relationshipModelResourceType": { - "enum": [ - "relationshipModels" - ], - "type": "string" - }, - "relationshipModelSecondaryResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/relationshipModelDataInResponse" - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceDocument" - } - }, - "additionalProperties": false - }, - "toManyRelationshipModelInRequest": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/relationshipModelIdentifier" - } - } - }, - "additionalProperties": false - }, - "toManyRelationshipModelInResponse": { - "required": [ - "links" - ], - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/relationshipModelIdentifier" - } - }, - "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "toOneRelationshipModelInRequest": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/relationshipModelIdentifier" - } - }, - "additionalProperties": false - }, - "toOneRelationshipModelInResponse": { - "required": [ - "links" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/relationshipModelIdentifier" - }, - "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - } - } - } -} \ No newline at end of file diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs index b624a756ef..8d62fa4fad 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs @@ -10,7 +10,7 @@ namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; -public sealed class RequestTests +public sealed class RelationshipRequestTests { private const string ChickenUrl = "http://localhost/chickens"; @@ -328,3 +328,4 @@ public async Task Can_set_default_value_to_ValueType_attributes() }"); } } + diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json index d71423c997..b694521108 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json @@ -195,6 +195,925 @@ } } } + }, + "/henHouses": { + "get": { + "tags": [ + "henHouses" + ], + "operationId": "getHenHouseCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/henHouseCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "henHouses" + ], + "operationId": "headHenHouseCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/henHouseCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "henHouses" + ], + "operationId": "postHenHouse", + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/henHousePostRequestDocument" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/henHousePrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "No Content" + } + } + } + }, + "/henHouses/{id}": { + "get": { + "tags": [ + "henHouses" + ], + "operationId": "getHenHouse", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/henHousePrimaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "henHouses" + ], + "operationId": "headHenHouse", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/henHousePrimaryResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "henHouses" + ], + "operationId": "patchHenHouse", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/henHousePatchRequestDocument" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/henHousePrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "henHouses" + ], + "operationId": "deleteHenHouse", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/henHouses/{id}/allChickens": { + "get": { + "tags": [ + "henHouses" + ], + "operationId": "getHenHouseAllChickens", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "henHouses" + ], + "operationId": "headHenHouseAllChickens", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenCollectionResponseDocument" + } + } + } + } + } + } + }, + "/henHouses/{id}/relationships/allChickens": { + "get": { + "tags": [ + "henHouses" + ], + "operationId": "getHenHouseAllChickensRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "henHouses" + ], + "operationId": "headHenHouseAllChickensRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "henHouses" + ], + "operationId": "postHenHouseAllChickensRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyChickenInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "henHouses" + ], + "operationId": "patchHenHouseAllChickensRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyChickenInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "henHouses" + ], + "operationId": "deleteHenHouseAllChickensRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyChickenInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/henHouses/{id}/chickensReadyForLaying": { + "get": { + "tags": [ + "henHouses" + ], + "operationId": "getHenHouseChickensReadyForLaying", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "henHouses" + ], + "operationId": "headHenHouseChickensReadyForLaying", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenCollectionResponseDocument" + } + } + } + } + } + } + }, + "/henHouses/{id}/relationships/chickensReadyForLaying": { + "get": { + "tags": [ + "henHouses" + ], + "operationId": "getHenHouseChickensReadyForLayingRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "henHouses" + ], + "operationId": "headHenHouseChickensReadyForLayingRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "henHouses" + ], + "operationId": "postHenHouseChickensReadyForLayingRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyChickenInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "henHouses" + ], + "operationId": "patchHenHouseChickensReadyForLayingRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyChickenInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "henHouses" + ], + "operationId": "deleteHenHouseChickensReadyForLayingRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyChickenInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/henHouses/{id}/firstChicken": { + "get": { + "tags": [ + "henHouses" + ], + "operationId": "getHenHouseFirstChicken", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "henHouses" + ], + "operationId": "headHenHouseFirstChicken", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/henHouses/{id}/relationships/firstChicken": { + "get": { + "tags": [ + "henHouses" + ], + "operationId": "getHenHouseFirstChickenRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "henHouses" + ], + "operationId": "headHenHouseFirstChickenRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/chickenIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "henHouses" + ], + "operationId": "patchHenHouseFirstChickenRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toOneChickenInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/henHouses/{id}/oldestChicken": { + "get": { + "tags": [ + "henHouses" + ], + "operationId": "getHenHouseOldestChicken", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableChickenSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "henHouses" + ], + "operationId": "headHenHouseOldestChicken", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableChickenSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/henHouses/{id}/relationships/oldestChicken": { + "get": { + "tags": [ + "henHouses" + ], + "operationId": "getHenHouseOldestChickenRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableChickenIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "henHouses" + ], + "operationId": "headHenHouseOldestChickenRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableChickenIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "henHouses" + ], + "operationId": "patchHenHouseOldestChickenRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableToOneChickenInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } } }, "components": { @@ -283,18 +1202,249 @@ "type": "integer", "format": "int32" }, - "timeAtCurrentFarmInDays": { - "type": "integer", - "format": "int32", - "nullable": true + "timeAtCurrentFarmInDays": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "hasProducedEggs": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "chickenCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/chickenDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "chickenDataInPatchRequest": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/chickenResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/chickenAttributesInPatchRequest" + } + }, + "additionalProperties": false + }, + "chickenDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/chickenResourceType" + }, + "attributes": { + "$ref": "#/components/schemas/chickenAttributesInPostRequest" + } + }, + "additionalProperties": false + }, + "chickenDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/chickenResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/chickenAttributesInResponse" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "chickenIdentifier": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/chickenResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "chickenIdentifierCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/chickenIdentifier" + } + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + } + }, + "additionalProperties": false + }, + "chickenIdentifierResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/chickenIdentifier" + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + }, + "additionalProperties": false + }, + "chickenPatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/chickenDataInPatchRequest" + } + }, + "additionalProperties": false + }, + "chickenPostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/chickenDataInPostRequest" + } + }, + "additionalProperties": false + }, + "chickenPrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/chickenDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "chickenResourceType": { + "enum": [ + "chickens" + ], + "type": "string" + }, + "chickenSecondaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/chickenDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" }, - "hasProducedEggs": { - "type": "boolean" + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" } }, "additionalProperties": false }, - "chickenCollectionResponseDocument": { + "henHouseCollectionResponseDocument": { "required": [ "data", "links" @@ -304,12 +1454,12 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/chickenDataInResponse" + "$ref": "#/components/schemas/henHouseDataInResponse" } }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -320,7 +1470,7 @@ }, "additionalProperties": false }, - "chickenDataInPatchRequest": { + "henHouseDataInPatchRequest": { "required": [ "id", "type" @@ -328,34 +1478,34 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/chickenResourceType" + "$ref": "#/components/schemas/henHouseResourceType" }, "id": { "minLength": 1, "type": "string" }, - "attributes": { - "$ref": "#/components/schemas/chickenAttributesInPatchRequest" + "relationships": { + "$ref": "#/components/schemas/henHouseRelationshipsInPatchRequest" } }, "additionalProperties": false }, - "chickenDataInPostRequest": { + "henHouseDataInPostRequest": { "required": [ "type" ], "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/chickenResourceType" + "$ref": "#/components/schemas/henHouseResourceType" }, - "attributes": { - "$ref": "#/components/schemas/chickenAttributesInPostRequest" + "relationships": { + "$ref": "#/components/schemas/henHouseRelationshipsInPostRequest" } }, "additionalProperties": false }, - "chickenDataInResponse": { + "henHouseDataInResponse": { "required": [ "id", "links", @@ -364,50 +1514,50 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/chickenResourceType" + "$ref": "#/components/schemas/henHouseResourceType" }, "id": { "minLength": 1, "type": "string" }, - "attributes": { - "$ref": "#/components/schemas/chickenAttributesInResponse" + "relationships": { + "$ref": "#/components/schemas/henHouseRelationshipsInResponse" }, "links": { "$ref": "#/components/schemas/linksInResourceObject" }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false }, - "chickenPatchRequestDocument": { + "henHousePatchRequestDocument": { "required": [ "data" ], "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/chickenDataInPatchRequest" + "$ref": "#/components/schemas/henHouseDataInPatchRequest" } }, "additionalProperties": false }, - "chickenPostRequestDocument": { + "henHousePostRequestDocument": { "required": [ "data" ], "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/chickenDataInPostRequest" + "$ref": "#/components/schemas/henHouseDataInPostRequest" } }, "additionalProperties": false }, - "chickenPrimaryResponseDocument": { + "henHousePrimaryResponseDocument": { "required": [ "data", "links" @@ -415,11 +1565,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/chickenDataInResponse" + "$ref": "#/components/schemas/henHouseDataInResponse" }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -430,9 +1580,67 @@ }, "additionalProperties": false }, - "chickenResourceType": { + "henHouseRelationshipsInPatchRequest": { + "type": "object", + "properties": { + "oldestChicken": { + "$ref": "#/components/schemas/nullableToOneChickenInRequest" + }, + "firstChicken": { + "$ref": "#/components/schemas/toOneChickenInRequest" + }, + "allChickens": { + "$ref": "#/components/schemas/toManyChickenInRequest" + }, + "chickensReadyForLaying": { + "$ref": "#/components/schemas/toManyChickenInRequest" + } + }, + "additionalProperties": false + }, + "henHouseRelationshipsInPostRequest": { + "required": [ + "chickensReadyForLaying", + "firstChicken" + ], + "type": "object", + "properties": { + "oldestChicken": { + "$ref": "#/components/schemas/nullableToOneChickenInRequest" + }, + "firstChicken": { + "$ref": "#/components/schemas/toOneChickenInRequest" + }, + "allChickens": { + "$ref": "#/components/schemas/toManyChickenInRequest" + }, + "chickensReadyForLaying": { + "$ref": "#/components/schemas/toManyChickenInRequest" + } + }, + "additionalProperties": false + }, + "henHouseRelationshipsInResponse": { + "type": "object", + "properties": { + "oldestChicken": { + "$ref": "#/components/schemas/nullableToOneChickenInResponse" + }, + "firstChicken": { + "$ref": "#/components/schemas/toOneChickenInResponse" + }, + "allChickens": { + "$ref": "#/components/schemas/toManyChickenInResponse" + }, + "chickensReadyForLaying": { + "$ref": "#/components/schemas/toManyChickenInResponse" + } + }, + "additionalProperties": false + }, + "henHouseResourceType": { "enum": [ - "chickens" + "henHouses" ], "type": "string" }, @@ -456,7 +1664,25 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "linksInRelationshipObject": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" } }, "additionalProperties": false @@ -507,6 +1733,62 @@ }, "additionalProperties": false }, + "linksInResourceIdentifierCollectionDocument": { + "required": [ + "first", + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + }, + "first": { + "minLength": 1, + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceIdentifierDocument": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "linksInResourceObject": { "required": [ "self" @@ -519,6 +1801,202 @@ } }, "additionalProperties": false + }, + "nullValue": { + "not": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "object" + }, + { + "type": "array" + } + ], + "items": {} + }, + "nullable": true + }, + "nullableChickenIdentifierResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/chickenIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + }, + "additionalProperties": false + }, + "nullableChickenSecondaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/chickenDataInResponse" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "nullableToOneChickenInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/chickenIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + } + }, + "additionalProperties": false + }, + "nullableToOneChickenInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/chickenIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "toManyChickenInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/chickenIdentifier" + } + } + }, + "additionalProperties": false + }, + "toManyChickenInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/chickenIdentifier" + } + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "toOneChickenInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/chickenIdentifier" + } + }, + "additionalProperties": false + }, + "toOneChickenInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/chickenIdentifier" + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false } } } diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesEnabledClient.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesEnabledClient.cs deleted file mode 100644 index a4d64afdbe..0000000000 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/GeneratedCode/NullableReferenceTypesEnabledClient.cs +++ /dev/null @@ -1,14 +0,0 @@ -using JsonApiDotNetCore.OpenApi.Client; -using Newtonsoft.Json; - -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject.GeneratedCode; - -internal partial class NullableReferenceTypesEnabledClientRelationshipsObject : JsonApiClient -{ - partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) - { - SetSerializerSettingsForJsonApi(settings); - - settings.Formatting = Formatting.Indented; - } -} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RequestTests.cs index a1d331c40a..02659fc39e 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RequestTests.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RequestTests.cs @@ -4,61 +4,61 @@ using JsonApiDotNetCore.Middleware; using Microsoft.Net.Http.Headers; using Newtonsoft.Json; -using OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject.GeneratedCode; +using OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.GeneratedCode; using TestBuildingBlocks; using Xunit; -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled; -public sealed class RequestTests +public sealed class RelationshipsRequestTests { - private const string NrtEnabledModelUrl = "http://localhost/nrtEnabledModels"; + private const string CowStableUrl = "http://localhost/cowStables"; [Fact] public async Task Can_exclude_optional_relationships() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - var requestDocument = new NrtEnabledModelPostRequestDocument() + var requestDocument = new CowStablePostRequestDocument { - Data = new NrtEnabledModelDataInPostRequest + Data = new CowStableDataInPostRequest { - Relationships = new NrtEnabledModelRelationshipsInPostRequest + Relationships = new CowStableRelationshipsInPostRequest { - HasOne = new ToOneRelationshipModelInRequest + OldestCow = new ToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstCow = new ToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - NullableRequiredHasOne = new ToOneRelationshipModelInRequest + FavoriteCow = new ToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + AllCows = new ToManyCowInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } } } @@ -66,43 +66,43 @@ public async Task Can_exclude_optional_relationships() } }; - await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtEnabledModelAsync(requestDocument)); + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); // Assert wrapper.Request.ShouldNotBeNull(); wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(NrtEnabledModelUrl); + wrapper.Request.RequestUri.Should().Be(CowStableUrl); wrapper.Request.Content.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); wrapper.RequestBody.Should().BeJson(@"{ ""data"": { - ""type"": ""nrtEnabledModels"", + ""type"": ""cowStables"", ""relationships"": { - ""hasOne"": { + ""oldestCow"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""cows"", ""id"": ""1"" } }, - ""requiredHasOne"": { + ""firstCow"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""cows"", ""id"": ""1"" } }, - ""nullableRequiredHasOne"": { + ""favoriteCow"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""cows"", ""id"": ""1"" } }, - ""requiredHasMany"": { + ""allCows"": { ""data"": [ { - ""type"": ""relationshipModels"", + ""type"": ""cows"", ""id"": ""1"" } ] @@ -113,61 +113,61 @@ public async Task Can_exclude_optional_relationships() } [Theory] - [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.HasOne), "hasOne")] - [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] - [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.NullableRequiredHasOne), "nullableRequiredHasOne")] - [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.OldestCow), "oldestCow")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.FirstCow), "firstCow")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.FavoriteCow), "favoriteCow")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.AllCows), "allCows")] public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - NrtEnabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + CowStableRelationshipsInPostRequest relationshipsInPostDocument = new() { - HasOne = new ToOneRelationshipModelInRequest + OldestCow = new ToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstCow = new ToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - NullableRequiredHasOne = new ToOneRelationshipModelInRequest + FavoriteCow = new ToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - HasMany = new ToManyRelationshipModelInRequest + CowsReadyForMilking = new ToManyCowInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + AllCows = new ToManyCowInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } } } @@ -175,93 +175,92 @@ public async Task Cannot_exclude_required_relationship_when_performing_POST_with relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - var requestDocument = new NrtEnabledModelPostRequestDocument + var requestDocument = new CowStablePostRequestDocument { - Data = new NrtEnabledModelDataInPostRequest + Data = new CowStableDataInPostRequest { Relationships = relationshipsInPostDocument } }; - Func> action; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) { // Act - action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtEnabledModelAsync(requestDocument)); - } - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); + } } [Theory] - [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.HasOne), "hasOne")] - [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.RequiredHasOne), "requiredHasOne")] - [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.NullableRequiredHasOne), "nullableRequiredHasOne")] - [InlineData(nameof(NrtEnabledModelRelationshipsInPostRequest.RequiredHasMany), "requiredHasMany")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.OldestCow), "oldestCow")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.FirstCow), "firstCow")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.FavoriteCow), "favoriteCow")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.AllCows), "allCows")] public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - NrtEnabledModelRelationshipsInPostRequest relationshipsInPostDocument = new() + CowStableRelationshipsInPostRequest relationshipsInPostDocument = new() { - HasOne = new ToOneRelationshipModelInRequest + OldestCow = new ToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstCow = new ToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - NullableHasOne = new NullableToOneRelationshipModelInRequest + AlbinoCow = new NullableToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - NullableRequiredHasOne = new ToOneRelationshipModelInRequest + FavoriteCow = new ToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - HasMany = new ToManyRelationshipModelInRequest + CowsReadyForMilking = new ToManyCowInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + AllCows = new ToManyCowInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } } } @@ -269,17 +268,17 @@ public async Task Cannot_exclude_required_relationship_when_performing_POST_with relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - var requestDocument = new NrtEnabledModelPostRequestDocument + var requestDocument = new CowStablePostRequestDocument { - Data = new NrtEnabledModelDataInPostRequest + Data = new CowStableDataInPostRequest { Relationships = relationshipsInPostDocument } }; // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtEnabledModelAsync(requestDocument)); + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); // Assert ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); @@ -293,32 +292,32 @@ public async Task Can_exclude_relationships_that_are_required_for_POST_when_perf { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - var requestDocument = new NrtEnabledModelPatchRequestDocument() + var requestDocument = new CowStablePatchRequestDocument { - Data = new NrtEnabledModelDataInPatchRequest + Data = new CowStableDataInPatchRequest { Id = "1", - Type = NrtEnabledModelResourceType.NrtEnabledModels, - Relationships = new NrtEnabledModelRelationshipsInPatchRequest() + Type = CowStableResourceType.CowStables, + Relationships = new CowStableRelationshipsInPatchRequest { - NullableHasOne = new NullableToOneRelationshipModelInRequest + AlbinoCow = new NullableToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - HasMany = new ToManyRelationshipModelInRequest + CowsReadyForMilking = new ToManyCowInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } } } @@ -326,32 +325,32 @@ public async Task Can_exclude_relationships_that_are_required_for_POST_when_perf } }; - await ApiResponse.TranslateAsync(async () => await apiClient.PatchNrtEnabledModelAsync(1, requestDocument)); + await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowStableAsync(1, requestDocument)); // Assert wrapper.Request.ShouldNotBeNull(); wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be(NrtEnabledModelUrl + "/1"); + wrapper.Request.RequestUri.Should().Be(CowStableUrl + "/1"); wrapper.Request.Content.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); wrapper.RequestBody.Should().BeJson(@"{ ""data"": { - ""type"": ""nrtEnabledModels"", + ""type"": ""cowStables"", ""id"": ""1"", ""relationships"": { - ""nullableHasOne"": { + ""albinoCow"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""cows"", ""id"": ""1"" } }, - ""hasMany"": { + ""cowsReadyForMilking"": { ""data"": [ { - ""type"": ""relationshipModels"", + ""type"": ""cows"", ""id"": ""1"" } ] @@ -366,61 +365,61 @@ public async Task Can_clear_nullable_relationship() { // Arrange using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClientRelationshipsObject(wrapper.HttpClient); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - var requestDocument = new NrtEnabledModelPostRequestDocument() + var requestDocument = new CowStablePostRequestDocument { - Data = new NrtEnabledModelDataInPostRequest + Data = new CowStableDataInPostRequest { - Relationships = new NrtEnabledModelRelationshipsInPostRequest + Relationships = new CowStableRelationshipsInPostRequest { - NullableHasOne = new NullableToOneRelationshipModelInRequest + AlbinoCow = new NullableToOneCowInRequest { Data = null }, - HasOne = new ToOneRelationshipModelInRequest + OldestCow = new ToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - RequiredHasOne = new ToOneRelationshipModelInRequest + FirstCow = new ToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - NullableRequiredHasOne = new ToOneRelationshipModelInRequest + FavoriteCow = new ToOneCowInRequest { - Data = new RelationshipModelIdentifier + Data = new CowIdentifier { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } }, - HasMany = new ToManyRelationshipModelInRequest + CowsReadyForMilking = new ToManyCowInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } } }, - RequiredHasMany = new ToManyRelationshipModelInRequest + AllCows = new ToManyCowInRequest { - Data = new List + Data = new List { new() { Id = "1", - Type = RelationshipModelResourceType.RelationshipModels + Type = CowResourceType.Cows } } } @@ -428,54 +427,54 @@ public async Task Can_clear_nullable_relationship() } }; - await ApiResponse.TranslateAsync(async () => await apiClient.PostNrtEnabledModelAsync(requestDocument)); + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); // Assert wrapper.Request.ShouldNotBeNull(); wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(NrtEnabledModelUrl); + wrapper.Request.RequestUri.Should().Be(CowStableUrl); wrapper.Request.Content.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); wrapper.RequestBody.Should().BeJson(@"{ ""data"": { - ""type"": ""nrtEnabledModels"", + ""type"": ""cowStables"", ""relationships"": { - ""hasOne"": { + ""oldestCow"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""cows"", ""id"": ""1"" } }, - ""requiredHasOne"": { + ""firstCow"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""cows"", ""id"": ""1"" } }, - ""nullableHasOne"": { + ""albinoCow"": { ""data"": null }, - ""nullableRequiredHasOne"": { + ""favoriteCow"": { ""data"": { - ""type"": ""relationshipModels"", + ""type"": ""cows"", ""id"": ""1"" } }, - ""hasMany"": { + ""cowsReadyForMilking"": { ""data"": [ { - ""type"": ""relationshipModels"", + ""type"": ""cows"", ""id"": ""1"" } ] }, - ""requiredHasMany"": { + ""allCows"": { ""data"": [ { - ""type"": ""relationshipModels"", + ""type"": ""cows"", ""id"": ""1"" } ] @@ -485,3 +484,4 @@ public async Task Can_clear_nullable_relationship() }"); } } + diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/swagger.g.json b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/swagger.g.json deleted file mode 100644 index 7522cd4fee..0000000000 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/swagger.g.json +++ /dev/null @@ -1,1954 +0,0 @@ -{ - "openapi": "3.0.1", - "info": { - "title": "OpenApiTests", - "version": "1.0" - }, - "paths": { - "/nrtEnabledModels": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelCollection", - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtEnabledModelCollectionResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelCollection", - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtEnabledModelCollectionResponseDocument" - } - } - } - } - } - }, - "post": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "postNrtEnabledModel", - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtEnabledModelPostRequestDocument" - } - } - } - }, - "responses": { - "201": { - "description": "Created", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtEnabledModelPrimaryResponseDocument" - } - } - } - }, - "204": { - "description": "No Content" - } - } - } - }, - "/nrtEnabledModels/{id}": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModel", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtEnabledModelPrimaryResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModel", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtEnabledModelPrimaryResponseDocument" - } - } - } - } - } - }, - "patch": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "patchNrtEnabledModel", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtEnabledModelPatchRequestDocument" - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nrtEnabledModelPrimaryResponseDocument" - } - } - } - }, - "204": { - "description": "No Content" - } - } - }, - "delete": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "deleteNrtEnabledModel", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "204": { - "description": "No Content" - } - } - } - }, - "/nrtEnabledModels/{id}/hasMany": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelHasMany", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelHasMany", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" - } - } - } - } - } - } - }, - "/nrtEnabledModels/{id}/relationships/hasMany": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" - } - } - } - } - } - }, - "post": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "postNrtEnabledModelHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - }, - "patch": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "patchNrtEnabledModelHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - }, - "delete": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "deleteNrtEnabledModelHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - } - }, - "/nrtEnabledModels/{id}/hasOne": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelHasOne", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelHasOne", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" - } - } - } - } - } - } - }, - "/nrtEnabledModels/{id}/relationships/hasOne": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" - } - } - } - } - } - }, - "patch": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "patchNrtEnabledModelHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toOneRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - } - }, - "/nrtEnabledModels/{id}/nullableHasOne": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelNullableHasOne", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nullableRelationshipModelSecondaryResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelNullableHasOne", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nullableRelationshipModelSecondaryResponseDocument" - } - } - } - } - } - } - }, - "/nrtEnabledModels/{id}/relationships/nullableHasOne": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelNullableHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nullableRelationshipModelIdentifierResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelNullableHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nullableRelationshipModelIdentifierResponseDocument" - } - } - } - } - } - }, - "patch": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "patchNrtEnabledModelNullableHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/nullableToOneRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - } - }, - "/nrtEnabledModels/{id}/nullableRequiredHasOne": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelNullableRequiredHasOne", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelNullableRequiredHasOne", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" - } - } - } - } - } - } - }, - "/nrtEnabledModels/{id}/relationships/nullableRequiredHasOne": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelNullableRequiredHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelNullableRequiredHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" - } - } - } - } - } - }, - "patch": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "patchNrtEnabledModelNullableRequiredHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toOneRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - } - }, - "/nrtEnabledModels/{id}/requiredHasMany": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelRequiredHasMany", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelRequiredHasMany", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelCollectionResponseDocument" - } - } - } - } - } - } - }, - "/nrtEnabledModels/{id}/relationships/requiredHasMany": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelRequiredHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelRequiredHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierCollectionResponseDocument" - } - } - } - } - } - }, - "post": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "postNrtEnabledModelRequiredHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - }, - "patch": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "patchNrtEnabledModelRequiredHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - }, - "delete": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "deleteNrtEnabledModelRequiredHasManyRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - } - }, - "/nrtEnabledModels/{id}/requiredHasOne": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelRequiredHasOne", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelRequiredHasOne", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelSecondaryResponseDocument" - } - } - } - } - } - } - }, - "/nrtEnabledModels/{id}/relationships/requiredHasOne": { - "get": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "getNrtEnabledModelRequiredHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "headNrtEnabledModelRequiredHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/relationshipModelIdentifierResponseDocument" - } - } - } - } - } - }, - "patch": { - "tags": [ - "nrtEnabledModels" - ], - "operationId": "patchNrtEnabledModelRequiredHasOneRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toOneRelationshipModelInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - } - } - }, - "components": { - "schemas": { - "jsonapiObject": { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "ext": { - "type": "array", - "items": { - "type": "string" - } - }, - "profile": { - "type": "array", - "items": { - "type": "string" - } - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "linksInRelationshipObject": { - "required": [ - "related", - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "related": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceCollectionDocument": { - "required": [ - "first", - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "describedby": { - "type": "string" - }, - "first": { - "minLength": 1, - "type": "string" - }, - "last": { - "type": "string" - }, - "prev": { - "type": "string" - }, - "next": { - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceDocument": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "describedby": { - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceIdentifierCollectionDocument": { - "required": [ - "first", - "related", - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "describedby": { - "type": "string" - }, - "related": { - "minLength": 1, - "type": "string" - }, - "first": { - "minLength": 1, - "type": "string" - }, - "last": { - "type": "string" - }, - "prev": { - "type": "string" - }, - "next": { - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceIdentifierDocument": { - "required": [ - "related", - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "describedby": { - "type": "string" - }, - "related": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceObject": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, - "nrtEnabledModelCollectionResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/nrtEnabledModelDataInResponse" - } - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" - } - }, - "additionalProperties": false - }, - "nrtEnabledModelDataInPatchRequest": { - "required": [ - "id", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/nrtEnabledModelResourceType" - }, - "id": { - "minLength": 1, - "type": "string" - }, - "relationships": { - "$ref": "#/components/schemas/nrtEnabledModelRelationshipsInPatchRequest" - } - }, - "additionalProperties": false - }, - "nrtEnabledModelDataInPostRequest": { - "required": [ - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/nrtEnabledModelResourceType" - }, - "relationships": { - "$ref": "#/components/schemas/nrtEnabledModelRelationshipsInPostRequest" - } - }, - "additionalProperties": false - }, - "nrtEnabledModelDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/nrtEnabledModelResourceType" - }, - "id": { - "minLength": 1, - "type": "string" - }, - "relationships": { - "$ref": "#/components/schemas/nrtEnabledModelRelationshipsInResponse" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceObject" - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "nrtEnabledModelPatchRequestDocument": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/nrtEnabledModelDataInPatchRequest" - } - }, - "additionalProperties": false - }, - "nrtEnabledModelPostRequestDocument": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/nrtEnabledModelDataInPostRequest" - } - }, - "additionalProperties": false - }, - "nrtEnabledModelPrimaryResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/nrtEnabledModelDataInResponse" - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceDocument" - } - }, - "additionalProperties": false - }, - "nrtEnabledModelRelationshipsInPatchRequest": { - "type": "object", - "properties": { - "hasOne": { - "$ref": "#/components/schemas/toOneRelationshipModelInRequest" - }, - "requiredHasOne": { - "$ref": "#/components/schemas/toOneRelationshipModelInRequest" - }, - "nullableHasOne": { - "$ref": "#/components/schemas/nullableToOneRelationshipModelInRequest" - }, - "nullableRequiredHasOne": { - "$ref": "#/components/schemas/toOneRelationshipModelInRequest" - }, - "hasMany": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - }, - "requiredHasMany": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - }, - "additionalProperties": false - }, - "nrtEnabledModelRelationshipsInPostRequest": { - "required": [ - "hasOne", - "nullableRequiredHasOne", - "requiredHasMany", - "requiredHasOne" - ], - "type": "object", - "properties": { - "hasOne": { - "$ref": "#/components/schemas/toOneRelationshipModelInRequest" - }, - "requiredHasOne": { - "$ref": "#/components/schemas/toOneRelationshipModelInRequest" - }, - "nullableHasOne": { - "$ref": "#/components/schemas/nullableToOneRelationshipModelInRequest" - }, - "nullableRequiredHasOne": { - "$ref": "#/components/schemas/toOneRelationshipModelInRequest" - }, - "hasMany": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - }, - "requiredHasMany": { - "$ref": "#/components/schemas/toManyRelationshipModelInRequest" - } - }, - "additionalProperties": false - }, - "nrtEnabledModelRelationshipsInResponse": { - "type": "object", - "properties": { - "hasOne": { - "$ref": "#/components/schemas/toOneRelationshipModelInResponse" - }, - "requiredHasOne": { - "$ref": "#/components/schemas/toOneRelationshipModelInResponse" - }, - "nullableHasOne": { - "$ref": "#/components/schemas/nullableToOneRelationshipModelInResponse" - }, - "nullableRequiredHasOne": { - "$ref": "#/components/schemas/toOneRelationshipModelInResponse" - }, - "hasMany": { - "$ref": "#/components/schemas/toManyRelationshipModelInResponse" - }, - "requiredHasMany": { - "$ref": "#/components/schemas/toManyRelationshipModelInResponse" - } - }, - "additionalProperties": false - }, - "nrtEnabledModelResourceType": { - "enum": [ - "nrtEnabledModels" - ], - "type": "string" - }, - "nullValue": { - "not": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "object" - }, - { - "type": "array" - } - ], - "items": { } - }, - "nullable": true - }, - "nullableRelationshipModelIdentifierResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "oneOf": [ - { - "$ref": "#/components/schemas/relationshipModelIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" - } - ] - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" - } - }, - "additionalProperties": false - }, - "nullableRelationshipModelSecondaryResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "oneOf": [ - { - "$ref": "#/components/schemas/relationshipModelDataInResponse" - }, - { - "$ref": "#/components/schemas/nullValue" - } - ] - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceDocument" - } - }, - "additionalProperties": false - }, - "nullableToOneRelationshipModelInRequest": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "oneOf": [ - { - "$ref": "#/components/schemas/relationshipModelIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" - } - ] - } - }, - "additionalProperties": false - }, - "nullableToOneRelationshipModelInResponse": { - "required": [ - "links" - ], - "type": "object", - "properties": { - "data": { - "oneOf": [ - { - "$ref": "#/components/schemas/relationshipModelIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" - } - ] - }, - "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "relationshipModelCollectionResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/relationshipModelDataInResponse" - } - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" - } - }, - "additionalProperties": false - }, - "relationshipModelDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/relationshipModelResourceType" - }, - "id": { - "minLength": 1, - "type": "string" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceObject" - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "relationshipModelIdentifier": { - "required": [ - "id", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/relationshipModelResourceType" - }, - "id": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, - "relationshipModelIdentifierCollectionResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/relationshipModelIdentifier" - } - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" - } - }, - "additionalProperties": false - }, - "relationshipModelIdentifierResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/relationshipModelIdentifier" - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" - } - }, - "additionalProperties": false - }, - "relationshipModelResourceType": { - "enum": [ - "relationshipModels" - ], - "type": "string" - }, - "relationshipModelSecondaryResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/relationshipModelDataInResponse" - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceDocument" - } - }, - "additionalProperties": false - }, - "toManyRelationshipModelInRequest": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/relationshipModelIdentifier" - } - } - }, - "additionalProperties": false - }, - "toManyRelationshipModelInResponse": { - "required": [ - "links" - ], - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/relationshipModelIdentifier" - } - }, - "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "toOneRelationshipModelInRequest": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/relationshipModelIdentifier" - } - }, - "additionalProperties": false - }, - "toOneRelationshipModelInResponse": { - "required": [ - "links" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/relationshipModelIdentifier" - }, - "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - } - } - } -} \ No newline at end of file diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json index 14669b5c84..d32dc3f54c 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json @@ -195,6 +195,1227 @@ } } } + }, + "/cowStables": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowStableCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowStableCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "cowStables" + ], + "operationId": "postCowStable", + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowStablePostRequestDocument" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowStablePrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "No Content" + } + } + } + }, + "/cowStables/{id}": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStable", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowStablePrimaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStable", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowStablePrimaryResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "cowStables" + ], + "operationId": "patchCowStable", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowStablePatchRequestDocument" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowStablePrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "cowStables" + ], + "operationId": "deleteCowStable", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/cowStables/{id}/albinoCow": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableAlbinoCow", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableCowSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableAlbinoCow", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableCowSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/cowStables/{id}/relationships/albinoCow": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableAlbinoCowRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableCowIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableAlbinoCowRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableCowIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "cowStables" + ], + "operationId": "patchCowStableAlbinoCowRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableToOneCowInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/cowStables/{id}/allCows": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableAllCows", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableAllCows", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowCollectionResponseDocument" + } + } + } + } + } + } + }, + "/cowStables/{id}/relationships/allCows": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableAllCowsRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableAllCowsRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "cowStables" + ], + "operationId": "postCowStableAllCowsRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyCowInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "cowStables" + ], + "operationId": "patchCowStableAllCowsRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyCowInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "cowStables" + ], + "operationId": "deleteCowStableAllCowsRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyCowInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/cowStables/{id}/cowsReadyForMilking": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableCowsReadyForMilking", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableCowsReadyForMilking", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowCollectionResponseDocument" + } + } + } + } + } + } + }, + "/cowStables/{id}/relationships/cowsReadyForMilking": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableCowsReadyForMilkingRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableCowsReadyForMilkingRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "cowStables" + ], + "operationId": "postCowStableCowsReadyForMilkingRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyCowInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "cowStables" + ], + "operationId": "patchCowStableCowsReadyForMilkingRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyCowInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "cowStables" + ], + "operationId": "deleteCowStableCowsReadyForMilkingRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyCowInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/cowStables/{id}/favoriteCow": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableFavoriteCow", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableFavoriteCow", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/cowStables/{id}/relationships/favoriteCow": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableFavoriteCowRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableFavoriteCowRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "cowStables" + ], + "operationId": "patchCowStableFavoriteCowRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toOneCowInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/cowStables/{id}/firstCow": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableFirstCow", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableFirstCow", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/cowStables/{id}/relationships/firstCow": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableFirstCowRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableFirstCowRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "cowStables" + ], + "operationId": "patchCowStableFirstCowRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toOneCowInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/cowStables/{id}/oldestCow": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableOldestCow", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableOldestCow", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/cowStables/{id}/relationships/oldestCow": { + "get": { + "tags": [ + "cowStables" + ], + "operationId": "getCowStableOldestCowRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "cowStables" + ], + "operationId": "headCowStableOldestCowRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/cowIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "cowStables" + ], + "operationId": "patchCowStableOldestCowRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toOneCowInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } } }, "components": { @@ -311,13 +1532,244 @@ "format": "int32", "nullable": true }, - "hasProducedMilk": { - "type": "boolean" + "hasProducedMilk": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "cowCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/cowDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "cowDataInPatchRequest": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/cowResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/cowAttributesInPatchRequest" + } + }, + "additionalProperties": false + }, + "cowDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/cowResourceType" + }, + "attributes": { + "$ref": "#/components/schemas/cowAttributesInPostRequest" + } + }, + "additionalProperties": false + }, + "cowDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/cowResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/cowAttributesInResponse" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "cowIdentifier": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/cowResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "cowIdentifierCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/cowIdentifier" + } + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + } + }, + "additionalProperties": false + }, + "cowIdentifierResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/cowIdentifier" + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + }, + "additionalProperties": false + }, + "cowPatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/cowDataInPatchRequest" + } + }, + "additionalProperties": false + }, + "cowPostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/cowDataInPostRequest" + } + }, + "additionalProperties": false + }, + "cowPrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/cowDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "cowResourceType": { + "enum": [ + "cows" + ], + "type": "string" + }, + "cowSecondaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/cowDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" } }, "additionalProperties": false }, - "cowCollectionResponseDocument": { + "cowStableCollectionResponseDocument": { "required": [ "data", "links" @@ -327,12 +1779,12 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/cowDataInResponse" + "$ref": "#/components/schemas/cowStableDataInResponse" } }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -343,7 +1795,7 @@ }, "additionalProperties": false }, - "cowDataInPatchRequest": { + "cowStableDataInPatchRequest": { "required": [ "id", "type" @@ -351,34 +1803,34 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/cowResourceType" + "$ref": "#/components/schemas/cowStableResourceType" }, "id": { "minLength": 1, "type": "string" }, - "attributes": { - "$ref": "#/components/schemas/cowAttributesInPatchRequest" + "relationships": { + "$ref": "#/components/schemas/cowStableRelationshipsInPatchRequest" } }, "additionalProperties": false }, - "cowDataInPostRequest": { + "cowStableDataInPostRequest": { "required": [ "type" ], "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/cowResourceType" + "$ref": "#/components/schemas/cowStableResourceType" }, - "attributes": { - "$ref": "#/components/schemas/cowAttributesInPostRequest" + "relationships": { + "$ref": "#/components/schemas/cowStableRelationshipsInPostRequest" } }, "additionalProperties": false }, - "cowDataInResponse": { + "cowStableDataInResponse": { "required": [ "id", "links", @@ -387,50 +1839,50 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/cowResourceType" + "$ref": "#/components/schemas/cowStableResourceType" }, "id": { "minLength": 1, "type": "string" }, - "attributes": { - "$ref": "#/components/schemas/cowAttributesInResponse" + "relationships": { + "$ref": "#/components/schemas/cowStableRelationshipsInResponse" }, "links": { "$ref": "#/components/schemas/linksInResourceObject" }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false }, - "cowPatchRequestDocument": { + "cowStablePatchRequestDocument": { "required": [ "data" ], "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/cowDataInPatchRequest" + "$ref": "#/components/schemas/cowStableDataInPatchRequest" } }, "additionalProperties": false }, - "cowPostRequestDocument": { + "cowStablePostRequestDocument": { "required": [ "data" ], "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/cowDataInPostRequest" + "$ref": "#/components/schemas/cowStableDataInPostRequest" } }, "additionalProperties": false }, - "cowPrimaryResponseDocument": { + "cowStablePrimaryResponseDocument": { "required": [ "data", "links" @@ -438,11 +1890,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/cowDataInResponse" + "$ref": "#/components/schemas/cowStableDataInResponse" }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -453,9 +1905,87 @@ }, "additionalProperties": false }, - "cowResourceType": { + "cowStableRelationshipsInPatchRequest": { + "type": "object", + "properties": { + "oldestCow": { + "$ref": "#/components/schemas/toOneCowInRequest" + }, + "firstCow": { + "$ref": "#/components/schemas/toOneCowInRequest" + }, + "albinoCow": { + "$ref": "#/components/schemas/nullableToOneCowInRequest" + }, + "favoriteCow": { + "$ref": "#/components/schemas/toOneCowInRequest" + }, + "cowsReadyForMilking": { + "$ref": "#/components/schemas/toManyCowInRequest" + }, + "allCows": { + "$ref": "#/components/schemas/toManyCowInRequest" + } + }, + "additionalProperties": false + }, + "cowStableRelationshipsInPostRequest": { + "required": [ + "allCows", + "favoriteCow", + "firstCow", + "oldestCow" + ], + "type": "object", + "properties": { + "oldestCow": { + "$ref": "#/components/schemas/toOneCowInRequest" + }, + "firstCow": { + "$ref": "#/components/schemas/toOneCowInRequest" + }, + "albinoCow": { + "$ref": "#/components/schemas/nullableToOneCowInRequest" + }, + "favoriteCow": { + "$ref": "#/components/schemas/toOneCowInRequest" + }, + "cowsReadyForMilking": { + "$ref": "#/components/schemas/toManyCowInRequest" + }, + "allCows": { + "$ref": "#/components/schemas/toManyCowInRequest" + } + }, + "additionalProperties": false + }, + "cowStableRelationshipsInResponse": { + "type": "object", + "properties": { + "oldestCow": { + "$ref": "#/components/schemas/toOneCowInResponse" + }, + "firstCow": { + "$ref": "#/components/schemas/toOneCowInResponse" + }, + "albinoCow": { + "$ref": "#/components/schemas/nullableToOneCowInResponse" + }, + "favoriteCow": { + "$ref": "#/components/schemas/toOneCowInResponse" + }, + "cowsReadyForMilking": { + "$ref": "#/components/schemas/toManyCowInResponse" + }, + "allCows": { + "$ref": "#/components/schemas/toManyCowInResponse" + } + }, + "additionalProperties": false + }, + "cowStableResourceType": { "enum": [ - "cows" + "cowStables" ], "type": "string" }, @@ -479,7 +2009,25 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "linksInRelationshipObject": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" } }, "additionalProperties": false @@ -530,6 +2078,62 @@ }, "additionalProperties": false }, + "linksInResourceIdentifierCollectionDocument": { + "required": [ + "first", + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + }, + "first": { + "minLength": 1, + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceIdentifierDocument": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "linksInResourceObject": { "required": [ "self" @@ -542,6 +2146,202 @@ } }, "additionalProperties": false + }, + "nullValue": { + "not": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "object" + }, + { + "type": "array" + } + ], + "items": {} + }, + "nullable": true + }, + "nullableCowIdentifierResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/cowIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + }, + "additionalProperties": false + }, + "nullableCowSecondaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/cowDataInResponse" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": {} + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "nullableToOneCowInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/cowIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + } + }, + "additionalProperties": false + }, + "nullableToOneCowInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/cowIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "toManyCowInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/cowIdentifier" + } + } + }, + "additionalProperties": false + }, + "toManyCowInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/cowIdentifier" + } + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "toOneCowInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/cowIdentifier" + } + }, + "additionalProperties": false + }, + "toOneCowInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/cowIdentifier" + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false } } } diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs index a5485de6e5..7d79bf9e6d 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs @@ -15,6 +15,7 @@ public NullabilityTests(OpenApiTestContext(); + testContext.UseController(); testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled"; } @@ -56,3 +57,4 @@ public async Task Property_in_schema_for_resource_should_not_be_nullable(string }); } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs index 16bdc07e15..57c3d96f17 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs @@ -1,16 +1,37 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; using TestBuildingBlocks; namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; +// @formatter:wrap_chained_method_calls chop_always + [UsedImplicitly(ImplicitUseTargetFlags.Members)] public sealed class NullableReferenceTypesDisabledDbContext : TestableDbContext { public DbSet Chicken => Set(); + public DbSet HenHouse => Set(); public NullableReferenceTypesDisabledDbContext(DbContextOptions options) : base(options) { } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(resource => resource.OldestChicken); + + builder.Entity() + .HasOne(resource => resource.FirstChicken); + + builder.Entity() + .HasMany(resource => resource.AllChickens); + + builder.Entity() + .HasMany(resource => resource.ChickensReadyForLaying); + + base.OnModelCreating(builder); + } } diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/HenHouse.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/HenHouse.cs new file mode 100644 index 0000000000..b148444378 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/HenHouse.cs @@ -0,0 +1,27 @@ +#nullable disable + +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] +public sealed class HenHouse : Identifiable +{ + [HasOne] + public Chicken OldestChicken { get; set; } + + [Required] + [HasOne] + public Chicken FirstChicken { get; set; } + + [HasMany] + public ICollection AllChickens { get; set; } = new HashSet(); + + [Required] + [HasMany] + public ICollection ChickensReadyForLaying { get; set; } = new HashSet(); +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationDisabledTests.cs index f34db02848..b9052eda76 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationDisabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationDisabledTests.cs @@ -16,19 +16,19 @@ public ModelStateValidationDisabledTests( { _testContext = testContext; - testContext.UseController(); + testContext.UseController(); } [Theory] - [InlineData("requiredHasOne")] - [InlineData("requiredHasMany")] + [InlineData("firstChicken")] + [InlineData("chickensReadyForLaying")] public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.nrtDisabledModelRelationshipsInPostRequest.required").With(propertySet => + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => { var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); @@ -37,15 +37,15 @@ public async Task Property_in_schema_for_relationships_in_POST_request_should_be } [Theory] - [InlineData("hasOne")] - [InlineData("hasMany")] + [InlineData("oldestChicken")] + [InlineData("allChickens")] public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.nrtDisabledModelRelationshipsInPostRequest.required").With(propertySet => + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => { var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); @@ -60,7 +60,8 @@ public async Task Schema_for_relationships_in_PATCH_request_should_have_no_requi JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldNotContainPath("components.schemas.nrtDisabledModelRelationshipsInPatchRequest.required"); + document.ShouldNotContainPath("components.schemas.henHouseRelationshipsInPatchRequest.required"); } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationEnabledTests.cs index d24f7e63c8..9bea8d4062 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationEnabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationEnabledTests.cs @@ -15,19 +15,19 @@ public ModelStateValidationEnabledTests( { _testContext = testContext; - testContext.UseController(); + testContext.UseController(); } [Theory] - [InlineData("requiredHasOne")] - [InlineData("requiredHasMany")] + [InlineData("firstChicken")] + [InlineData("chickensReadyForLaying")] public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.nrtDisabledModelRelationshipsInPostRequest.required").With(propertySet => + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => { var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); @@ -36,15 +36,15 @@ public async Task Property_in_schema_for_relationships_in_POST_request_should_be } [Theory] - [InlineData("hasOne")] - [InlineData("hasMany")] + [InlineData("oldestChicken")] + [InlineData("allChickens")] public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.nrtDisabledModelRelationshipsInPostRequest.required").With(propertySet => + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => { var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); @@ -59,7 +59,8 @@ public async Task Schema_for_relationships_in_PATCH_request_should_have_no_requi JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldNotContainPath("components.schemas.nrtDisabledModelRelationshipsInPatchRequest.required"); + document.ShouldNotContainPath("components.schemas.henHouseRelationshipsInPatchRequest.required"); } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NrtDisabledModel.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NrtDisabledModel.cs deleted file mode 100644 index b23e1e10ff..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NrtDisabledModel.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -#nullable disable - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] -public sealed class NrtDisabledModel : Identifiable -{ - [HasOne] public RelationshipModel HasOne { get; set; } - [Required] [HasOne] public RelationshipModel RequiredHasOne { get; set; } - [HasMany] public ICollection HasMany { get; set; } = new HashSet(); - [Required] [HasMany] public ICollection RequiredHasMany { get; set; } = new HashSet(); -} \ No newline at end of file diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullabilityTests.cs index 24f31d855a..544c1cd9ec 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullabilityTests.cs @@ -13,19 +13,18 @@ public NullabilityTests(OpenApiTestContext(); - testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject"; + testContext.UseController(); } [Theory] - [InlineData("hasOne")] + [InlineData("oldestChicken")] public async Task Property_in_schema_for_relationship_of_resource_should_be_nullable(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.nrtDisabledModelRelationshipsInPostRequest.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => { schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => { @@ -35,16 +34,16 @@ public async Task Property_in_schema_for_relationship_of_resource_should_be_null } [Theory] - [InlineData("hasMany")] - [InlineData("requiredHasOne")] - [InlineData("requiredHasMany")] + [InlineData("allChickens")] + [InlineData("firstChicken")] + [InlineData("chickensReadyForLaying")] public async Task Property_in_schema_for_relationship_of_resource_should_not_be_nullable(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.nrtDisabledModelRelationshipsInPostRequest.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => { schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => { @@ -54,3 +53,4 @@ public async Task Property_in_schema_for_relationship_of_resource_should_not_be_ } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullableReferenceTypesDisabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullableReferenceTypesDisabledDbContext.cs deleted file mode 100644 index e0a777d8a5..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullableReferenceTypesDisabledDbContext.cs +++ /dev/null @@ -1,33 +0,0 @@ -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; -using TestBuildingBlocks; - -// @formatter:wrap_chained_method_calls chop_always - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class NullableReferenceTypesDisabledDbContext : TestableDbContext -{ - public DbSet NrtDisabledModel => Set(); - public DbSet RelationshipModel => Set(); - - public NullableReferenceTypesDisabledDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - builder.Entity() - .HasOne(resource => resource.HasOne); - builder.Entity() - .HasOne(resource => resource.RequiredHasOne); - builder.Entity() - .HasMany(resource => resource.HasMany); - builder.Entity() - .HasMany(resource => resource.RequiredHasMany); - - base.OnModelCreating(builder); - } -} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RelationshipModel.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RelationshipModel.cs deleted file mode 100644 index 5bb9e96563..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RelationshipModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -#nullable disable -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; - -[Resource] -public class RelationshipModel : Identifiable -{ -} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs index 3b5562a0b3..f876351bec 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs @@ -37,3 +37,5 @@ public sealed class Cow : Identifiable [Required] public bool? HasProducedMilk { get; set; } } + + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs index be3c930744..99100b8353 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs @@ -15,6 +15,7 @@ public NullabilityTests(OpenApiTestContext(); + testContext.UseController(); testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled"; } @@ -59,3 +60,4 @@ public async Task Property_in_schema_for_attribute_of_resource_should_not_be_nul } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs index b7011e7d27..278349c29a 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs @@ -1,16 +1,44 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; using TestBuildingBlocks; namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; +// @formatter:wrap_chained_method_calls chop_always + [UsedImplicitly(ImplicitUseTargetFlags.Members)] public sealed class NullableReferenceTypesEnabledDbContext : TestableDbContext { public DbSet Cow => Set(); + public DbSet CowStable => Set(); public NullableReferenceTypesEnabledDbContext(DbContextOptions options) : base(options) { } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(resource => resource.OldestCow); + + builder.Entity() + .HasOne(resource => resource.FirstCow); + + builder.Entity() + .HasOne(resource => resource.AlbinoCow); + + builder.Entity() + .HasOne(resource => resource.FavoriteCow); + + builder.Entity() + .HasMany(resource => resource.AllCows); + + builder.Entity() + .HasMany(resource => resource.CowsReadyForMilking); + + base.OnModelCreating(builder); + } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/CowStable.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/CowStable.cs new file mode 100644 index 0000000000..c2f4ad9367 --- /dev/null +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/CowStable.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] +public sealed class CowStable : Identifiable +{ + [HasOne] + public Cow OldestCow { get; set; } = null!; + + [Required] + [HasOne] + public Cow FirstCow { get; set; } = null!; + + [HasOne] + public Cow? AlbinoCow { get; set; } + + [Required] + [HasOne] + public Cow? FavoriteCow { get; set; } + + [HasMany] + public ICollection CowsReadyForMilking { get; set; } = new HashSet(); + + [Required] + [HasMany] + public ICollection AllCows { get; set; } = new HashSet(); +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationDisabledTests.cs index b4ec24bbc0..f52097c764 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationDisabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationDisabledTests.cs @@ -16,20 +16,20 @@ public ModelStateValidationDisabledTests( { _testContext = testContext; - testContext.UseController(); + testContext.UseController(); } [Theory] - [InlineData("requiredHasOne")] - [InlineData("requiredHasMany")] - [InlineData("nullableRequiredHasOne")] + [InlineData("firstCow")] + [InlineData("allCows")] + [InlineData("favoriteCow")] public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.nrtEnabledModelRelationshipsInPostRequest.required").With(propertySet => + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => { var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); @@ -38,16 +38,16 @@ public async Task Property_in_schema_for_relationships_in_POST_request_should_be } [Theory] - [InlineData("hasOne")] - [InlineData("hasMany")] - [InlineData("nullableHasOne")] + [InlineData("oldestCow")] + [InlineData("cowsReadyForMilking")] + [InlineData("albinoCow")] public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.nrtEnabledModelRelationshipsInPostRequest.required").With(propertySet => + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => { var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); @@ -62,7 +62,8 @@ public async Task Schema_for_relationships_in_PATCH_request_should_have_no_requi JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldNotContainPath("components.schemas.nrtEnabledModelRelationshipsInPatchRequest.required"); + document.ShouldNotContainPath("components.schemas.cowStableRelationshipsInPatchRequest.required"); } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationEnabledTests.cs index 174bac59aa..946e41ea27 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationEnabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationEnabledTests.cs @@ -15,21 +15,21 @@ public ModelStateValidationEnabledTests( { _testContext = testContext; - testContext.UseController(); + testContext.UseController(); } [Theory] - [InlineData("hasOne")] - [InlineData("requiredHasOne")] - [InlineData("requiredHasMany")] - [InlineData("nullableRequiredHasOne")] + [InlineData("oldestCow")] + [InlineData("firstCow")] + [InlineData("allCows")] + [InlineData("favoriteCow")] public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.nrtEnabledModelRelationshipsInPostRequest.required").With(propertySet => + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => { var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); @@ -38,15 +38,15 @@ public async Task Property_in_schema_for_relationships_in_POST_request_should_be } [Theory] - [InlineData("hasMany")] - [InlineData("nullableHasOne")] + [InlineData("cowsReadyForMilking")] + [InlineData("albinoCow")] public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.nrtEnabledModelRelationshipsInPostRequest.required").With(propertySet => + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => { var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); @@ -61,7 +61,8 @@ public async Task Schema_for_relationships_in_PATCH_request_should_have_no_requi JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldNotContainPath("components.schemas.nrtEnabledModelRelationshipsInPatchRequest.required"); + document.ShouldNotContainPath("components.schemas.cowStableRelationshipsInPatchRequest.required"); } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NrtEnabledModel.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NrtEnabledModel.cs deleted file mode 100644 index 109d4510c2..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NrtEnabledModel.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] -public sealed class NrtEnabledModel : Identifiable -{ - [HasOne] - public RelationshipModel HasOne { get; set; } = null!; - - [Required] - [HasOne] - public RelationshipModel RequiredHasOne { get; set; } = null!; - - [HasOne] - public RelationshipModel? NullableHasOne { get; set; } - - [Required] - [HasOne] - public RelationshipModel? NullableRequiredHasOne { get; set; } - - [HasMany] - public ICollection HasMany { get; set; } = new HashSet(); - - [Required] - [HasMany] - public ICollection RequiredHasMany { get; set; } = new HashSet(); -} \ No newline at end of file diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullabilityTests.cs index ed710bbf41..ed599af06c 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullabilityTests.cs @@ -13,19 +13,18 @@ public NullabilityTests(OpenApiTestContext(); - testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject"; + testContext.UseController(); } [Theory] - [InlineData("nullableHasOne")] + [InlineData("albinoCow")] public async Task Property_in_schema_for_relationship_of_resource_should_be_nullable(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.nrtEnabledModelRelationshipsInPostRequest.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => { schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => { @@ -35,18 +34,18 @@ public async Task Property_in_schema_for_relationship_of_resource_should_be_null } [Theory] - [InlineData("hasOne")] - [InlineData("requiredHasOne")] - [InlineData("hasMany")] - [InlineData("requiredHasMany")] - [InlineData("nullableRequiredHasOne")] + [InlineData("oldestCow")] + [InlineData("firstCow")] + [InlineData("cowsReadyForMilking")] + [InlineData("allCows")] + [InlineData("favoriteCow")] public async Task Property_in_schema_for_relationship_of_resource_should_not_be_nullable(string propertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.nrtEnabledModelRelationshipsInPostRequest.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => { schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => { @@ -56,3 +55,4 @@ public async Task Property_in_schema_for_relationship_of_resource_should_not_be_ } } + diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullableReferenceTypesEnabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullableReferenceTypesEnabledDbContext.cs deleted file mode 100644 index 0979fe4e9a..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullableReferenceTypesEnabledDbContext.cs +++ /dev/null @@ -1,43 +0,0 @@ -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; -using TestBuildingBlocks; - -// @formatter:wrap_chained_method_calls chop_always - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class NullableReferenceTypesEnabledDbContext : TestableDbContext -{ - public DbSet NrtEnabledModel => Set(); - public DbSet RelationshipModel => Set(); - - public NullableReferenceTypesEnabledDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - builder.Entity() - .HasOne(resource => resource.HasOne); - - builder.Entity() - .HasOne(resource => resource.RequiredHasOne); - - builder.Entity() - .HasMany(resource => resource.HasMany); - - builder.Entity() - .HasMany(resource => resource.RequiredHasMany); - - builder.Entity() - .HasOne(resource => resource.NullableHasOne); - - builder.Entity() - .HasOne(resource => resource.NullableRequiredHasOne); - - base.OnModelCreating(builder); - } -} - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RelationshipModel.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RelationshipModel.cs deleted file mode 100644 index 119cb1e9e3..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RelationshipModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; - -[Resource] -public class RelationshipModel : Identifiable -{ -} From affc1876074fc9d6d5503bcf11ff4c63e0fa0614 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Sat, 24 Dec 2022 21:36:52 +0100 Subject: [PATCH 19/26] Move into consistent folder structure, remove bad cleanupcode eof linebreaks --- .../JsonApiClient.cs | 1 - .../ResourceFieldObjectSchemaBuilder.cs | 1 - .../AlternativeFormRequestTests.cs | 252 +++++++ .../NullableReferenceTypesDisabledClient.cs | 1 - .../RelationshipsObject/RequestTests.cs | 559 --------------- .../RelationshipsObject/RequestTestsAlt.cs | 647 ------------------ .../RequestTests.cs | 544 ++++++++++++++- .../swagger.g.json | 32 +- .../RelationshipsObject/RequestTests.cs | 487 ------------- .../RequestTests.cs | 471 +++++++++++++ .../swagger.g.json | 32 +- test/OpenApiTests/JsonElementExtensions.cs | 3 +- .../{RelationshipsObject => }/HenHouse.cs | 2 +- .../ModelStateValidationDisabledTests.cs | 45 ++ .../ModelStateValidationEnabledTests.cs | 45 ++ .../NullabilityTests.cs | 41 +- ...NullableReferenceTypesDisabledDbContext.cs | 1 - .../ModelStateValidationDisabledTests.cs | 67 -- .../ModelStateValidationEnabledTests.cs | 66 -- .../RelationshipsObject/NullabilityTests.cs | 56 -- .../NullableReferenceTypesEnabled/Cow.cs | 2 - .../{RelationshipsObject => }/CowStable.cs | 2 +- .../ModelStateValidationDisabledTests.cs | 47 ++ .../ModelStateValidationEnabledTests.cs | 47 ++ .../NullabilityTests.cs | 38 +- .../NullableReferenceTypesEnabledDbContext.cs | 2 - .../ModelStateValidationDisabledTests.cs | 69 -- .../ModelStateValidationEnabledTests.cs | 68 -- .../RelationshipsObject/NullabilityTests.cs | 58 -- 29 files changed, 1560 insertions(+), 2126 deletions(-) create mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/AlternativeFormRequestTests.cs delete mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTests.cs delete mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTestsAlt.cs delete mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RequestTests.cs rename test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/{RelationshipsObject => }/HenHouse.cs (96%) delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationDisabledTests.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationEnabledTests.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullabilityTests.cs rename test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/{RelationshipsObject => }/CowStable.cs (96%) delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationDisabledTests.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationEnabledTests.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullabilityTests.cs diff --git a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs index 79a25e7deb..f6d9dbdb2d 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs +++ b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs @@ -345,4 +345,3 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ } } } - diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs index 14cdfcc953..7c9cb75a15 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs @@ -238,4 +238,3 @@ private static bool IsDataPropertyNullableInRelationshipSchemaType(Type relation return NullableRelationshipSchemaOpenTypes.Contains(relationshipSchemaOpenType); } } - diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/AlternativeFormRequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/AlternativeFormRequestTests.cs new file mode 100644 index 0000000000..daf1bd38b6 --- /dev/null +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/AlternativeFormRequestTests.cs @@ -0,0 +1,252 @@ +using System.Net; +using FluentAssertions; +using JsonApiDotNetCore.Middleware; +using Microsoft.Net.Http.Headers; +using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; + +/// +/// Should consider if the shape of the two tests here is more favourable over the test with the same name in the RequestTests suite. The drawback of the +/// form here is that the expected json string is less easy to read. However the win is that this form allows us to run the tests in a [Theory]. This is +/// relevant because each of these properties represent unique test cases. In the other test form, it is not clear which properties are tested without. +/// For instance: here in Can_exclude_optional_relationships it is immediately clear that the properties we omit are those in the inline data. +/// +public sealed class AlternativeFormRequestTests +{ + private const string HenHouseUrl = "http://localhost/henHouses"; + + private readonly Dictionary _partials = new() + { + { + nameof(HenHouseRelationshipsInPostRequest.OldestChicken), @"""oldestChicken"": { + ""data"": { + ""type"": ""chickens"", + ""id"": ""1"" + } + }" + }, + { + nameof(HenHouseRelationshipsInPostRequest.FirstChicken), @"""firstChicken"": { + ""data"": { + ""type"": ""chickens"", + ""id"": ""1"" + } + }" + }, + { + nameof(HenHouseRelationshipsInPostRequest.AllChickens), @"""allChickens"": { + ""data"": [ + { + ""type"": ""chickens"", + ""id"": ""1"" + } + ] + }" + }, + + { + nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), @"""chickensReadyForLaying"": { + ""data"": [ + { + ""type"": ""chickens"", + ""id"": ""1"" + } + ] + }" + } + }; + + [Theory] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.OldestChicken))] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens))] + public async Task Can_exclude_optional_relationships(string propertyName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHouseRelationshipsInPostRequest relationshipsObject = new() + { + OldestChicken = new NullableToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + FirstChicken = new ToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + AllChickens = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + }, + ChickensReadyForLaying = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + } + }; + + relationshipsObject.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new HenHousePostRequestDocument + { + Data = new HenHouseDataInPostRequest + { + Relationships = relationshipsObject + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(HenHouseUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + string body = GetRelationshipsObjectWithSinglePropertyOmitted(propertyName); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""henHouses"", + ""relationships"": " + body + @" + } +}"); + } + + [Theory] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken))] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying))] + public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH(string propertyName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + var relationshipsObject = new HenHouseRelationshipsInPatchRequest + { + OldestChicken = new NullableToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + FirstChicken = new ToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + AllChickens = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + }, + ChickensReadyForLaying = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + } + }; + + relationshipsObject.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new HenHousePatchRequestDocument + { + Data = new HenHouseDataInPatchRequest + { + Id = "1", + Type = HenHouseResourceType.HenHouses, + Relationships = relationshipsObject + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PatchHenHouseAsync(1, requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Patch); + wrapper.Request.RequestUri.Should().Be(HenHouseUrl + "/1"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + string serializedRelationshipsObject = GetRelationshipsObjectWithSinglePropertyOmitted(propertyName); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""henHouses"", + ""id"": ""1"", + ""relationships"": " + serializedRelationshipsObject + @" + } +}"); + } + + private string GetRelationshipsObjectWithSinglePropertyOmitted(string excludeProperty) + { + string partial = ""; + + foreach ((string key, string relationshipJsonPartial) in _partials) + { + if (excludeProperty == key) + { + continue; + } + + if (partial.Length > 0) + { + partial += ",\n "; + } + + partial += relationshipJsonPartial; + } + + return @"{ + " + partial + @" + }"; + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs index 6670a0566f..c1a4eebab5 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs @@ -12,4 +12,3 @@ partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) settings.Formatting = Formatting.Indented; } } - diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTests.cs deleted file mode 100644 index 9ccda82859..0000000000 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTests.cs +++ /dev/null @@ -1,559 +0,0 @@ -using System.Net; -using System.Reflection; -using FluentAssertions; -using FluentAssertions.Specialized; -using JsonApiDotNetCore.Middleware; -using Microsoft.Net.Http.Headers; -using Newtonsoft.Json; -using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; - -public sealed class RequestTests -{ - private const string HenHouseUrl = "http://localhost/henHouses"; - - [Fact] - public async Task Can_exclude_optional_relationships() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = new HenHouseRelationshipsInPostRequest - { - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(HenHouseUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""henHouses"", - ""relationships"": { - ""firstChicken"": { - ""data"": { - ""type"": ""chickens"", - ""id"": ""1"" - } - }, - ""chickensReadyForLaying"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] - public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); - } - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] - public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Cannot write a null value for property '{jsonName}'. Property requires a value. Path 'data.relationships'."); - } - - [Fact] - public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new HenHousePatchRequestDocument - { - Data = new HenHouseDataInPatchRequest - { - Id = "1", - Type = HenHouseResourceType.HenHouses, - Relationships = new HenHouseRelationshipsInPatchRequest - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PatchHenHouseAsync(1, requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be(HenHouseUrl + "/1"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""henHouses"", - ""id"": ""1"", - ""relationships"": { - ""oldestChicken"": { - ""data"": { - ""type"": ""chickens"", - ""id"": ""1"" - } - }, - ""allChickens"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } - - [Fact] - public async Task Can_clear_nullable_relationship() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = new HenHouseRelationshipsInPostRequest - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = null - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(HenHouseUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""henHouses"", - ""relationships"": { - ""oldestChicken"": { - ""data"": null - }, - ""firstChicken"": { - ""data"": { - ""type"": ""chickens"", - ""id"": ""1"" - } - }, - ""allChickens"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - }, - ""chickensReadyForLaying"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens), "allChickens")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] - public async Task Cannot_clear_non_nullable_relationships_with_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - PropertyInfo relationshipToClearPropertyInfo = relationshipsInPostDocument.GetType().GetProperties().Single(property => property.Name == propertyName); - object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; - relationshipToClear.SetPropertyToDefaultValue("Data"); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - Func> action; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, - model => model.FirstChicken, model => model.AllChickens, model => model.ChickensReadyForLaying)) - { - // Act - action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - } - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens), "allChickens")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] - public async Task Cannot_clear_non_nullable_relationships_without_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - PropertyInfo relationshipToClearPropertyInfo = relationshipsInPostDocument.GetType().GetProperties().Single(property => property.Name == propertyName); - object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; - relationshipToClear.SetPropertyToDefaultValue("Data"); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); - } -} - diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTestsAlt.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTestsAlt.cs deleted file mode 100644 index 870bb548fd..0000000000 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/RequestTestsAlt.cs +++ /dev/null @@ -1,647 +0,0 @@ -using System.Net; -using System.Reflection; -using FluentAssertions; -using FluentAssertions.Specialized; -using JsonApiDotNetCore.Middleware; -using Microsoft.Net.Http.Headers; -using Newtonsoft.Json; -using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; - -public sealed class RequestTestsAlt -{ - private const string HenHouseUrl = "http://localhost/henHouses"; - - private readonly Dictionary _partials = new() - { - { - nameof(HenHouseRelationshipsInPostRequest.OldestChicken), @"""oldestChicken"": { - ""data"": { - ""type"": ""chickens"", - ""id"": ""1"" - } - }" - }, - { - nameof(HenHouseRelationshipsInPostRequest.FirstChicken), @"""firstChicken"": { - ""data"": { - ""type"": ""chickens"", - ""id"": ""1"" - } - }" - }, - { - nameof(HenHouseRelationshipsInPostRequest.AllChickens), @"""allChickens"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - }" - }, - - { - nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), @"""chickensReadyForLaying"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - }" - } - }; - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.OldestChicken))] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens))] - public async Task Can_exclude_optional_relationships(string propertyName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsObject = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - relationshipsObject.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsObject - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(HenHouseUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - string body = GetRelationshipsObjectWithSinglePropertyOmitted(propertyName); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""henHouses"", - ""relationships"": " + body + @" - } -}"); - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] - public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); - } - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] - public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Cannot write a null value for property '{jsonName}'. Property requires a value. Path 'data.relationships'."); - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken))] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying))] - public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH(string propertyName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var relationshipsObject = new HenHouseRelationshipsInPatchRequest - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - relationshipsObject.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new HenHousePatchRequestDocument - { - Data = new HenHouseDataInPatchRequest - { - Id = "1", - Type = HenHouseResourceType.HenHouses, - Relationships = relationshipsObject - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PatchHenHouseAsync(1, requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be(HenHouseUrl + "/1"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - string serializedRelationshipsObject = GetRelationshipsObjectWithSinglePropertyOmitted(propertyName); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""henHouses"", - ""id"": ""1"", - ""relationships"": " + serializedRelationshipsObject + @" - } -}"); - } - - [Fact] - public async Task Can_clear_nullable_relationship() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = new HenHouseRelationshipsInPostRequest - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = null - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(HenHouseUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""henHouses"", - ""relationships"": { - ""oldestChicken"": { - ""data"": null - }, - ""firstChicken"": { - ""data"": { - ""type"": ""chickens"", - ""id"": ""1"" - } - }, - ""allChickens"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - }, - ""chickensReadyForLaying"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens), "allChickens")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] - public async Task Cannot_clear_non_nullable_relationships_with_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - PropertyInfo relationshipToClearPropertyInfo = relationshipsInPostDocument.GetType().GetProperties().Single(property => property.Name == propertyName); - object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; - relationshipToClear.SetPropertyToDefaultValue("Data"); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, - model => model.FirstChicken, model => model.AllChickens, model => model.ChickensReadyForLaying)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); - } - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens), "allChickens")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] - public async Task Cannot_clear_non_nullable_relationships_without_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - PropertyInfo relationshipToClearPropertyInfo = relationshipsInPostDocument.GetType().GetProperties().Single(property => property.Name == propertyName); - object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; - relationshipToClear.SetPropertyToDefaultValue("Data"); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); - } - - private string GetRelationshipsObjectWithSinglePropertyOmitted(string excludeProperty) - { - string partial = ""; - - foreach ((string key, string relationshipJsonPartial) in _partials) - { - if (excludeProperty == key) - { - continue; - } - - if (partial.Length > 0) - { - partial += ",\n "; - } - - partial += relationshipJsonPartial; - } - - return @"{ - " + partial + @" - }"; - } -} - diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs index 8d62fa4fad..1ee3ee0fc3 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Reflection; using FluentAssertions; using FluentAssertions.Specialized; using JsonApiDotNetCore.Middleware; @@ -13,6 +14,7 @@ namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; public sealed class RelationshipRequestTests { private const string ChickenUrl = "http://localhost/chickens"; + private const string HenHouseUrl = "http://localhost/henHouses"; [Fact] public async Task Can_exclude_optional_attributes() @@ -327,5 +329,545 @@ public async Task Can_set_default_value_to_ValueType_attributes() } }"); } -} + [Fact] + public async Task Can_exclude_optional_relationships() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + var requestDocument = new HenHousePostRequestDocument + { + Data = new HenHouseDataInPostRequest + { + Relationships = new HenHouseRelationshipsInPostRequest + { + FirstChicken = new ToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + ChickensReadyForLaying = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(HenHouseUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""henHouses"", + ""relationships"": { + ""firstChicken"": { + ""data"": { + ""type"": ""chickens"", + ""id"": ""1"" + } + }, + ""chickensReadyForLaying"": { + ""data"": [ + { + ""type"": ""chickens"", + ""id"": ""1"" + } + ] + } + } + } +}"); + } + + [Theory] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] + public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() + { + OldestChicken = new NullableToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + FirstChicken = new ToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + AllChickens = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + }, + ChickensReadyForLaying = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + } + }; + + relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new HenHousePostRequestDocument + { + Data = new HenHouseDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); + } + } + + [Theory] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] + public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() + { + OldestChicken = new NullableToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + FirstChicken = new ToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + AllChickens = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + }, + ChickensReadyForLaying = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + } + }; + + relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new HenHousePostRequestDocument + { + Data = new HenHouseDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Cannot write a null value for property '{jsonName}'. Property requires a value. Path 'data.relationships'."); + } + + [Fact] + public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + var requestDocument = new HenHousePatchRequestDocument + { + Data = new HenHouseDataInPatchRequest + { + Id = "1", + Type = HenHouseResourceType.HenHouses, + Relationships = new HenHouseRelationshipsInPatchRequest + { + OldestChicken = new NullableToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + AllChickens = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PatchHenHouseAsync(1, requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Patch); + wrapper.Request.RequestUri.Should().Be(HenHouseUrl + "/1"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""henHouses"", + ""id"": ""1"", + ""relationships"": { + ""oldestChicken"": { + ""data"": { + ""type"": ""chickens"", + ""id"": ""1"" + } + }, + ""allChickens"": { + ""data"": [ + { + ""type"": ""chickens"", + ""id"": ""1"" + } + ] + } + } + } +}"); + } + + [Fact] + public async Task Can_clear_nullable_relationship() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + var requestDocument = new HenHousePostRequestDocument + { + Data = new HenHouseDataInPostRequest + { + Relationships = new HenHouseRelationshipsInPostRequest + { + OldestChicken = new NullableToOneChickenInRequest + { + Data = null + }, + FirstChicken = new ToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + AllChickens = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + }, + ChickensReadyForLaying = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(HenHouseUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""henHouses"", + ""relationships"": { + ""oldestChicken"": { + ""data"": null + }, + ""firstChicken"": { + ""data"": { + ""type"": ""chickens"", + ""id"": ""1"" + } + }, + ""allChickens"": { + ""data"": [ + { + ""type"": ""chickens"", + ""id"": ""1"" + } + ] + }, + ""chickensReadyForLaying"": { + ""data"": [ + { + ""type"": ""chickens"", + ""id"": ""1"" + } + ] + } + } + } +}"); + } + + [Theory] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens), "allChickens")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] + public async Task Cannot_clear_non_nullable_relationships_with_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() + { + OldestChicken = new NullableToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + FirstChicken = new ToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + AllChickens = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + }, + ChickensReadyForLaying = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + } + }; + + PropertyInfo relationshipToClearPropertyInfo = relationshipsInPostDocument.GetType().GetProperties().Single(property => property.Name == propertyName); + object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; + relationshipToClear.SetPropertyToDefaultValue("Data"); + + var requestDocument = new HenHousePostRequestDocument + { + Data = new HenHouseDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + Func> action; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + model => model.FirstChicken, model => model.AllChickens, model => model.ChickensReadyForLaying)) + { + // Act + action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + } + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); + } + + [Theory] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens), "allChickens")] + [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] + public async Task Cannot_clear_non_nullable_relationships_without_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() + { + OldestChicken = new NullableToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + FirstChicken = new ToOneChickenInRequest + { + Data = new ChickenIdentifier + { + Id = "1", + Type = ChickenResourceType.Chickens + } + }, + AllChickens = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + }, + ChickensReadyForLaying = new ToManyChickenInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = ChickenResourceType.Chickens + } + } + } + }; + + PropertyInfo relationshipToClearPropertyInfo = relationshipsInPostDocument.GetType().GetProperties().Single(property => property.Name == propertyName); + object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; + relationshipToClear.SetPropertyToDefaultValue("Data"); + + var requestDocument = new HenHousePostRequestDocument + { + Data = new HenHouseDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json index b694521108..8357d61d05 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json @@ -1228,7 +1228,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1297,7 +1297,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } } }, "additionalProperties": false @@ -1334,7 +1334,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1357,7 +1357,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1404,7 +1404,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1433,7 +1433,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1459,7 +1459,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1528,7 +1528,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } } }, "additionalProperties": false @@ -1569,7 +1569,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1664,7 +1664,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } } }, "additionalProperties": false @@ -1821,7 +1821,7 @@ "type": "array" } ], - "items": {} + "items": { } }, "nullable": true }, @@ -1844,7 +1844,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1874,7 +1874,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1925,7 +1925,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } } }, "additionalProperties": false @@ -1962,7 +1962,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } } }, "additionalProperties": false @@ -1993,7 +1993,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } } }, "additionalProperties": false diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RequestTests.cs deleted file mode 100644 index 02659fc39e..0000000000 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/RequestTests.cs +++ /dev/null @@ -1,487 +0,0 @@ -using System.Net; -using FluentAssertions; -using FluentAssertions.Specialized; -using JsonApiDotNetCore.Middleware; -using Microsoft.Net.Http.Headers; -using Newtonsoft.Json; -using OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.GeneratedCode; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled; - -public sealed class RelationshipsRequestTests -{ - private const string CowStableUrl = "http://localhost/cowStables"; - - [Fact] - public async Task Can_exclude_optional_relationships() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - var requestDocument = new CowStablePostRequestDocument - { - Data = new CowStableDataInPostRequest - { - Relationships = new CowStableRelationshipsInPostRequest - { - OldestCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FirstCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FavoriteCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - AllCows = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(CowStableUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""cowStables"", - ""relationships"": { - ""oldestCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""firstCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""favoriteCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""allCows"": { - ""data"": [ - { - ""type"": ""cows"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } - - [Theory] - [InlineData(nameof(CowStableRelationshipsInPostRequest.OldestCow), "oldestCow")] - [InlineData(nameof(CowStableRelationshipsInPostRequest.FirstCow), "firstCow")] - [InlineData(nameof(CowStableRelationshipsInPostRequest.FavoriteCow), "favoriteCow")] - [InlineData(nameof(CowStableRelationshipsInPostRequest.AllCows), "allCows")] - public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStableRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FirstCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FavoriteCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - CowsReadyForMilking = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - }, - AllCows = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - } - }; - - relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new CowStablePostRequestDocument - { - Data = new CowStableDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); - } - } - - [Theory] - [InlineData(nameof(CowStableRelationshipsInPostRequest.OldestCow), "oldestCow")] - [InlineData(nameof(CowStableRelationshipsInPostRequest.FirstCow), "firstCow")] - [InlineData(nameof(CowStableRelationshipsInPostRequest.FavoriteCow), "favoriteCow")] - [InlineData(nameof(CowStableRelationshipsInPostRequest.AllCows), "allCows")] - public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStableRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FirstCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - AlbinoCow = new NullableToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FavoriteCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - CowsReadyForMilking = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - }, - AllCows = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - } - }; - - relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new CowStablePostRequestDocument - { - Data = new CowStableDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Cannot write a null value for property '{jsonName}'. Property requires a value. Path 'data.relationships'."); - } - - [Fact] - public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - var requestDocument = new CowStablePatchRequestDocument - { - Data = new CowStableDataInPatchRequest - { - Id = "1", - Type = CowStableResourceType.CowStables, - Relationships = new CowStableRelationshipsInPatchRequest - { - AlbinoCow = new NullableToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - CowsReadyForMilking = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowStableAsync(1, requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be(CowStableUrl + "/1"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""cowStables"", - ""id"": ""1"", - ""relationships"": { - ""albinoCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""cowsReadyForMilking"": { - ""data"": [ - { - ""type"": ""cows"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } - - [Fact] - public async Task Can_clear_nullable_relationship() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - var requestDocument = new CowStablePostRequestDocument - { - Data = new CowStableDataInPostRequest - { - Relationships = new CowStableRelationshipsInPostRequest - { - AlbinoCow = new NullableToOneCowInRequest - { - Data = null - }, - OldestCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FirstCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FavoriteCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - CowsReadyForMilking = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - }, - AllCows = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(CowStableUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""cowStables"", - ""relationships"": { - ""oldestCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""firstCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""albinoCow"": { - ""data"": null - }, - ""favoriteCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""cowsReadyForMilking"": { - ""data"": [ - { - ""type"": ""cows"", - ""id"": ""1"" - } - ] - }, - ""allCows"": { - ""data"": [ - { - ""type"": ""cows"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } -} - diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs index 50fdf9c355..aef305b63d 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs @@ -13,6 +13,7 @@ namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled; public sealed class RequestTests { private const string CowUrl = "http://localhost/cows"; + private const string CowStableUrl = "http://localhost/cowStables"; [Fact] public async Task Can_exclude_optional_attributes() @@ -304,6 +305,476 @@ public async Task Can_set_default_value_to_ValueType_attributes() ""hasProducedMilk"": false } } +}"); + } + + [Fact] + public async Task Can_exclude_optional_relationships() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + var requestDocument = new CowStablePostRequestDocument + { + Data = new CowStableDataInPostRequest + { + Relationships = new CowStableRelationshipsInPostRequest + { + OldestCow = new ToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + FirstCow = new ToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + FavoriteCow = new ToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + AllCows = new ToManyCowInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = CowResourceType.Cows + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(CowStableUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""cowStables"", + ""relationships"": { + ""oldestCow"": { + ""data"": { + ""type"": ""cows"", + ""id"": ""1"" + } + }, + ""firstCow"": { + ""data"": { + ""type"": ""cows"", + ""id"": ""1"" + } + }, + ""favoriteCow"": { + ""data"": { + ""type"": ""cows"", + ""id"": ""1"" + } + }, + ""allCows"": { + ""data"": [ + { + ""type"": ""cows"", + ""id"": ""1"" + } + ] + } + } + } +}"); + } + + [Theory] + [InlineData(nameof(CowStableRelationshipsInPostRequest.OldestCow), "oldestCow")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.FirstCow), "firstCow")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.FavoriteCow), "favoriteCow")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.AllCows), "allCows")] + public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStableRelationshipsInPostRequest relationshipsInPostDocument = new() + { + OldestCow = new ToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + FirstCow = new ToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + FavoriteCow = new ToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + CowsReadyForMilking = new ToManyCowInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = CowResourceType.Cows + } + } + }, + AllCows = new ToManyCowInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = CowResourceType.Cows + } + } + } + }; + + relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new CowStablePostRequestDocument + { + Data = new CowStableDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); + } + } + + [Theory] + [InlineData(nameof(CowStableRelationshipsInPostRequest.OldestCow), "oldestCow")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.FirstCow), "firstCow")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.FavoriteCow), "favoriteCow")] + [InlineData(nameof(CowStableRelationshipsInPostRequest.AllCows), "allCows")] + public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStableRelationshipsInPostRequest relationshipsInPostDocument = new() + { + OldestCow = new ToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + FirstCow = new ToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + AlbinoCow = new NullableToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + FavoriteCow = new ToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + CowsReadyForMilking = new ToManyCowInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = CowResourceType.Cows + } + } + }, + AllCows = new ToManyCowInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = CowResourceType.Cows + } + } + } + }; + + relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); + + var requestDocument = new CowStablePostRequestDocument + { + Data = new CowStableDataInPostRequest + { + Relationships = relationshipsInPostDocument + } + }; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be($"Cannot write a null value for property '{jsonName}'. Property requires a value. Path 'data.relationships'."); + } + + [Fact] + public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + var requestDocument = new CowStablePatchRequestDocument + { + Data = new CowStableDataInPatchRequest + { + Id = "1", + Type = CowStableResourceType.CowStables, + Relationships = new CowStableRelationshipsInPatchRequest + { + AlbinoCow = new NullableToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + CowsReadyForMilking = new ToManyCowInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = CowResourceType.Cows + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowStableAsync(1, requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Patch); + wrapper.Request.RequestUri.Should().Be(CowStableUrl + "/1"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""cowStables"", + ""id"": ""1"", + ""relationships"": { + ""albinoCow"": { + ""data"": { + ""type"": ""cows"", + ""id"": ""1"" + } + }, + ""cowsReadyForMilking"": { + ""data"": [ + { + ""type"": ""cows"", + ""id"": ""1"" + } + ] + } + } + } +}"); + } + + [Fact] + public async Task Can_clear_nullable_relationship() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + var requestDocument = new CowStablePostRequestDocument + { + Data = new CowStableDataInPostRequest + { + Relationships = new CowStableRelationshipsInPostRequest + { + AlbinoCow = new NullableToOneCowInRequest + { + Data = null + }, + OldestCow = new ToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + FirstCow = new ToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + FavoriteCow = new ToOneCowInRequest + { + Data = new CowIdentifier + { + Id = "1", + Type = CowResourceType.Cows + } + }, + CowsReadyForMilking = new ToManyCowInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = CowResourceType.Cows + } + } + }, + AllCows = new ToManyCowInRequest + { + Data = new List + { + new() + { + Id = "1", + Type = CowResourceType.Cows + } + } + } + } + } + }; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be(CowStableUrl); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + wrapper.RequestBody.Should().BeJson(@"{ + ""data"": { + ""type"": ""cowStables"", + ""relationships"": { + ""oldestCow"": { + ""data"": { + ""type"": ""cows"", + ""id"": ""1"" + } + }, + ""firstCow"": { + ""data"": { + ""type"": ""cows"", + ""id"": ""1"" + } + }, + ""albinoCow"": { + ""data"": null + }, + ""favoriteCow"": { + ""data"": { + ""type"": ""cows"", + ""id"": ""1"" + } + }, + ""cowsReadyForMilking"": { + ""data"": [ + { + ""type"": ""cows"", + ""id"": ""1"" + } + ] + }, + ""allCows"": { + ""data"": [ + { + ""type"": ""cows"", + ""id"": ""1"" + } + ] + } + } + } }"); } } diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json index d32dc3f54c..1f2d189d46 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json +++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json @@ -1553,7 +1553,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1622,7 +1622,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } } }, "additionalProperties": false @@ -1659,7 +1659,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1682,7 +1682,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1729,7 +1729,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1758,7 +1758,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1784,7 +1784,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -1853,7 +1853,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } } }, "additionalProperties": false @@ -1894,7 +1894,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -2009,7 +2009,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } } }, "additionalProperties": false @@ -2166,7 +2166,7 @@ "type": "array" } ], - "items": {} + "items": { } }, "nullable": true }, @@ -2189,7 +2189,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -2219,7 +2219,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } }, "jsonapi": { "$ref": "#/components/schemas/jsonapiObject" @@ -2270,7 +2270,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } } }, "additionalProperties": false @@ -2307,7 +2307,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } } }, "additionalProperties": false @@ -2338,7 +2338,7 @@ }, "meta": { "type": "object", - "additionalProperties": {} + "additionalProperties": { } } }, "additionalProperties": false diff --git a/test/OpenApiTests/JsonElementExtensions.cs b/test/OpenApiTests/JsonElementExtensions.cs index 93d903baac..3649127917 100644 --- a/test/OpenApiTests/JsonElementExtensions.cs +++ b/test/OpenApiTests/JsonElementExtensions.cs @@ -41,7 +41,7 @@ public static SchemaReferenceIdContainer ShouldBeSchemaReferenceId(this JsonElem return new SchemaReferenceIdContainer(value); } - public static string GetSchemaReferenceId(this JsonElement source) + private static string GetSchemaReferenceId(this JsonElement source) { source.ValueKind.Should().Be(JsonValueKind.String); @@ -98,4 +98,3 @@ public void ContainProperty(string propertyName) } } } - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/HenHouse.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/HenHouse.cs similarity index 96% rename from test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/HenHouse.cs rename to test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/HenHouse.cs index b148444378..979b029717 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/HenHouse.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/HenHouse.cs @@ -5,7 +5,7 @@ using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; [UsedImplicitly(ImplicitUseTargetFlags.Members)] [Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs index 0149a07e32..0b88c3f88b 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs @@ -17,6 +17,7 @@ public ModelStateValidationDisabledTests( _testContext = testContext; testContext.UseController(); + testContext.UseController(); } [Theory] @@ -64,4 +65,48 @@ public async Task Schema_for_attributes_in_PATCH_request_should_have_no_required // Assert document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); } + + [Theory] + [InlineData("firstChicken")] + [InlineData("chickensReadyForLaying")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => + { + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredAttributes.Should().Contain(propertyName); + }); + } + + [Theory] + [InlineData("oldestChicken")] + [InlineData("allChickens")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain(propertyName); + }); + } + + [Fact] + public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.henHouseRelationshipsInPatchRequest.required"); + } } diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs index e200fe365c..bd31a1f25a 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs @@ -16,6 +16,7 @@ public ModelStateValidationEnabledTests( _testContext = testContext; testContext.UseController(); + testContext.UseController(); } [Theory] @@ -63,4 +64,48 @@ public async Task Schema_for_attributes_in_PATCH_request_should_have_no_required // Assert document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); } + + [Theory] + [InlineData("firstChicken")] + [InlineData("chickensReadyForLaying")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => + { + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredAttributes.Should().Contain(propertyName); + }); + } + + [Theory] + [InlineData("oldestChicken")] + [InlineData("allChickens")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain(propertyName); + }); + } + + [Fact] + public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.henHouseRelationshipsInPatchRequest.required"); + } } diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs index 7d79bf9e6d..dca51da02b 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs @@ -22,7 +22,7 @@ public NullabilityTests(OpenApiTestContext + { + schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + }); + }); + } + + [Theory] + [InlineData("allChickens")] + [InlineData("firstChicken")] + [InlineData("chickensReadyForLaying")] + public async Task Data_property_in_schema_for_relationship_of_resource_should_not_be_nullable(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs index 57c3d96f17..a93f4bf779 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs @@ -1,6 +1,5 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -using OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; using TestBuildingBlocks; namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationDisabledTests.cs deleted file mode 100644 index b9052eda76..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationDisabledTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; - -public sealed class ModelStateValidationDisabledTests - : IClassFixture, NullableReferenceTypesDisabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> - _testContext; - - public ModelStateValidationDisabledTests( - OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - } - - [Theory] - [InlineData("firstChicken")] - [InlineData("chickensReadyForLaying")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredAttributes.Should().Contain(propertyName); - }); - } - - [Theory] - [InlineData("oldestChicken")] - [InlineData("allChickens")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain(propertyName); - }); - } - - [Fact] - public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.henHouseRelationshipsInPatchRequest.required"); - } -} - - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationEnabledTests.cs deleted file mode 100644 index 9bea8d4062..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/ModelStateValidationEnabledTests.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; - -public sealed class ModelStateValidationEnabledTests - : IClassFixture, NullableReferenceTypesDisabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; - - public ModelStateValidationEnabledTests( - OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - } - - [Theory] - [InlineData("firstChicken")] - [InlineData("chickensReadyForLaying")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredAttributes.Should().Contain(propertyName); - }); - } - - [Theory] - [InlineData("oldestChicken")] - [InlineData("allChickens")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain(propertyName); - }); - } - - [Fact] - public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.henHouseRelationshipsInPatchRequest.required"); - } -} - - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullabilityTests.cs deleted file mode 100644 index 544c1cd9ec..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/RelationshipsObject/NullabilityTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Text.Json; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled.RelationshipsObject; - -public sealed class NullabilityTests - : IClassFixture, NullableReferenceTypesDisabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; - - public NullabilityTests(OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - } - - [Theory] - [InlineData("oldestChicken")] - public async Task Property_in_schema_for_relationship_of_resource_should_be_nullable(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); - }); - }); - } - - [Theory] - [InlineData("allChickens")] - [InlineData("firstChicken")] - [InlineData("chickensReadyForLaying")] - public async Task Property_in_schema_for_relationship_of_resource_should_not_be_nullable(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } -} - - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs index f876351bec..3b5562a0b3 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs @@ -37,5 +37,3 @@ public sealed class Cow : Identifiable [Required] public bool? HasProducedMilk { get; set; } } - - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/CowStable.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/CowStable.cs similarity index 96% rename from test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/CowStable.cs rename to test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/CowStable.cs index c2f4ad9367..b267423501 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/CowStable.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/CowStable.cs @@ -3,7 +3,7 @@ using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; +namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; [UsedImplicitly(ImplicitUseTargetFlags.Members)] [Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs index 988fe06896..fb83c4ea5f 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs @@ -17,6 +17,7 @@ public ModelStateValidationDisabledTests( _testContext = testContext; testContext.UseController(); + testContext.UseController(); } [Theory] @@ -65,4 +66,50 @@ public async Task Schema_for_attributes_in_PATCH_request_should_have_no_required // Assert document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); } + + [Theory] + [InlineData("firstCow")] + [InlineData("allCows")] + [InlineData("favoriteCow")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredAttributes.Should().Contain(propertyName); + }); + } + + [Theory] + [InlineData("oldestCow")] + [InlineData("cowsReadyForMilking")] + [InlineData("albinoCow")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain(propertyName); + }); + } + + [Fact] + public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.cowStableRelationshipsInPatchRequest.required"); + } } diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs index 70e4b3f7a4..d45937a580 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs @@ -16,6 +16,7 @@ public ModelStateValidationEnabledTests( _testContext = testContext; testContext.UseController(); + testContext.UseController(); } [Theory] @@ -64,4 +65,50 @@ public async Task Schema_for_attributes_in_PATCH_request_should_have_no_required // Assert document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); } + + [Theory] + [InlineData("oldestCow")] + [InlineData("firstCow")] + [InlineData("allCows")] + [InlineData("favoriteCow")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredAttributes.Should().Contain(propertyName); + }); + } + + [Theory] + [InlineData("cowsReadyForMilking")] + [InlineData("albinoCow")] + public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain(propertyName); + }); + } + + [Fact] + public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.cowStableRelationshipsInPatchRequest.required"); + } } diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs index 99100b8353..5652c61f33 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs @@ -58,6 +58,42 @@ public async Task Property_in_schema_for_attribute_of_resource_should_not_be_nul }); }); } -} + [Theory] + [InlineData("albinoCow")] + public async Task Property_in_schema_for_relationship_of_resource_should_be_nullable(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + }); + }); + } + + [Theory] + [InlineData("oldestCow")] + [InlineData("firstCow")] + [InlineData("cowsReadyForMilking")] + [InlineData("allCows")] + [InlineData("favoriteCow")] + public async Task Data_property_in_schema_for_relationship_of_resource_should_not_be_nullable(string propertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs index 278349c29a..e83298f28d 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs @@ -1,6 +1,5 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -using OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; using TestBuildingBlocks; namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; @@ -41,4 +40,3 @@ protected override void OnModelCreating(ModelBuilder builder) base.OnModelCreating(builder); } } - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationDisabledTests.cs deleted file mode 100644 index f52097c764..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationDisabledTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; - -public sealed class ModelStateValidationDisabledTests - : IClassFixture, NullableReferenceTypesEnabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> - _testContext; - - public ModelStateValidationDisabledTests( - OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - } - - [Theory] - [InlineData("firstCow")] - [InlineData("allCows")] - [InlineData("favoriteCow")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredAttributes.Should().Contain(propertyName); - }); - } - - [Theory] - [InlineData("oldestCow")] - [InlineData("cowsReadyForMilking")] - [InlineData("albinoCow")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain(propertyName); - }); - } - - [Fact] - public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.cowStableRelationshipsInPatchRequest.required"); - } -} - - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationEnabledTests.cs deleted file mode 100644 index 946e41ea27..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/ModelStateValidationEnabledTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; - -public sealed class ModelStateValidationEnabledTests - : IClassFixture, NullableReferenceTypesEnabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; - - public ModelStateValidationEnabledTests( - OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - } - - [Theory] - [InlineData("oldestCow")] - [InlineData("firstCow")] - [InlineData("allCows")] - [InlineData("favoriteCow")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredAttributes.Should().Contain(propertyName); - }); - } - - [Theory] - [InlineData("cowsReadyForMilking")] - [InlineData("albinoCow")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain(propertyName); - }); - } - - [Fact] - public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.cowStableRelationshipsInPatchRequest.required"); - } -} - - diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullabilityTests.cs deleted file mode 100644 index ed599af06c..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/RelationshipsObject/NullabilityTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Text.Json; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled.RelationshipsObject; - -public sealed class NullabilityTests - : IClassFixture, NullableReferenceTypesEnabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; - - public NullabilityTests(OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - } - - [Theory] - [InlineData("albinoCow")] - public async Task Property_in_schema_for_relationship_of_resource_should_be_nullable(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); - }); - }); - } - - [Theory] - [InlineData("oldestCow")] - [InlineData("firstCow")] - [InlineData("cowsReadyForMilking")] - [InlineData("allCows")] - [InlineData("favoriteCow")] - public async Task Property_in_schema_for_relationship_of_resource_should_not_be_nullable(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } -} - - From 76e098aab019ec38bf82c6186fa33806138022fb Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Wed, 28 Dec 2022 23:08:51 +0100 Subject: [PATCH 20/26] Organise tests such that they map directly to the tables in #1231 and #1111 Organise tests such that they map directly to the tables in #1231 and #1111 --- appveyor.yml | 1 - .../FakeHttpClientWrapper.cs | 12 + test/OpenApiClientTests/ObjectExtensions.cs | 20 - .../OpenApiClientTests.csproj | 8 +- .../NullableReferenceTypesDisabledClient.cs | 2 +- .../NullabilityTests.cs | 4 +- .../NullableReferenceTypesDisabledFaker.cs | 124 +++ .../RequestTests.cs | 636 +++++++++++++ .../swagger.g.json | 0 .../NullableReferenceTypesEnabledClient.cs | 2 +- .../NullabilityTests.cs | 4 +- .../NullableReferenceTypesEnabledFaker.cs | 132 +++ .../RequestTests.cs | 724 +++++++++++++++ .../swagger.g.json | 0 .../AlternativeFormRequestTests.cs | 252 ----- .../RequestTests.cs | 873 ------------------ .../RequestTests.cs | 780 ---------------- test/OpenApiTests/OpenApiTests.csproj | 3 +- .../ModelStateValidationDisabledStartup.cs | 2 +- .../NullableReferenceTypesDisabled/Chicken.cs | 4 +- .../HenHouse.cs | 4 +- .../NullabilityTests.cs | 181 ++++ ...NullableReferenceTypesDisabledDbContext.cs | 2 +- .../ModelStateValidationDisabledTests.cs | 192 ++++ .../ModelStateValidationEnabledTests.cs | 191 ++++ .../NullableReferenceTypesEnabled/Cow.cs | 4 +- .../CowStable.cs | 4 +- .../NullabilityTests.cs | 245 +++++ .../NullableReferenceTypesEnabledDbContext.cs | 2 +- .../ModelStateValidationDisabledTests.cs | 252 +++++ .../ModelStateValidationEnabledTests.cs | 251 +++++ .../ModelStateValidationDisabledTests.cs | 112 --- .../ModelStateValidationEnabledTests.cs | 111 --- .../NullabilityTests.cs | 95 -- .../ModelStateValidationDisabledTests.cs | 115 --- .../ModelStateValidationEnabledTests.cs | 114 --- .../NullabilityTests.cs | 99 -- .../JsonElementExtensions.cs | 15 +- .../TestBuildingBlocks.csproj | 1 + 39 files changed, 2973 insertions(+), 2600 deletions(-) delete mode 100644 test/OpenApiClientTests/ObjectExtensions.cs rename test/OpenApiClientTests/{SchemaProperties => ResourceFieldsValidation}/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs (77%) rename test/OpenApiClientTests/{SchemaProperties => ResourceFieldsValidation}/NullableReferenceTypesDisabled/NullabilityTests.cs (85%) create mode 100644 test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledFaker.cs create mode 100644 test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/RequestTests.cs rename test/OpenApiClientTests/{SchemaProperties => ResourceFieldsValidation}/NullableReferenceTypesDisabled/swagger.g.json (100%) rename test/OpenApiClientTests/{SchemaProperties => ResourceFieldsValidation}/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs (77%) rename test/OpenApiClientTests/{SchemaProperties => ResourceFieldsValidation}/NullableReferenceTypesEnabled/NullabilityTests.cs (86%) create mode 100644 test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledFaker.cs create mode 100644 test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/RequestTests.cs rename test/OpenApiClientTests/{SchemaProperties => ResourceFieldsValidation}/NullableReferenceTypesEnabled/swagger.g.json (100%) delete mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/AlternativeFormRequestTests.cs delete mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs delete mode 100644 test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs rename test/OpenApiTests/{SchemaProperties => ResourceFieldsValidation}/ModelStateValidationDisabledStartup.cs (90%) rename test/OpenApiTests/{SchemaProperties => ResourceFieldsValidation}/NullableReferenceTypesDisabled/Chicken.cs (80%) rename test/OpenApiTests/{SchemaProperties => ResourceFieldsValidation}/NullableReferenceTypesDisabled/HenHouse.cs (80%) create mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs rename test/OpenApiTests/{SchemaProperties => ResourceFieldsValidation}/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs (92%) create mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationDisabledTests.cs create mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationEnabledTests.cs rename test/OpenApiTests/{SchemaProperties => ResourceFieldsValidation}/NullableReferenceTypesEnabled/Cow.cs (83%) rename test/OpenApiTests/{SchemaProperties => ResourceFieldsValidation}/NullableReferenceTypesEnabled/CowStable.cs (82%) create mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs rename test/OpenApiTests/{SchemaProperties => ResourceFieldsValidation}/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs (93%) create mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationDisabledTests.cs create mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationEnabledTests.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs delete mode 100644 test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs rename test/{OpenApiTests => TestBuildingBlocks}/JsonElementExtensions.cs (87%) diff --git a/appveyor.yml b/appveyor.yml index bbd35ed160..433d391b16 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,7 +14,6 @@ environment: branches: only: - - openapi-required-and-nullable-properties # TODO: remove - master - openapi - develop diff --git a/test/OpenApiClientTests/FakeHttpClientWrapper.cs b/test/OpenApiClientTests/FakeHttpClientWrapper.cs index 222650ac2d..8226a4e496 100644 --- a/test/OpenApiClientTests/FakeHttpClientWrapper.cs +++ b/test/OpenApiClientTests/FakeHttpClientWrapper.cs @@ -1,6 +1,7 @@ using System.Net; using System.Net.Http.Headers; using System.Text; +using System.Text.Json; using JsonApiDotNetCore.OpenApi.Client; namespace OpenApiClientTests; @@ -22,6 +23,17 @@ private FakeHttpClientWrapper(HttpClient httpClient, FakeHttpMessageHandler hand _handler = handler; } + public JsonElement ParseRequestBody() + { + if (RequestBody == null) + { + throw new InvalidOperationException(); + } + + using JsonDocument jsonDocument = JsonDocument.Parse(RequestBody); + return jsonDocument.RootElement.Clone(); + } + public static FakeHttpClientWrapper Create(HttpStatusCode statusCode, string? responseBody) { HttpResponseMessage response = CreateResponse(statusCode, responseBody); diff --git a/test/OpenApiClientTests/ObjectExtensions.cs b/test/OpenApiClientTests/ObjectExtensions.cs deleted file mode 100644 index e3790eaa15..0000000000 --- a/test/OpenApiClientTests/ObjectExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Reflection; -using JsonApiDotNetCore.OpenApi.Client; - -namespace OpenApiClientTests; - -internal static class ObjectExtensions -{ - public static void SetPropertyToDefaultValue(this object target, string propertyName) - { - ArgumentGuard.NotNull(target); - ArgumentGuard.NotNull(propertyName); - - Type declaringType = target.GetType(); - - PropertyInfo property = declaringType.GetProperties().Single(property => property.Name == propertyName); - object? defaultValue = declaringType.IsValueType ? Activator.CreateInstance(declaringType) : null; - - property.SetValue(target, defaultValue); - } -} diff --git a/test/OpenApiClientTests/OpenApiClientTests.csproj b/test/OpenApiClientTests/OpenApiClientTests.csproj index badeeaeae7..4e726ad900 100644 --- a/test/OpenApiClientTests/OpenApiClientTests.csproj +++ b/test/OpenApiClientTests/OpenApiClientTests.csproj @@ -52,15 +52,15 @@ NSwagCSharp /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions - - OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.GeneratedCode + + OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.GeneratedCode NullableReferenceTypesEnabledClient NullableReferenceTypesEnabledClient.cs NSwagCSharp /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true - - OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode + + OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.GeneratedCode NullableReferenceTypesDisabledClient NullableReferenceTypesDisabledClient.cs NSwagCSharp diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs similarity index 77% rename from test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs rename to test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs index c1a4eebab5..8c6b98a237 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs +++ b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs @@ -1,7 +1,7 @@ using JsonApiDotNetCore.OpenApi.Client; using Newtonsoft.Json; -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; +namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.GeneratedCode; internal partial class NullableReferenceTypesDisabledClient : JsonApiClient { diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs similarity index 85% rename from test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs rename to test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs index 76c80cbf73..a979e26901 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs +++ b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs @@ -1,9 +1,9 @@ using System.Reflection; using FluentAssertions; -using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; +using OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.GeneratedCode; using Xunit; -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; +namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; public sealed class NullabilityTests { diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledFaker.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledFaker.cs new file mode 100644 index 0000000000..1f63b62ff9 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledFaker.cs @@ -0,0 +1,124 @@ +using Bogus; +using OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.GeneratedCode; + +namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; + +// @formatter:wrap_chained_method_calls chop_always +// @formatter:keep_existing_linebreaks true + +internal sealed class NullableReferenceTypesDisabledFaker +{ + private readonly Lazy> _lazyHenHousePostRequestDocumentFaker; + private readonly Lazy> _lazyHenHousePatchRequestDocumentFaker; + + private readonly Lazy> _lazyChickenPostRequestDocumentFaker = new(() => + { + Faker attributesInPostRequestFaker = new Faker() + .RuleFor(attributes => attributes.Name, faker => faker.Name.FirstName()) + .RuleFor(attributes => attributes.NameOfCurrentFarm, faker => faker.Company.CompanyName()) + .RuleFor(attributes => attributes.Age, faker => faker.Random.Int(1, 20)) + .RuleFor(attributes => attributes.Weight, faker => faker.Random.Int(20, 50)) + .RuleFor(attributes => attributes.TimeAtCurrentFarmInDays, faker => faker.Random.Int(1, 356)) + .RuleFor(attributes => attributes.HasProducedEggs, _ => true); + + Faker dataInPostRequestFaker = new Faker() + .RuleFor(data => data.Attributes, _ => attributesInPostRequestFaker.Generate()); + + return new Faker() + .RuleFor(document => document.Data, _ => dataInPostRequestFaker.Generate()); + }); + + private readonly Lazy> _lazyChickenPatchRequestDocumentFaker = new(() => + { + Faker attributesInPatchRequestFaker = new Faker() + .RuleFor(attributes => attributes.Name, faker => faker.Name.FirstName()) + .RuleFor(attributes => attributes.NameOfCurrentFarm, faker => faker.Company.CompanyName()) + .RuleFor(attributes => attributes.Age, faker => faker.Random.Int(1, 20)) + .RuleFor(attributes => attributes.Weight, faker => faker.Random.Int(20, 50)) + .RuleFor(attributes => attributes.TimeAtCurrentFarmInDays, faker => faker.Random.Int(1, 356)) + .RuleFor(attributes => attributes.HasProducedEggs, _ => true); + + Faker dataInPatchRequestFaker = new Faker() + // @formatter:wrap_chained_method_calls chop_if_long + .RuleFor(data => data.Id, faker => faker.Random.Int(1, 100).ToString()) + // @formatter:wrap_chained_method_calls restore + .RuleFor(data => data.Attributes, _ => attributesInPatchRequestFaker.Generate()); + + return new Faker() + .RuleFor(document => document.Data, _ => dataInPatchRequestFaker.Generate()); + }); + + private readonly Lazy> _lazyToOneChickenInRequestFaker = new(() => + new Faker() + .RuleFor(relationship => relationship.Data, faker => new ChickenIdentifier + { + // @formatter:wrap_chained_method_calls chop_if_long + Id = faker.Random.Int(1, 100).ToString() + // @formatter:wrap_chained_method_calls restore + })); + + private readonly Lazy> _lazyNullableToOneChickenInRequestFaker = new(() => + new Faker() + .RuleFor(relationship => relationship.Data, faker => new ChickenIdentifier + { + // @formatter:wrap_chained_method_calls chop_if_long + Id = faker.Random.Int(1, 100).ToString() + // @formatter:wrap_chained_method_calls restore + })); + + private readonly Lazy> _lazyToManyChickenInRequestFaker = new(() => + new Faker() + .RuleFor(relationship => relationship.Data, faker => new List + { + new() + { + // @formatter:wrap_chained_method_calls chop_if_long + Id = faker.Random.Int(1, 100).ToString() + // @formatter:wrap_chained_method_calls restore + } + })); + + public Faker ChickenPostRequestDocument => _lazyChickenPostRequestDocumentFaker.Value; + public Faker ChickenPatchRequestDocument => _lazyChickenPatchRequestDocumentFaker.Value; + public Faker HenHousePostRequestDocument => _lazyHenHousePostRequestDocumentFaker.Value; + public Faker HenHousePatchRequestDocument => _lazyHenHousePatchRequestDocumentFaker.Value; + + public NullableReferenceTypesDisabledFaker() + { + _lazyHenHousePostRequestDocumentFaker = new Lazy>(HenHousePostRequestDocumentFakerFactory); + _lazyHenHousePatchRequestDocumentFaker = new Lazy>(HenHousePatchRequestDocumentFakerFactory); + } + + private Faker HenHousePostRequestDocumentFakerFactory() + { + Faker relationshipsInPostRequestFaker = new Faker() + .RuleFor(relationships => relationships.OldestChicken, _ => _lazyNullableToOneChickenInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.FirstChicken, _ => _lazyToOneChickenInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.ChickensReadyForLaying, _ => _lazyToManyChickenInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.AllChickens, _ => _lazyToManyChickenInRequestFaker.Value.Generate()); + + Faker dataInPostRequestFaker = new Faker() + .RuleFor(data => data.Relationships, _ => relationshipsInPostRequestFaker.Generate()); + + return new Faker() + .RuleFor(document => document.Data, _ => dataInPostRequestFaker.Generate()); + } + + private Faker HenHousePatchRequestDocumentFakerFactory() + { + Faker relationshipsInPatchRequestFaker = new Faker() + .RuleFor(relationships => relationships.OldestChicken, _ => _lazyNullableToOneChickenInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.FirstChicken, _ => _lazyToOneChickenInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.ChickensReadyForLaying, _ => _lazyToManyChickenInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.AllChickens, _ => _lazyToManyChickenInRequestFaker.Value.Generate()); + + Faker dataInPatchRequestFaker = new Faker() + // @formatter:wrap_chained_method_calls chop_if_long + .RuleFor(data => data.Id, faker => faker.Random.Int(1, 100).ToString()) + // @formatter:wrap_chained_method_calls restore + .RuleFor(data => data.Relationships, _ => relationshipsInPatchRequestFaker.Generate()); + + return new Faker() + .RuleFor(document => document.Data, _ => dataInPatchRequestFaker.Generate()); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/RequestTests.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/RequestTests.cs new file mode 100644 index 0000000000..35499a2cc0 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/RequestTests.cs @@ -0,0 +1,636 @@ +using System.Net; +using System.Text.Json; +using FluentAssertions; +using FluentAssertions.Specialized; +using JsonApiDotNetCore.Middleware; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; +using OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; + +public sealed class RequestTests +{ + private readonly NullableReferenceTypesDisabledFaker _fakers = new(); + + [Fact] + public async Task Can_clear_reference_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); + requestDocument.Data.Attributes.Name = null; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + chicken => chicken.Name)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath("name").With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null)); + }); + } + + [Fact] + public async Task Can_exclude_reference_type_attribute_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); + requestDocument.Data.Attributes.Name = default; + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath("name"); + }); + } + + [Fact] + public async Task Cannot_clear_required_reference_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); + requestDocument.Data.Attributes.NameOfCurrentFarm = default; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'nameOfCurrentFarm'. Property requires a value. Path 'data.attributes'."); + } + + [Fact] + public async Task Cannot_exclude_required_reference_type_attribute_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); + requestDocument.Data.Attributes.NameOfCurrentFarm = default!; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'nameOfCurrentFarm' must have a value because it is required. Path 'data.attributes'."); + } + } + + [Fact] + public async Task Can_set_default_value_to_value_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); + requestDocument.Data.Attributes.Age = default; + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath("age").With(attribute => attribute.ShouldBeInteger(0)); + }); + } + + [Fact] + public async Task Can_exclude_value_type_attribute_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); + requestDocument.Data.Attributes.Age = default; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath("age"); + }); + } + + [Fact] + public async Task Can_set_default_value_to_required_value_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); + requestDocument.Data.Attributes.Weight = default; + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath("weight").With(attribute => attribute.ShouldBeInteger(0)); + }); + } + + [Fact] + public async Task Cannot_exclude_required_value_type_attribute_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); + requestDocument.Data.Attributes.Weight = default; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'weight' must have a value because it is required. Path 'data.attributes'."); + } + } + + [Fact] + public async Task Can_clear_nullable_value_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); + requestDocument.Data.Attributes.TimeAtCurrentFarmInDays = null; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + chicken => chicken.TimeAtCurrentFarmInDays)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath("timeAtCurrentFarmInDays").With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null)); + }); + } + + [Fact] + public async Task Can_exclude_nullable_value_type_attribute_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); + requestDocument.Data.Attributes.TimeAtCurrentFarmInDays = default; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath("timeAtCurrentFarmInDays"); + }); + } + + [Fact] + public async Task Cannot_exclude_required_nullable_value_type_attribute_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); + requestDocument.Data.Attributes.HasProducedEggs = default; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'hasProducedEggs' must have a value because it is required. Path 'data.attributes'."); + } + } + + [Fact] + public async Task Can_set_default_value_to_required_nullable_value_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); + requestDocument.Data.Attributes.HasProducedEggs = default; + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath("hasProducedEggs").With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.False)); + }); + } + + [Fact] + public async Task Can_clear_has_one_relationship() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); + requestDocument.Data.Relationships.OldestChicken.Data = null; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/henHouses"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships.oldestChicken.data").With(relationshipDataObject => + { + relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null); + }); + } + + [Fact] + public async Task Can_exclude_has_one_relationship_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); + requestDocument.Data.Relationships.OldestChicken = default!; + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/henHouses"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath("oldestChicken"); + }); + } + + [Fact] + public async Task Cannot_exclude_required_has_one_relationship_without_document_registration_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); + requestDocument.Data.Relationships.FirstChicken = default!; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'firstChicken'. Property requires a value. Path 'data.relationships'."); + } + + [Fact] + public async Task Cannot_exclude_required_has_one_relationship_with_document_registration_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); + requestDocument.Data.Relationships.FirstChicken = default!; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'firstChicken' must have a value because it is required. Path 'data.relationships'."); + } + } + + [Fact] + public async Task Can_exclude_has_many_relationship_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); + requestDocument.Data.Relationships.AllChickens = default!; + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/henHouses"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath("allChickens"); + }); + } + + [Fact] + public async Task Cannot_exclude_required_has_many_relationship_with_document_registration_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); + requestDocument.Data.Relationships.ChickensReadyForLaying = default!; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'chickensReadyForLaying' must have a value because it is required. Path 'data.relationships'."); + } + } + + [Fact] + public async Task Cannot_exclude_required_has_many_relationship_without_document_registration_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); + requestDocument.Data.Relationships.ChickensReadyForLaying = default!; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'chickensReadyForLaying'. Property requires a value. Path 'data.relationships'."); + } + + [Fact] + public async Task Cannot_exclude_id_when_performing_PATCH() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPatchRequestDocument requestDocument = _fakers.ChickenPatchRequestDocument.Generate(); + requestDocument.Data.Id = default!; + + // Act + Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchChickenAsync(999, requestDocument)); + + // Assert + await action.Should().ThrowAsync(); + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'id'. Property requires a value. Path 'data'."); + } + + [Fact] + public async Task Attributes_required_in_POST_request_are_not_required_in_PATCH_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + ChickenPatchRequestDocument requestDocument = _fakers.ChickenPatchRequestDocument.Generate(); + requestDocument.Data.Attributes.NameOfCurrentFarm = default!; + requestDocument.Data.Attributes.Weight = default!; + requestDocument.Data.Attributes.HasProducedEggs = default!; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PatchChickenAsync(int.Parse(requestDocument.Data.Id), requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Patch); + wrapper.Request.RequestUri.Should().Be($"http://localhost/chickens/{int.Parse(requestDocument.Data.Id)}"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath("nameOfCurrentFarm"); + attributesObject.ShouldNotContainPath("weight"); + attributesObject.ShouldNotContainPath("hasProducedMilk"); + }); + } + + [Fact] + public async Task Relationships_required_in_POST_request_are_not_required_in_PATCH_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); + + HenHousePatchRequestDocument requestDocument = _fakers.HenHousePatchRequestDocument.Generate(); + requestDocument.Data.Relationships.OldestChicken = default!; + requestDocument.Data.Relationships.FirstChicken = default!; + requestDocument.Data.Relationships.ChickensReadyForLaying = default!; + requestDocument.Data.Relationships.AllChickens = default!; + + await ApiResponse.TranslateAsync(async () => await apiClient.PatchHenHouseAsync(int.Parse(requestDocument.Data.Id), requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Patch); + wrapper.Request.RequestUri.Should().Be($"http://localhost/henHouses/{int.Parse(requestDocument.Data.Id)}"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath("oldestChicken"); + relationshipsObject.ShouldNotContainPath("firstChicken"); + relationshipsObject.ShouldNotContainPath("favoriteChicken"); + relationshipsObject.ShouldNotContainPath("allChickens"); + }); + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/swagger.g.json similarity index 100% rename from test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/swagger.g.json rename to test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/swagger.g.json diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs similarity index 77% rename from test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs rename to test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs index 17b822f56d..aa15aa3c91 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs +++ b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs @@ -1,7 +1,7 @@ using JsonApiDotNetCore.OpenApi.Client; using Newtonsoft.Json; -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.GeneratedCode; +namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.GeneratedCode; internal partial class NullableReferenceTypesEnabledClient : JsonApiClient { diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs similarity index 86% rename from test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs rename to test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs index d351867b8b..e3d01f54d4 100644 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs +++ b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs @@ -1,9 +1,9 @@ using System.Reflection; using FluentAssertions; -using OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.GeneratedCode; +using OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.GeneratedCode; using Xunit; -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled; +namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; public sealed class NullabilityTests { diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledFaker.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledFaker.cs new file mode 100644 index 0000000000..c999cecef2 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledFaker.cs @@ -0,0 +1,132 @@ +using Bogus; +using OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.GeneratedCode; + +namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; + +// @formatter:wrap_chained_method_calls chop_always +// @formatter:keep_existing_linebreaks true + +internal sealed class NullableReferenceTypesEnabledFaker +{ + private readonly Lazy> _lazyCowStablePostRequestDocumentFaker; + private readonly Lazy> _lazyCowStablePatchRequestDocumentFaker; + + private readonly Lazy> _lazyCowPostRequestDocumentFaker = new(() => + { + Faker attributesInPostRequestFaker = new Faker() + .RuleFor(attributes => attributes.Name, faker => faker.Name.FirstName()) + .RuleFor(attributes => attributes.NameOfCurrentFarm, faker => faker.Company.CompanyName()) + .RuleFor(attributes => attributes.NameOfPreviousFarm, faker => faker.Company.CompanyName()) + .RuleFor(attributes => attributes.Nickname, faker => faker.Internet.UserName()) + .RuleFor(attributes => attributes.Age, faker => faker.Random.Int(1, 20)) + .RuleFor(attributes => attributes.Weight, faker => faker.Random.Int(20, 50)) + .RuleFor(attributes => attributes.TimeAtCurrentFarmInDays, faker => faker.Random.Int(1, 356)) + .RuleFor(attributes => attributes.HasProducedMilk, _ => true); + + Faker dataInPostRequestFaker = new Faker() + .RuleFor(data => data.Attributes, _ => attributesInPostRequestFaker.Generate()); + + return new Faker() + .RuleFor(document => document.Data, _ => dataInPostRequestFaker.Generate()); + }); + + private readonly Lazy> _lazyCowPatchRequestDocumentFaker = new(() => + { + Faker attributesInPatchRequestFaker = new Faker() + .RuleFor(attributes => attributes.Name, faker => faker.Name.FirstName()) + .RuleFor(attributes => attributes.NameOfCurrentFarm, faker => faker.Company.CompanyName()) + .RuleFor(attributes => attributes.NameOfPreviousFarm, faker => faker.Company.CompanyName()) + .RuleFor(attributes => attributes.Nickname, faker => faker.Internet.UserName()) + .RuleFor(attributes => attributes.Age, faker => faker.Random.Int(1, 20)) + .RuleFor(attributes => attributes.Weight, faker => faker.Random.Int(20, 50)) + .RuleFor(attributes => attributes.TimeAtCurrentFarmInDays, faker => faker.Random.Int(1, 356)) + .RuleFor(attributes => attributes.HasProducedMilk, _ => true); + + Faker dataInPatchRequestFaker = new Faker() + // @formatter:wrap_chained_method_calls chop_if_long + .RuleFor(data => data.Id, faker => faker.Random.Int(1, 100).ToString()) + // @formatter:wrap_chained_method_calls restore + .RuleFor(data => data.Attributes, _ => attributesInPatchRequestFaker.Generate()); + + return new Faker() + .RuleFor(document => document.Data, _ => dataInPatchRequestFaker.Generate()); + }); + + private readonly Lazy> _lazyToOneCowInRequestFaker = new(() => + new Faker() + .RuleFor(relationship => relationship.Data, faker => new CowIdentifier + { + // @formatter:wrap_chained_method_calls chop_if_long + Id = faker.Random.Int(1, 100).ToString() + // @formatter:wrap_chained_method_calls restore + })); + + private readonly Lazy> _lazyNullableToOneCowInRequestFaker = new(() => + new Faker() + .RuleFor(relationship => relationship.Data, faker => new CowIdentifier + { + // @formatter:wrap_chained_method_calls chop_if_long + Id = faker.Random.Int(1, 100).ToString() + // @formatter:wrap_chained_method_calls restore + })); + + private readonly Lazy> _lazyToManyCowInRequestFaker = new(() => + new Faker() + .RuleFor(relationship => relationship.Data, faker => new List + { + new() + { + // @formatter:wrap_chained_method_calls chop_if_long + Id = faker.Random.Int(1, 100).ToString() + // @formatter:wrap_chained_method_calls restore + } + })); + + public Faker CowPostRequestDocument => _lazyCowPostRequestDocumentFaker.Value; + public Faker CowPatchRequestDocument => _lazyCowPatchRequestDocumentFaker.Value; + public Faker CowStablePostRequestDocument => _lazyCowStablePostRequestDocumentFaker.Value; + public Faker CowStablePatchRequestDocument => _lazyCowStablePatchRequestDocumentFaker.Value; + + public NullableReferenceTypesEnabledFaker() + { + _lazyCowStablePostRequestDocumentFaker = new Lazy>(CreateCowStablePostRequestDocumentFaker); + _lazyCowStablePatchRequestDocumentFaker = new Lazy>(CreateCowStablePatchRequestDocumentFaker); + } + + private Faker CreateCowStablePostRequestDocumentFaker() + { + Faker relationshipsInPostRequestFaker = new Faker() + .RuleFor(relationships => relationships.OldestCow, _ => _lazyToOneCowInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.FirstCow, _ => _lazyToOneCowInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.AlbinoCow, _ => _lazyNullableToOneCowInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.FavoriteCow, _ => _lazyToOneCowInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.CowsReadyForMilking, _ => _lazyToManyCowInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.AllCows, _ => _lazyToManyCowInRequestFaker.Value.Generate()); + + Faker dataInPostRequestFaker = new Faker() + .RuleFor(data => data.Relationships, _ => relationshipsInPostRequestFaker.Generate()); + + return new Faker() + .RuleFor(document => document.Data, _ => dataInPostRequestFaker.Generate()); + } + + private Faker CreateCowStablePatchRequestDocumentFaker() + { + Faker relationshipsInPatchRequestFaker = new Faker() + .RuleFor(relationships => relationships.OldestCow, _ => _lazyToOneCowInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.FirstCow, _ => _lazyToOneCowInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.AlbinoCow, _ => _lazyNullableToOneCowInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.FavoriteCow, _ => _lazyToOneCowInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.CowsReadyForMilking, _ => _lazyToManyCowInRequestFaker.Value.Generate()) + .RuleFor(relationships => relationships.AllCows, _ => _lazyToManyCowInRequestFaker.Value.Generate()); + + Faker dataInPatchRequestFaker = new Faker() + // @formatter:wrap_chained_method_calls chop_if_long + .RuleFor(data => data.Id, faker => faker.Random.Int(1, 100).ToString()) + // @formatter:wrap_chained_method_calls restore + .RuleFor(data => data.Relationships, _ => relationshipsInPatchRequestFaker.Generate()); + + return new Faker() + .RuleFor(document => document.Data, _ => dataInPatchRequestFaker.Generate()); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/RequestTests.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/RequestTests.cs new file mode 100644 index 0000000000..2a509fef2a --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/RequestTests.cs @@ -0,0 +1,724 @@ +using System.Net; +using System.Text.Json; +using FluentAssertions; +using FluentAssertions.Specialized; +using JsonApiDotNetCore.Middleware; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; +using OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; + +public sealed class RequestTests +{ + private readonly NullableReferenceTypesEnabledFaker _fakers = new(); + + [Fact] + public async Task Cannot_exclude_non_nullable_reference_type_attribute_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); + requestDocument.Data.Attributes.Name = default!; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'name' must have a value because it is required. Path 'data.attributes'."); + } + } + + [Fact] + public async Task Cannot_exclude_required_non_nullable_reference_type_attribute_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); + requestDocument.Data.Attributes.NameOfCurrentFarm = default!; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'nameOfCurrentFarm' must have a value because it is required. Path 'data.attributes'."); + } + } + + [Fact] + public async Task Can_exclude_nullable_reference_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); + requestDocument.Data.Attributes.NameOfPreviousFarm = default; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath("nameOfPreviousFarm"); + }); + } + + [Fact] + public async Task Cannot_exclude_required_nullable_reference_type_attribute_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); + requestDocument.Data.Attributes.Nickname = default!; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'nickname' must have a value because it is required. Path 'data.attributes'."); + } + } + + [Fact] + public async Task Can_set_default_value_to_value_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); + requestDocument.Data.Attributes.Age = default; + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath("age").With(attribute => attribute.ShouldBeInteger(0)); + }); + } + + [Fact] + public async Task Can_exclude_value_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); + requestDocument.Data.Attributes.Age = default; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath("age"); + }); + } + + [Fact] + public async Task Can_set_default_value_to_required_value_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); + requestDocument.Data.Attributes.Weight = default; + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath("weight").With(attribute => attribute.ShouldBeInteger(0)); + }); + } + + [Fact] + public async Task Cannot_exclude_required_value_type_attribute_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); + requestDocument.Data.Attributes.Weight = default; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'weight' must have a value because it is required. Path 'data.attributes'."); + } + } + + [Fact] + public async Task Can_clear_nullable_value_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); + requestDocument.Data.Attributes.TimeAtCurrentFarmInDays = null; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + cow => cow.TimeAtCurrentFarmInDays)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath("timeAtCurrentFarmInDays").With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null)); + }); + } + + [Fact] + public async Task Can_exclude_nullable_value_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); + requestDocument.Data.Attributes.TimeAtCurrentFarmInDays = default; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath("timeAtCurrentFarmInDays"); + }); + } + + [Fact] + public async Task Can_set_default_value_to_required_nullable_value_type_attribute() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); + requestDocument.Data.Attributes.HasProducedMilk = default; + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath("hasProducedMilk").With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.False)); + }); + } + + [Fact] + public async Task Cannot_exclude_required_nullable_value_type_attribute_in_POST_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); + requestDocument.Data.Attributes.HasProducedMilk = default; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'hasProducedMilk' must have a value because it is required. Path 'data.attributes'."); + } + } + + [Fact] + public async Task Cannot_exclude_has_one_relationship_in_POST_request_with_document_registration() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); + requestDocument.Data.Relationships.OldestCow = default!; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'oldestCow' must have a value because it is required. Path 'data.relationships'."); + } + } + + [Fact] + public async Task Cannot_exclude_has_one_relationship_in_POST_request_without_document_registration() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); + requestDocument.Data.Relationships.OldestCow = default!; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'oldestCow'. Property requires a value. Path 'data.relationships'."); + } + + [Fact] + public async Task Cannot_exclude_required_has_one_relationship_in_POST_request_with_document_registration() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); + requestDocument.Data.Relationships.FirstCow = default!; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'firstCow' must have a value because it is required. Path 'data.relationships'."); + } + } + + [Fact] + public async Task Cannot_exclude_required_has_one_relationship_in_POST_request_without_document_registration() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); + requestDocument.Data.Relationships.FirstCow = default!; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'firstCow'. Property requires a value. Path 'data.relationships'."); + } + + [Fact] + public async Task Can_clear_nullable_has_one_relationship() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); + requestDocument.Data.Relationships.AlbinoCow.Data = null; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/cowStables"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships.albinoCow.data").With(relationshipDataObject => + { + relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null); + }); + } + + [Fact] + public async Task Can_exclude_nullable_has_one_relationship() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); + requestDocument.Data.Relationships.AlbinoCow = default!; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/cowStables"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath("albinoCow"); + }); + } + + [Fact] + public async Task Cannot_exclude_required_nullable_has_one_relationship_in_POST_request_with_document_registration() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); + requestDocument.Data.Relationships.FavoriteCow = default!; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'favoriteCow' must have a value because it is required. Path 'data.relationships'."); + } + } + + [Fact] + public async Task Cannot_exclude_required_nullable_has_one_relationship_in_POST_request_without_document_registration() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); + requestDocument.Data.Relationships.FavoriteCow = default!; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'favoriteCow'. Property requires a value. Path 'data.relationships'."); + } + + [Fact] + public async Task Can_exclude_has_many_relationship() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); + requestDocument.Data.Relationships.CowsReadyForMilking = default!; + + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Post); + wrapper.Request.RequestUri.Should().Be("http://localhost/cowStables"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath("cowsReadyForMilking"); + }); + } + + [Fact] + public async Task Cannot_exclude_required_has_many_relationship_in_POST_request_with_document_registration() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); + requestDocument.Data.Relationships.AllCows = default!; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Ignored property 'allCows' must have a value because it is required. Path 'data.relationships'."); + } + } + + [Fact] + public async Task Cannot_exclude_required_has_many_relationship_in_POST_request_without_document_registration() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); + requestDocument.Data.Relationships.AllCows = default!; + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'allCows'. Property requires a value. Path 'data.relationships'."); + } + + [Fact] + public async Task Cannot_exclude_id_when_performing_PATCH() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPatchRequestDocument requestDocument = _fakers.CowPatchRequestDocument.Generate(); + requestDocument.Data.Id = default!; + + // Act + Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowAsync(999, requestDocument)); + + // Assert + await action.Should().ThrowAsync(); + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'id'. Property requires a value. Path 'data'."); + } + + [Fact] + public async Task Attributes_required_in_POST_request_are_not_required_in_PATCH_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowPatchRequestDocument requestDocument = _fakers.CowPatchRequestDocument.Generate(); + requestDocument.Data.Attributes.Name = default!; + requestDocument.Data.Attributes.NameOfCurrentFarm = default!; + requestDocument.Data.Attributes.Nickname = default!; + requestDocument.Data.Attributes.Weight = default!; + requestDocument.Data.Attributes.HasProducedMilk = default!; + + using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowAsync(int.Parse(requestDocument.Data.Id), requestDocument)); + } + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Patch); + wrapper.Request.RequestUri.Should().Be($"http://localhost/cows/{int.Parse(requestDocument.Data.Id)}"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath("name"); + attributesObject.ShouldNotContainPath("nameOfCurrentFarm"); + attributesObject.ShouldNotContainPath("nickname"); + attributesObject.ShouldNotContainPath("weight"); + attributesObject.ShouldNotContainPath("hasProducedMilk"); + }); + } + + [Fact] + public async Task Relationships_required_in_POST_request_are_not_required_in_PATCH_request() + { + // Arrange + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); + + CowStablePatchRequestDocument requestDocument = _fakers.CowStablePatchRequestDocument.Generate(); + requestDocument.Data.Relationships.OldestCow = default!; + requestDocument.Data.Relationships.FirstCow = default!; + requestDocument.Data.Relationships.FavoriteCow = default!; + requestDocument.Data.Relationships.AllCows = default!; + + await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowStableAsync(int.Parse(requestDocument.Data.Id), requestDocument)); + + // Assert + wrapper.Request.ShouldNotBeNull(); + wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); + wrapper.Request.Method.Should().Be(HttpMethod.Patch); + wrapper.Request.RequestUri.Should().Be($"http://localhost/cowStables/{int.Parse(requestDocument.Data.Id)}"); + wrapper.Request.Content.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); + wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); + + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath("oldestCow"); + relationshipsObject.ShouldNotContainPath("firstCow"); + relationshipsObject.ShouldNotContainPath("favoriteCow"); + relationshipsObject.ShouldNotContainPath("allCows"); + }); + } +} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/swagger.g.json similarity index 100% rename from test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/swagger.g.json rename to test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/swagger.g.json diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/AlternativeFormRequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/AlternativeFormRequestTests.cs deleted file mode 100644 index daf1bd38b6..0000000000 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/AlternativeFormRequestTests.cs +++ /dev/null @@ -1,252 +0,0 @@ -using System.Net; -using FluentAssertions; -using JsonApiDotNetCore.Middleware; -using Microsoft.Net.Http.Headers; -using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; - -/// -/// Should consider if the shape of the two tests here is more favourable over the test with the same name in the RequestTests suite. The drawback of the -/// form here is that the expected json string is less easy to read. However the win is that this form allows us to run the tests in a [Theory]. This is -/// relevant because each of these properties represent unique test cases. In the other test form, it is not clear which properties are tested without. -/// For instance: here in Can_exclude_optional_relationships it is immediately clear that the properties we omit are those in the inline data. -/// -public sealed class AlternativeFormRequestTests -{ - private const string HenHouseUrl = "http://localhost/henHouses"; - - private readonly Dictionary _partials = new() - { - { - nameof(HenHouseRelationshipsInPostRequest.OldestChicken), @"""oldestChicken"": { - ""data"": { - ""type"": ""chickens"", - ""id"": ""1"" - } - }" - }, - { - nameof(HenHouseRelationshipsInPostRequest.FirstChicken), @"""firstChicken"": { - ""data"": { - ""type"": ""chickens"", - ""id"": ""1"" - } - }" - }, - { - nameof(HenHouseRelationshipsInPostRequest.AllChickens), @"""allChickens"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - }" - }, - - { - nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), @"""chickensReadyForLaying"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - }" - } - }; - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.OldestChicken))] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens))] - public async Task Can_exclude_optional_relationships(string propertyName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsObject = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - relationshipsObject.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsObject - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(HenHouseUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - string body = GetRelationshipsObjectWithSinglePropertyOmitted(propertyName); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""henHouses"", - ""relationships"": " + body + @" - } -}"); - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken))] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying))] - public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH(string propertyName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var relationshipsObject = new HenHouseRelationshipsInPatchRequest - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - relationshipsObject.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new HenHousePatchRequestDocument - { - Data = new HenHouseDataInPatchRequest - { - Id = "1", - Type = HenHouseResourceType.HenHouses, - Relationships = relationshipsObject - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PatchHenHouseAsync(1, requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be(HenHouseUrl + "/1"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - string serializedRelationshipsObject = GetRelationshipsObjectWithSinglePropertyOmitted(propertyName); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""henHouses"", - ""id"": ""1"", - ""relationships"": " + serializedRelationshipsObject + @" - } -}"); - } - - private string GetRelationshipsObjectWithSinglePropertyOmitted(string excludeProperty) - { - string partial = ""; - - foreach ((string key, string relationshipJsonPartial) in _partials) - { - if (excludeProperty == key) - { - continue; - } - - if (partial.Length > 0) - { - partial += ",\n "; - } - - partial += relationshipJsonPartial; - } - - return @"{ - " + partial + @" - }"; - } -} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs deleted file mode 100644 index 1ee3ee0fc3..0000000000 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs +++ /dev/null @@ -1,873 +0,0 @@ -using System.Net; -using System.Reflection; -using FluentAssertions; -using FluentAssertions.Specialized; -using JsonApiDotNetCore.Middleware; -using Microsoft.Net.Http.Headers; -using Newtonsoft.Json; -using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled; - -public sealed class RelationshipRequestTests -{ - private const string ChickenUrl = "http://localhost/chickens"; - private const string HenHouseUrl = "http://localhost/henHouses"; - - [Fact] - public async Task Can_exclude_optional_attributes() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new ChickenPostRequestDocument - { - Data = new ChickenDataInPostRequest - { - Attributes = new ChickenAttributesInPostRequest - { - NameOfCurrentFarm = "Cow and Chicken Farm", - Weight = 30, - HasProducedEggs = true - } - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(ChickenUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""chickens"", - ""attributes"": { - ""nameOfCurrentFarm"": ""Cow and Chicken Farm"", - ""weight"": 30, - ""hasProducedEggs"": true - } - } -}"); - } - - [Theory] - [InlineData(nameof(ChickenAttributesInResponse.NameOfCurrentFarm), "nameOfCurrentFarm")] - [InlineData(nameof(ChickenAttributesInResponse.Weight), "weight")] - [InlineData(nameof(ChickenAttributesInResponse.HasProducedEggs), "hasProducedEggs")] - public async Task Cannot_exclude_required_attribute_when_performing_POST(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var attributesInPostRequest = new ChickenAttributesInPostRequest - { - Name = "Chicken", - NameOfCurrentFarm = "Cow and Chicken Farm", - Age = 10, - Weight = 30, - TimeAtCurrentFarmInDays = 100, - HasProducedEggs = true - }; - - attributesInPostRequest.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new ChickenPostRequestDocument - { - Data = new ChickenDataInPostRequest - { - Attributes = attributesInPostRequest - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.attributes'."); - } - } - - [Fact] - public async Task Can_exclude_attributes_that_are_required_for_POST_when_performing_PATCH() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new ChickenPatchRequestDocument - { - Data = new ChickenDataInPatchRequest - { - Id = "1", - Attributes = new ChickenAttributesInPatchRequest - { - Name = "Chicken", - Age = 10, - TimeAtCurrentFarmInDays = 100 - } - } - }; - - // Act - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - await ApiResponse.TranslateAsync(async () => await apiClient.PatchChickenAsync(1, requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be(ChickenUrl + "/1"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""chickens"", - ""id"": ""1"", - ""attributes"": { - ""name"": ""Chicken"", - ""age"": 10, - ""timeAtCurrentFarmInDays"": 100 - } - } -}"); - } - - [Fact] - public async Task Cannot_exclude_id_when_performing_PATCH() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new ChickenPatchRequestDocument - { - Data = new ChickenDataInPatchRequest - { - Attributes = new ChickenAttributesInPatchRequest - { - Name = "Chicken", - NameOfCurrentFarm = "Cow and Chicken Farm", - Age = 10, - Weight = 30, - TimeAtCurrentFarmInDays = 100, - HasProducedEggs = true - } - } - }; - - // Act - Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchChickenAsync(1, requestDocument)); - - // Assert - await action.Should().ThrowAsync(); - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Cannot write a null value for property 'id'. Property requires a value. Path 'data'."); - } - - [Fact] - public async Task Can_clear_nullable_attributes() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new ChickenPostRequestDocument - { - Data = new ChickenDataInPostRequest - { - Attributes = new ChickenAttributesInPostRequest - { - Name = null, - TimeAtCurrentFarmInDays = null, - NameOfCurrentFarm = "Cow and Chicken Farm", - Age = 10, - Weight = 30, - HasProducedEggs = true - } - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, - chicken => chicken.Name, chicken => chicken.TimeAtCurrentFarmInDays)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(ChickenUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""chickens"", - ""attributes"": { - ""name"": null, - ""nameOfCurrentFarm"": ""Cow and Chicken Farm"", - ""age"": 10, - ""weight"": 30, - ""timeAtCurrentFarmInDays"": null, - ""hasProducedEggs"": true - } - } -}"); - } - - [Fact] - public async Task Cannot_clear_required_attribute_when_performing_POST() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new ChickenPostRequestDocument - { - Data = new ChickenDataInPostRequest - { - Attributes = new ChickenAttributesInPostRequest - { - Name = "Chicken", - NameOfCurrentFarm = null, - Age = 10, - Weight = 30, - TimeAtCurrentFarmInDays = 100, - HasProducedEggs = true - } - } - }; - - Func> action; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, - chicken => chicken.NameOfCurrentFarm)) - { - // Act - action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - } - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Cannot write a null value for property 'nameOfCurrentFarm'. Property requires a value. Path 'data.attributes'."); - } - - [Fact] - public async Task Can_set_default_value_to_ValueType_attributes() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new ChickenPostRequestDocument - { - Data = new ChickenDataInPostRequest - { - Attributes = new ChickenAttributesInPostRequest - { - Name = "Chicken", - NameOfCurrentFarm = "Cow and Chicken Farm", - TimeAtCurrentFarmInDays = 100 - } - } - }; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(ChickenUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""chickens"", - ""attributes"": { - ""name"": ""Chicken"", - ""nameOfCurrentFarm"": ""Cow and Chicken Farm"", - ""age"": 0, - ""weight"": 0, - ""timeAtCurrentFarmInDays"": 100, - ""hasProducedEggs"": false - } - } -}"); - } - - [Fact] - public async Task Can_exclude_optional_relationships() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = new HenHouseRelationshipsInPostRequest - { - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(HenHouseUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""henHouses"", - ""relationships"": { - ""firstChicken"": { - ""data"": { - ""type"": ""chickens"", - ""id"": ""1"" - } - }, - ""chickensReadyForLaying"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] - public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); - } - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] - public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Cannot write a null value for property '{jsonName}'. Property requires a value. Path 'data.relationships'."); - } - - [Fact] - public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new HenHousePatchRequestDocument - { - Data = new HenHouseDataInPatchRequest - { - Id = "1", - Type = HenHouseResourceType.HenHouses, - Relationships = new HenHouseRelationshipsInPatchRequest - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PatchHenHouseAsync(1, requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be(HenHouseUrl + "/1"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""henHouses"", - ""id"": ""1"", - ""relationships"": { - ""oldestChicken"": { - ""data"": { - ""type"": ""chickens"", - ""id"": ""1"" - } - }, - ""allChickens"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } - - [Fact] - public async Task Can_clear_nullable_relationship() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = new HenHouseRelationshipsInPostRequest - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = null - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(HenHouseUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""henHouses"", - ""relationships"": { - ""oldestChicken"": { - ""data"": null - }, - ""firstChicken"": { - ""data"": { - ""type"": ""chickens"", - ""id"": ""1"" - } - }, - ""allChickens"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - }, - ""chickensReadyForLaying"": { - ""data"": [ - { - ""type"": ""chickens"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens), "allChickens")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] - public async Task Cannot_clear_non_nullable_relationships_with_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - PropertyInfo relationshipToClearPropertyInfo = relationshipsInPostDocument.GetType().GetProperties().Single(property => property.Name == propertyName); - object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; - relationshipToClear.SetPropertyToDefaultValue("Data"); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - Func> action; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, - model => model.FirstChicken, model => model.AllChickens, model => model.ChickensReadyForLaying)) - { - // Act - action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - } - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); - } - - [Theory] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.FirstChicken), "firstChicken")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.AllChickens), "allChickens")] - [InlineData(nameof(HenHouseRelationshipsInPostRequest.ChickensReadyForLaying), "chickensReadyForLaying")] - public async Task Cannot_clear_non_nullable_relationships_without_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHouseRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestChicken = new NullableToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - FirstChicken = new ToOneChickenInRequest - { - Data = new ChickenIdentifier - { - Id = "1", - Type = ChickenResourceType.Chickens - } - }, - AllChickens = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - }, - ChickensReadyForLaying = new ToManyChickenInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = ChickenResourceType.Chickens - } - } - } - }; - - PropertyInfo relationshipToClearPropertyInfo = relationshipsInPostDocument.GetType().GetProperties().Single(property => property.Name == propertyName); - object relationshipToClear = relationshipToClearPropertyInfo.GetValue(relationshipsInPostDocument)!; - relationshipToClear.SetPropertyToDefaultValue("Data"); - - var requestDocument = new HenHousePostRequestDocument - { - Data = new HenHouseDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonName}'."); - } -} diff --git a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs deleted file mode 100644 index aef305b63d..0000000000 --- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs +++ /dev/null @@ -1,780 +0,0 @@ -using System.Net; -using FluentAssertions; -using FluentAssertions.Specialized; -using JsonApiDotNetCore.Middleware; -using Microsoft.Net.Http.Headers; -using Newtonsoft.Json; -using OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.GeneratedCode; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled; - -public sealed class RequestTests -{ - private const string CowUrl = "http://localhost/cows"; - private const string CowStableUrl = "http://localhost/cowStables"; - - [Fact] - public async Task Can_exclude_optional_attributes() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - var requestDocument = new CowPostRequestDocument - { - Data = new CowDataInPostRequest - { - Attributes = new CowAttributesInPostRequest - { - Name = "Cow", - NameOfCurrentFarm = "Cow and Chicken Farm", - Nickname = "Cow", - Weight = 30, - HasProducedMilk = true - } - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(CowUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""cows"", - ""attributes"": { - ""name"": ""Cow"", - ""nameOfCurrentFarm"": ""Cow and Chicken Farm"", - ""nickname"": ""Cow"", - ""weight"": 30, - ""hasProducedMilk"": true - } - } -}"); - } - - [Theory] - [InlineData(nameof(CowAttributesInResponse.Name), "name")] - [InlineData(nameof(CowAttributesInResponse.NameOfCurrentFarm), "nameOfCurrentFarm")] - [InlineData(nameof(CowAttributesInResponse.Nickname), "nickname")] - [InlineData(nameof(CowAttributesInResponse.Weight), "weight")] - [InlineData(nameof(CowAttributesInResponse.HasProducedMilk), "hasProducedMilk")] - public async Task Cannot_exclude_required_attribute_when_performing_POST(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - var attributesInPostRequest = new CowAttributesInPostRequest - { - Name = "Cow", - NameOfCurrentFarm = "Cow and Chicken Farm", - NameOfPreviousFarm = "Animal Farm", - Nickname = "Cow", - Age = 10, - Weight = 30, - TimeAtCurrentFarmInDays = 100, - HasProducedMilk = true - }; - - attributesInPostRequest.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new CowPostRequestDocument - { - Data = new CowDataInPostRequest - { - Attributes = attributesInPostRequest - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.attributes'."); - } - } - - [Fact] - public async Task Can_exclude_attributes_that_are_required_for_POST_when_performing_PATCH() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - var requestDocument = new CowPatchRequestDocument - { - Data = new CowDataInPatchRequest - { - Id = "1", - Attributes = new CowAttributesInPatchRequest - { - NameOfPreviousFarm = "Animal Farm", - Age = 10, - TimeAtCurrentFarmInDays = 100 - } - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowAsync(1, requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be(CowUrl + "/1"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""cows"", - ""id"": ""1"", - ""attributes"": { - ""nameOfPreviousFarm"": ""Animal Farm"", - ""age"": 10, - ""timeAtCurrentFarmInDays"": 100 - } - } -}"); - } - - [Fact] - public async Task Cannot_exclude_id_when_performing_PATCH() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - var requestDocument = new CowPatchRequestDocument - { - Data = new CowDataInPatchRequest - { - Attributes = new CowAttributesInPatchRequest - { - Name = "Cow", - NameOfCurrentFarm = "Cow and Chicken Farm", - NameOfPreviousFarm = "Animal Farm", - Nickname = "Cow", - Age = 10, - Weight = 30, - TimeAtCurrentFarmInDays = 100, - HasProducedMilk = true - } - } - }; - - // Act - Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowAsync(1, requestDocument)); - - // Assert - await action.Should().ThrowAsync(); - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Cannot write a null value for property 'id'. Property requires a value. Path 'data'."); - } - - [Fact] - public async Task Can_clear_nullable_attributes() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - var requestDocument = new CowPostRequestDocument - { - Data = new CowDataInPostRequest - { - Attributes = new CowAttributesInPostRequest - { - NameOfPreviousFarm = null, - TimeAtCurrentFarmInDays = null, - Name = "Cow", - NameOfCurrentFarm = "Cow and Chicken Farm", - Nickname = "Cow", - Age = 10, - Weight = 30, - HasProducedMilk = true - } - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, - cow => cow.NameOfPreviousFarm, cow => cow.TimeAtCurrentFarmInDays)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(CowUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""cows"", - ""attributes"": { - ""name"": ""Cow"", - ""nameOfCurrentFarm"": ""Cow and Chicken Farm"", - ""nameOfPreviousFarm"": null, - ""nickname"": ""Cow"", - ""age"": 10, - ""weight"": 30, - ""timeAtCurrentFarmInDays"": null, - ""hasProducedMilk"": true - } - } -}"); - } - - [Fact] - public async Task Can_set_default_value_to_ValueType_attributes() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - var requestDocument = new CowPostRequestDocument - { - Data = new CowDataInPostRequest - { - Attributes = new CowAttributesInPostRequest - { - Name = "Cow", - NameOfCurrentFarm = "Cow and Chicken Farm", - NameOfPreviousFarm = "Animal Farm", - Nickname = "Cow", - TimeAtCurrentFarmInDays = 100 - } - } - }; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(CowUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""cows"", - ""attributes"": { - ""name"": ""Cow"", - ""nameOfCurrentFarm"": ""Cow and Chicken Farm"", - ""nameOfPreviousFarm"": ""Animal Farm"", - ""nickname"": ""Cow"", - ""age"": 0, - ""weight"": 0, - ""timeAtCurrentFarmInDays"": 100, - ""hasProducedMilk"": false - } - } -}"); - } - - [Fact] - public async Task Can_exclude_optional_relationships() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - var requestDocument = new CowStablePostRequestDocument - { - Data = new CowStableDataInPostRequest - { - Relationships = new CowStableRelationshipsInPostRequest - { - OldestCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FirstCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FavoriteCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - AllCows = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(CowStableUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""cowStables"", - ""relationships"": { - ""oldestCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""firstCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""favoriteCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""allCows"": { - ""data"": [ - { - ""type"": ""cows"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } - - [Theory] - [InlineData(nameof(CowStableRelationshipsInPostRequest.OldestCow), "oldestCow")] - [InlineData(nameof(CowStableRelationshipsInPostRequest.FirstCow), "firstCow")] - [InlineData(nameof(CowStableRelationshipsInPostRequest.FavoriteCow), "favoriteCow")] - [InlineData(nameof(CowStableRelationshipsInPostRequest.AllCows), "allCows")] - public async Task Cannot_exclude_required_relationship_when_performing_POST_with_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStableRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FirstCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FavoriteCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - CowsReadyForMilking = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - }, - AllCows = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - } - }; - - relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new CowStablePostRequestDocument - { - Data = new CowStableDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Ignored property '{jsonName}' must have a value because it is required. Path 'data.relationships'."); - } - } - - [Theory] - [InlineData(nameof(CowStableRelationshipsInPostRequest.OldestCow), "oldestCow")] - [InlineData(nameof(CowStableRelationshipsInPostRequest.FirstCow), "firstCow")] - [InlineData(nameof(CowStableRelationshipsInPostRequest.FavoriteCow), "favoriteCow")] - [InlineData(nameof(CowStableRelationshipsInPostRequest.AllCows), "allCows")] - public async Task Cannot_exclude_required_relationship_when_performing_POST_without_document_registration(string propertyName, string jsonName) - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStableRelationshipsInPostRequest relationshipsInPostDocument = new() - { - OldestCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FirstCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - AlbinoCow = new NullableToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FavoriteCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - CowsReadyForMilking = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - }, - AllCows = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - } - }; - - relationshipsInPostDocument.SetPropertyToDefaultValue(propertyName); - - var requestDocument = new CowStablePostRequestDocument - { - Data = new CowStableDataInPostRequest - { - Relationships = relationshipsInPostDocument - } - }; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be($"Cannot write a null value for property '{jsonName}'. Property requires a value. Path 'data.relationships'."); - } - - [Fact] - public async Task Can_exclude_relationships_that_are_required_for_POST_when_performing_PATCH() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - var requestDocument = new CowStablePatchRequestDocument - { - Data = new CowStableDataInPatchRequest - { - Id = "1", - Type = CowStableResourceType.CowStables, - Relationships = new CowStableRelationshipsInPatchRequest - { - AlbinoCow = new NullableToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - CowsReadyForMilking = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowStableAsync(1, requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be(CowStableUrl + "/1"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""cowStables"", - ""id"": ""1"", - ""relationships"": { - ""albinoCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""cowsReadyForMilking"": { - ""data"": [ - { - ""type"": ""cows"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } - - [Fact] - public async Task Can_clear_nullable_relationship() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - var requestDocument = new CowStablePostRequestDocument - { - Data = new CowStableDataInPostRequest - { - Relationships = new CowStableRelationshipsInPostRequest - { - AlbinoCow = new NullableToOneCowInRequest - { - Data = null - }, - OldestCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FirstCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - FavoriteCow = new ToOneCowInRequest - { - Data = new CowIdentifier - { - Id = "1", - Type = CowResourceType.Cows - } - }, - CowsReadyForMilking = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - }, - AllCows = new ToManyCowInRequest - { - Data = new List - { - new() - { - Id = "1", - Type = CowResourceType.Cows - } - } - } - } - } - }; - - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be(CowStableUrl); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - wrapper.RequestBody.Should().BeJson(@"{ - ""data"": { - ""type"": ""cowStables"", - ""relationships"": { - ""oldestCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""firstCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""albinoCow"": { - ""data"": null - }, - ""favoriteCow"": { - ""data"": { - ""type"": ""cows"", - ""id"": ""1"" - } - }, - ""cowsReadyForMilking"": { - ""data"": [ - { - ""type"": ""cows"", - ""id"": ""1"" - } - ] - }, - ""allCows"": { - ""data"": [ - { - ""type"": ""cows"", - ""id"": ""1"" - } - ] - } - } - } -}"); - } -} diff --git a/test/OpenApiTests/OpenApiTests.csproj b/test/OpenApiTests/OpenApiTests.csproj index d6cb1d3002..bbd9209a7b 100644 --- a/test/OpenApiTests/OpenApiTests.csproj +++ b/test/OpenApiTests/OpenApiTests.csproj @@ -6,7 +6,7 @@ - + @@ -17,6 +17,5 @@ - diff --git a/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs b/test/OpenApiTests/ResourceFieldsValidation/ModelStateValidationDisabledStartup.cs similarity index 90% rename from test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs rename to test/OpenApiTests/ResourceFieldsValidation/ModelStateValidationDisabledStartup.cs index 1b4dfe7c62..f3e39495c3 100644 --- a/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs +++ b/test/OpenApiTests/ResourceFieldsValidation/ModelStateValidationDisabledStartup.cs @@ -2,7 +2,7 @@ using JsonApiDotNetCore.Configuration; using TestBuildingBlocks; -namespace OpenApiTests.SchemaProperties; +namespace OpenApiTests.ResourceFieldsValidation; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class ModelStateValidationDisabledStartup : OpenApiStartup diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/Chicken.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Chicken.cs similarity index 80% rename from test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/Chicken.cs rename to test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Chicken.cs index 4865d33af9..53c1a17f98 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/Chicken.cs +++ b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Chicken.cs @@ -5,10 +5,10 @@ using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; +namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] +[Resource(ControllerNamespace = "OpenApiTests.ResourceFieldsValidation")] public sealed class Chicken : Identifiable { [Attr] diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/HenHouse.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/HenHouse.cs similarity index 80% rename from test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/HenHouse.cs rename to test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/HenHouse.cs index 979b029717..8c24664eec 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/HenHouse.cs +++ b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/HenHouse.cs @@ -5,10 +5,10 @@ using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; +namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] +[Resource(ControllerNamespace = "OpenApiTests.ResourceFieldsValidation")] public sealed class HenHouse : Identifiable { [HasOne] diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs new file mode 100644 index 0000000000..4620ebd844 --- /dev/null +++ b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs @@ -0,0 +1,181 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; + +public sealed class NullabilityTests + : IClassFixture, NullableReferenceTypesDisabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; + + public NullabilityTests(OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.UseController(); + testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled"; + } + + [Fact] + public async Task Schema_property_is_nullable_for_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("name").With(schemaProperty => + { + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_required_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("nameOfCurrentFarm").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("age").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_required_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("weight").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_nullable_for_nullable_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("timeAtCurrentFarmInDays").With(schemaProperty => + { + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_required_nullable_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("hasProducedEggs").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_nullable_for_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("oldestChicken.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_required_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("firstChicken.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_has_many_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("chickensReadyForLaying.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_required_has_many_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("allChickens.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs similarity index 92% rename from test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs rename to test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs index a93f4bf779..6562979252 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs +++ b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; +namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; // @formatter:wrap_chained_method_calls chop_always diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationDisabledTests.cs new file mode 100644 index 0000000000..d2d7669b34 --- /dev/null +++ b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationDisabledTests.cs @@ -0,0 +1,192 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.Required; + +public sealed class ModelStateValidationDisabledTests + : IClassFixture, NullableReferenceTypesDisabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> + _testContext; + + public ModelStateValidationDisabledTests( + OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.UseController(); + } + + [Fact] + public async Task Schema_property_is_not_required_for_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("name"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("nameOfCurrentFarm"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("age"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("weight"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_nullable_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("timeAtCurrentFarmInDays"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_nullable_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("hasProducedEggs"); + }); + } + + [Fact] + public async Task No_schema_properties_for_attributes_are_required_in_PATCH_request() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); + } + + [Fact] + public async Task Schema_property_is_not_required_for_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("oldestChicken"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => + { + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredAttributes.Should().Contain("firstChicken"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_has_many_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("allChickens"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_has_many_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => + { + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredAttributes.Should().Contain("chickensReadyForLaying"); + }); + } + + [Fact] + public async Task No_schema_properties_for_relationships_are_required_in_PATCH_request() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.henHouseRelationshipsInPatchRequest.required"); + } +} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationEnabledTests.cs new file mode 100644 index 0000000000..24b01433c2 --- /dev/null +++ b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationEnabledTests.cs @@ -0,0 +1,191 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.Required; + +public sealed class ModelStateValidationEnabledTests + : IClassFixture, NullableReferenceTypesDisabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; + + public ModelStateValidationEnabledTests( + OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.UseController(); + } + + [Fact] + public async Task Schema_property_is_not_required_for_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("name"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("nameOfCurrentFarm"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("age"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("weight"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_nullable_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("timeAtCurrentFarmInDays"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_nullable_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("hasProducedEggs"); + }); + } + + [Fact] + public async Task No_schema_properties_for_attributes_are_required_in_PATCH_request() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); + } + + [Fact] + public async Task Schema_property_is_not_required_for_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("oldestChicken"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => + { + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredAttributes.Should().Contain("firstChicken"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_has_many_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("allChickens"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_has_many_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => + { + var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredAttributes.Should().Contain("chickensReadyForLaying"); + }); + } + + [Fact] + public async Task No_schema_properties_for_relationships_are_required_in_PATCH_request() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.henHouseRelationshipsInPatchRequest.required"); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Cow.cs similarity index 83% rename from test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs rename to test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Cow.cs index 3b5562a0b3..4a09687602 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/Cow.cs +++ b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Cow.cs @@ -3,10 +3,10 @@ using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; +namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] +[Resource(ControllerNamespace = "OpenApiTests.ResourceFieldsValidation")] public sealed class Cow : Identifiable { [Attr] diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/CowStable.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/CowStable.cs similarity index 82% rename from test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/CowStable.cs rename to test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/CowStable.cs index b267423501..6b7af3c5d0 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/CowStable.cs +++ b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/CowStable.cs @@ -3,10 +3,10 @@ using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; +namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(ControllerNamespace = "OpenApiTests.SchemaProperties")] +[Resource(ControllerNamespace = "OpenApiTests.ResourceFieldsValidation")] public sealed class CowStable : Identifiable { [HasOne] diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs new file mode 100644 index 0000000000..82b2a94b8c --- /dev/null +++ b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs @@ -0,0 +1,245 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; + +public sealed class NullabilityTests + : IClassFixture, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; + + public NullabilityTests(OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.UseController(); + testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled"; + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("name").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_required_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("nameOfCurrentFarm").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_nullable_for_nullable_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("nameOfPreviousFarm").With(schemaProperty => + { + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_required_nullable_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("nickname").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("age").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_required_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("weight").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_nullable_for_nullable_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("timeAtCurrentFarmInDays").With(schemaProperty => + { + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_required_nullable_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("hasProducedMilk").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("oldestCow.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_required_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("firstCow.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_nullable_for_nullable_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("albinoCow.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_required_nullable_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("favoriteCow.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_has_many_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("cowsReadyForMilking.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } + + [Fact] + public async Task Schema_property_is_not_nullable_for_required_has_many_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("allCows.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs similarity index 93% rename from test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs rename to test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs index e83298f28d..d8ad163bdd 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs +++ b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; +namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; // @formatter:wrap_chained_method_calls chop_always diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationDisabledTests.cs new file mode 100644 index 0000000000..17ac968dee --- /dev/null +++ b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationDisabledTests.cs @@ -0,0 +1,252 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.Required; + +public sealed class ModelStateValidationDisabledTests + : IClassFixture, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> + _testContext; + + public ModelStateValidationDisabledTests( + OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.UseController(); + } + + [Fact] + public async Task Schema_property_is_required_for_non_nullable_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("name"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_non_nullable_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("nameOfCurrentFarm"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_nullable_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("nameOfPreviousFarm"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_nullable_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("nickname"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("age"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("weight"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_nullable_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("timeAtCurrentFarmInDays"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_nullable_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("hasProducedMilk"); + }); + } + + [Fact] + public async Task No_schema_properties_for_attributes_are_required_in_PATCH_request() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.cowStableAttributesInPatchRequest.required"); + } + + [Fact] + public async Task Schema_property_is_required_for_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("oldestCow"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("firstCow"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_nullable_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("albinoCow"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_nullable_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("favoriteCow"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_has_many_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("cowsReadyForMilking"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_has_many_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("allCows"); + }); + } + + [Fact] + public async Task No_schema_properties_for_relationships_are_required_in_PATCH_request() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.cowStableRelationshipsInPatchRequest.required"); + } +} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationEnabledTests.cs new file mode 100644 index 0000000000..24146e7bba --- /dev/null +++ b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationEnabledTests.cs @@ -0,0 +1,251 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.Required; + +public sealed class ModelStateValidationEnabledTests + : IClassFixture, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; + + public ModelStateValidationEnabledTests( + OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.UseController(); + } + + [Fact] + public async Task Schema_property_is_required_for_non_nullable_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("name"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_non_nullable_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("nameOfCurrentFarm"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_nullable_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("nameOfPreviousFarm"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_nullable_reference_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("nickname"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("age"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("weight"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_nullable_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("timeAtCurrentFarmInDays"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_nullable_value_type_attribute() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("hasProducedMilk"); + }); + } + + [Fact] + public async Task No_schema_properties_for_attributes_are_required_in_PATCH_request() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.cowStableAttributesInPatchRequest.required"); + } + + [Fact] + public async Task Schema_property_is_required_for_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("oldestCow"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("firstCow"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_nullable_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("albinoCow"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_nullable_has_one_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("favoriteCow"); + }); + } + + [Fact] + public async Task Schema_property_is_not_required_for_has_many_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().NotContain("cowsReadyForMilking"); + }); + } + + [Fact] + public async Task Schema_property_is_required_for_required_has_many_relationship() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => + { + var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); + + requiredProperties.Should().Contain("allCows"); + }); + } + + [Fact] + public async Task No_schema_properties_for_relationships_are_required_in_PATCH_request() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.cowStableRelationshipsInPatchRequest.required"); + } +} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs deleted file mode 100644 index 0b88c3f88b..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationDisabledTests.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; - -public sealed class ModelStateValidationDisabledTests - : IClassFixture, NullableReferenceTypesDisabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> - _testContext; - - public ModelStateValidationDisabledTests( - OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - testContext.UseController(); - } - - [Theory] - [InlineData("nameOfCurrentFarm")] - [InlineData("weight")] - [InlineData("hasProducedEggs")] - public async Task Property_in_schema_for_attributes_in_POST_request_should_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain(propertyName); - }); - } - - [Theory] - [InlineData("name")] - [InlineData("age")] - [InlineData("timeAtCurrentFarmInDays")] - public async Task Property_in_schema_for_attributes_in_POST_request_should_not_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain(propertyName); - }); - } - - [Fact] - public async Task Schema_for_attributes_in_PATCH_request_should_have_no_required_properties() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); - } - - [Theory] - [InlineData("firstChicken")] - [InlineData("chickensReadyForLaying")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredAttributes.Should().Contain(propertyName); - }); - } - - [Theory] - [InlineData("oldestChicken")] - [InlineData("allChickens")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain(propertyName); - }); - } - - [Fact] - public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.henHouseRelationshipsInPatchRequest.required"); - } -} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs deleted file mode 100644 index bd31a1f25a..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/ModelStateValidationEnabledTests.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; - -public sealed class ModelStateValidationEnabledTests - : IClassFixture, NullableReferenceTypesDisabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; - - public ModelStateValidationEnabledTests( - OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - testContext.UseController(); - } - - [Theory] - [InlineData("nameOfCurrentFarm")] - [InlineData("weight")] - [InlineData("hasProducedEggs")] - public async Task Property_in_schema_for_attributes_in_POST_request_should_be_required(string attributeName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain(attributeName); - }); - } - - [Theory] - [InlineData("name")] - [InlineData("age")] - [InlineData("timeAtCurrentFarmInDays")] - public async Task Property_in_schema_for_attributes_in_POST_request_should_not_be_required(string attributeName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain(attributeName); - }); - } - - [Fact] - public async Task Schema_for_attributes_in_PATCH_request_should_have_no_required_properties() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); - } - - [Theory] - [InlineData("firstChicken")] - [InlineData("chickensReadyForLaying")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredAttributes.Should().Contain(propertyName); - }); - } - - [Theory] - [InlineData("oldestChicken")] - [InlineData("allChickens")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain(propertyName); - }); - } - - [Fact] - public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.henHouseRelationshipsInPatchRequest.required"); - } -} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs deleted file mode 100644 index dca51da02b..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; - -public sealed class NullabilityTests - : IClassFixture, NullableReferenceTypesDisabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; - - public NullabilityTests(OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - testContext.UseController(); - testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled"; - } - - [Theory] - [InlineData("name")] - [InlineData("timeAtCurrentFarmInDays")] - public async Task Property_in_schema_for_attribute_of_resource_should_be_nullable(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath(propertyName).With(schemaProperty => - { - schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); - }); - }); - } - - [Theory] - [InlineData("nameOfCurrentFarm")] - [InlineData("age")] - [InlineData("weight")] - [InlineData("hasProducedEggs")] - public async Task Property_in_schema_for_attribute_of_should_not_be_nullable(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath(propertyName).With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Theory] - [InlineData("oldestChicken")] - public async Task Property_in_schema_for_relationship_of_resource_should_be_nullable(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); - }); - }); - } - - [Theory] - [InlineData("allChickens")] - [InlineData("firstChicken")] - [InlineData("chickensReadyForLaying")] - public async Task Data_property_in_schema_for_relationship_of_resource_should_not_be_nullable(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } -} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs deleted file mode 100644 index fb83c4ea5f..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationDisabledTests.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; - -public sealed class ModelStateValidationDisabledTests - : IClassFixture, NullableReferenceTypesEnabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> - _testContext; - - public ModelStateValidationDisabledTests( - OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - testContext.UseController(); - } - - [Theory] - [InlineData("nameOfCurrentFarm")] - [InlineData("nickname")] - [InlineData("weight")] - [InlineData("hasProducedMilk")] - public async Task Property_in_schema_for_attributes_in_POST_request_should_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain(propertyName); - }); - } - - [Theory] - [InlineData("name")] - [InlineData("age")] - [InlineData("timeAtCurrentFarmInDays")] - public async Task Property_in_schema_for_attributes_in_POST_request_should_not_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain(propertyName); - }); - } - - [Fact] - public async Task Schema_for_attributes_in_PATCH_request_should_have_no_required_properties() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); - } - - [Theory] - [InlineData("firstCow")] - [InlineData("allCows")] - [InlineData("favoriteCow")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredAttributes.Should().Contain(propertyName); - }); - } - - [Theory] - [InlineData("oldestCow")] - [InlineData("cowsReadyForMilking")] - [InlineData("albinoCow")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain(propertyName); - }); - } - - [Fact] - public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.cowStableRelationshipsInPatchRequest.required"); - } -} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs deleted file mode 100644 index d45937a580..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/ModelStateValidationEnabledTests.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; - -public sealed class ModelStateValidationEnabledTests - : IClassFixture, NullableReferenceTypesEnabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; - - public ModelStateValidationEnabledTests( - OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - testContext.UseController(); - } - - [Theory] - [InlineData("name")] - [InlineData("nameOfCurrentFarm")] - [InlineData("nickname")] - [InlineData("weight")] - [InlineData("hasProducedMilk")] - public async Task Property_in_schema_for_attributes_in_POST_request_should_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain(propertyName); - }); - } - - [Theory] - [InlineData("age")] - [InlineData("timeAtCurrentFarmInDays")] - public async Task Property_in_schema_for_attributes_in_POST_request_should_not_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain(propertyName); - }); - } - - [Fact] - public async Task Schema_for_attributes_in_PATCH_request_should_have_no_required_properties() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); - } - - [Theory] - [InlineData("oldestCow")] - [InlineData("firstCow")] - [InlineData("allCows")] - [InlineData("favoriteCow")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredAttributes.Should().Contain(propertyName); - }); - } - - [Theory] - [InlineData("cowsReadyForMilking")] - [InlineData("albinoCow")] - public async Task Property_in_schema_for_relationships_in_POST_request_should_not_be_required(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain(propertyName); - }); - } - - [Fact] - public async Task Schema_for_relationships_in_PATCH_request_should_have_no_required_properties() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.cowStableRelationshipsInPatchRequest.required"); - } -} diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs deleted file mode 100644 index 5652c61f33..0000000000 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; - -public sealed class NullabilityTests - : IClassFixture, NullableReferenceTypesEnabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; - - public NullabilityTests(OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - testContext.UseController(); - testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled"; - } - - [Theory] - [InlineData("nameOfPreviousFarm")] - [InlineData("timeAtCurrentFarmInDays")] - public async Task Property_in_schema_for_attribute_of_resource_should_be_nullable(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath(propertyName).With(schemaProperty => - { - schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); - }); - }); - } - - [Theory] - [InlineData("name")] - [InlineData("nameOfCurrentFarm")] - [InlineData("nickname")] - [InlineData("age")] - [InlineData("weight")] - [InlineData("hasProducedMilk")] - public async Task Property_in_schema_for_attribute_of_resource_should_not_be_nullable(string attributeName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath(attributeName).With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Theory] - [InlineData("albinoCow")] - public async Task Property_in_schema_for_relationship_of_resource_should_be_nullable(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); - }); - }); - } - - [Theory] - [InlineData("oldestCow")] - [InlineData("firstCow")] - [InlineData("cowsReadyForMilking")] - [InlineData("allCows")] - [InlineData("favoriteCow")] - public async Task Data_property_in_schema_for_relationship_of_resource_should_not_be_nullable(string propertyName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath($"{propertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } -} diff --git a/test/OpenApiTests/JsonElementExtensions.cs b/test/TestBuildingBlocks/JsonElementExtensions.cs similarity index 87% rename from test/OpenApiTests/JsonElementExtensions.cs rename to test/TestBuildingBlocks/JsonElementExtensions.cs index 3649127917..ee5c362489 100644 --- a/test/OpenApiTests/JsonElementExtensions.cs +++ b/test/TestBuildingBlocks/JsonElementExtensions.cs @@ -3,11 +3,10 @@ using FluentAssertions; using FluentAssertions.Execution; using JetBrains.Annotations; -using TestBuildingBlocks; -namespace OpenApiTests; +namespace TestBuildingBlocks; -internal static class JsonElementExtensions +public static class JsonElementExtensions { public static JsonElementAssertions Should(this JsonElement source) { @@ -33,6 +32,12 @@ public static void ShouldBeString(this JsonElement source, string value) source.GetString().Should().Be(value); } + public static void ShouldBeInteger(this JsonElement source, int value) + { + source.ValueKind.Should().Be(JsonValueKind.Number); + source.GetInt32().Should().Be(value); + } + public static SchemaReferenceIdContainer ShouldBeSchemaReferenceId(this JsonElement source, string value) { string schemaReferenceId = GetSchemaReferenceId(source); @@ -63,7 +68,7 @@ public sealed class SchemaReferenceIdContainer { public string SchemaReferenceId { get; } - public SchemaReferenceIdContainer(string schemaReferenceId) + internal SchemaReferenceIdContainer(string schemaReferenceId) { SchemaReferenceId = schemaReferenceId; } @@ -71,7 +76,7 @@ public SchemaReferenceIdContainer(string schemaReferenceId) public sealed class JsonElementAssertions : JsonElementAssertions { - public JsonElementAssertions(JsonElement subject) + internal JsonElementAssertions(JsonElement subject) : base(subject) { } diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index ce8c54ef3b..55e2830bb9 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -8,6 +8,7 @@ + From a2e7a96dcd5c380c6da677b461acffa259ca9fdb Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Wed, 28 Dec 2022 23:59:11 +0100 Subject: [PATCH 21/26] Add two missing 'Act' comments --- .../NullableReferenceTypesDisabled/RequestTests.cs | 2 ++ .../NullableReferenceTypesEnabled/RequestTests.cs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/RequestTests.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/RequestTests.cs index 35499a2cc0..75d086e6ce 100644 --- a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/RequestTests.cs +++ b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/RequestTests.cs @@ -372,6 +372,7 @@ public async Task Can_clear_has_one_relationship() HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); requestDocument.Data.Relationships.OldestChicken.Data = null; + // Act await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); // Assert @@ -612,6 +613,7 @@ public async Task Relationships_required_in_POST_request_are_not_required_in_PAT requestDocument.Data.Relationships.ChickensReadyForLaying = default!; requestDocument.Data.Relationships.AllChickens = default!; + // Act await ApiResponse.TranslateAsync(async () => await apiClient.PatchHenHouseAsync(int.Parse(requestDocument.Data.Id), requestDocument)); // Assert diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/RequestTests.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/RequestTests.cs index 2a509fef2a..0c36b0a137 100644 --- a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/RequestTests.cs +++ b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/RequestTests.cs @@ -458,6 +458,7 @@ public async Task Can_clear_nullable_has_one_relationship() CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); requestDocument.Data.Relationships.AlbinoCow.Data = null; + // Act await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); // Assert @@ -487,6 +488,7 @@ public async Task Can_exclude_nullable_has_one_relationship() CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); requestDocument.Data.Relationships.AlbinoCow = default!; + // Act await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); // Assert @@ -561,6 +563,7 @@ public async Task Can_exclude_has_many_relationship() CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); requestDocument.Data.Relationships.CowsReadyForMilking = default!; + // Act await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); // Assert @@ -700,6 +703,7 @@ public async Task Relationships_required_in_POST_request_are_not_required_in_PAT requestDocument.Data.Relationships.FavoriteCow = default!; requestDocument.Data.Relationships.AllCows = default!; + // Act await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowStableAsync(int.Parse(requestDocument.Data.Id), requestDocument)); // Assert From 1815decf1d653e49f3f6cbf7fb27855778604843 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Tue, 3 Jan 2023 10:20:50 +0100 Subject: [PATCH 22/26] More elaborate testing -> in sync with latest version of nullability/required table -> introduces ResourceFieldValidationMetadataProvider -> Fix test in legacy projects -> Reusable faker building block for OpenApiClient related concerns --- .../IJsonApiClient.cs | 2 +- .../JsonApiClient.cs | 72 +- ...onApiActionDescriptorCollectionProvider.cs | 5 +- .../JsonApiEndpointMetadataProvider.cs | 18 +- .../NonPrimaryDocumentTypeFactory.cs | 11 +- .../JsonApiObjects/RelationshipTypeFactory.cs | 11 +- .../ResourceFieldAttributeExtensions.cs | 39 - ...ResourceFieldValidationMetadataProvider.cs | 92 + .../ServiceCollectionExtensions.cs | 7 +- .../JsonApiSchemaGenerator.cs | 7 +- .../ResourceFieldObjectSchemaBuilder.cs | 44 +- .../ResourceObjectSchemaGenerator.cs | 4 +- test/OpenApiClientTests/FakerFactory.cs | 41 + ...equestDocumentRegistrationLifetimeTests.cs | 26 +- .../LegacyClient/RequestTests.cs | 4 +- .../LegacyClient/ResponseTests.cs | 10 +- .../LegacyClient/swagger.g.json | 14 + .../CamelCase/swagger.g.json | 9 + .../KebabCase/swagger.g.json | 9 + .../PascalCase/swagger.g.json | 9 + test/OpenApiClientTests/ObjectExtensions.cs | 37 + test/OpenApiClientTests/OpenApiClientTests.cs | 20 + .../OpenApiClientTests.csproj | 36 +- .../CreateResourceTests.cs | 528 +++++ .../GeneratedCode/NrtOffMsvOffClient.cs | 14 + .../NullabilityTests.cs | 23 + .../ResourceFieldValidationFakers.cs | 25 + .../UpdateResourceTests.cs | 146 ++ .../swagger.g.json | 1677 ++++++++++++++++ .../CreateResourceTests.cs | 569 ++++++ .../GeneratedCode/NrtOffMsvOnClient.cs} | 4 +- .../NullabilityTests.cs | 23 + .../ResourceFieldValidationFakers.cs | 29 + .../UpdateResourceTests.cs | 146 ++ .../swagger.g.json | 1744 +++++++++++++++++ .../CreateResourceTests.cs | 613 ++++++ .../GeneratedCode/NrtOnMsvOffClient.cs | 14 + .../NullabilityTests.cs | 25 + .../ResourceFieldValidationFakers.cs | 29 + .../UpdateResourceTests.cs | 156 ++ .../swagger.g.json | 1507 ++++++-------- .../CreateResourceTests.cs | 613 ++++++ .../GeneratedCode/NrtOnMsvOnClient.cs} | 4 +- .../NullabilityTests.cs | 25 + .../ResourceFieldValidationFakers.cs | 29 + .../UpdateResourceTests.cs | 156 ++ .../swagger.g.json | 1436 +++++++------- .../NullabilityTests.cs | 23 - .../NullableReferenceTypesDisabledFaker.cs | 124 -- .../RequestTests.cs | 638 ------ .../NullabilityTests.cs | 25 - .../NullableReferenceTypesEnabledFaker.cs | 132 -- .../RequestTests.cs | 728 ------- .../LegacyOpenApiIntegration/swagger.json | 14 + test/OpenApiTests/OpenApiTestContext.cs | 1 - .../ResourceFieldValidation/EmptyResource.cs | 9 + .../ModelStateValidationDisabledStartup.cs | 2 +- .../NullabilityTests.cs | 182 ++ .../RequiredTests.cs | 180 ++ .../NullabilityTests.cs | 67 +- .../RequiredTests.cs | 181 ++ .../NrtDisabledResource.cs | 48 + ...NullableReferenceTypesDisabledDbContext.cs | 35 + .../NullabilityTests.cs | 100 + .../RequiredTests.cs | 100 + .../NullabilityTests.cs | 244 +++ .../RequiredTests.cs | 234 +++ .../NrtEnabledResource.cs | 60 + .../NullableReferenceTypesEnabledDbContext.cs | 41 + .../NullableReferenceTypesDisabled/Chicken.cs | 34 - .../HenHouse.cs | 27 - ...NullableReferenceTypesDisabledDbContext.cs | 36 - .../ModelStateValidationDisabledTests.cs | 192 -- .../ModelStateValidationEnabledTests.cs | 191 -- .../NullableReferenceTypesEnabled/Cow.cs | 39 - .../CowStable.cs | 32 - .../NullabilityTests.cs | 245 --- .../NullableReferenceTypesEnabledDbContext.cs | 42 - .../ModelStateValidationDisabledTests.cs | 252 --- .../ModelStateValidationEnabledTests.cs | 251 --- .../JsonElementExtensions.cs | 16 + 81 files changed, 9828 insertions(+), 4759 deletions(-) delete mode 100644 src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs create mode 100644 src/JsonApiDotNetCore.OpenApi/ResourceFieldValidationMetadataProvider.cs create mode 100644 test/OpenApiClientTests/FakerFactory.cs create mode 100644 test/OpenApiClientTests/ObjectExtensions.cs create mode 100644 test/OpenApiClientTests/OpenApiClientTests.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/CreateResourceTests.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/GeneratedCode/NrtOffMsvOffClient.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/UpdateResourceTests.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/swagger.g.json create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/CreateResourceTests.cs rename test/OpenApiClientTests/{ResourceFieldsValidation/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs => ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/GeneratedCode/NrtOffMsvOnClient.cs} (58%) create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/UpdateResourceTests.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/swagger.g.json create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/CreateResourceTests.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/GeneratedCode/NrtOnMsvOffClient.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/UpdateResourceTests.cs rename test/OpenApiClientTests/{ResourceFieldsValidation/NullableReferenceTypesEnabled => ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled}/swagger.g.json (67%) create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/CreateResourceTests.cs rename test/OpenApiClientTests/{ResourceFieldsValidation/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs => ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/GeneratedCode/NrtOnMsvOnClient.cs} (59%) create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs create mode 100644 test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/UpdateResourceTests.cs rename test/OpenApiClientTests/{ResourceFieldsValidation/NullableReferenceTypesDisabled => ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled}/swagger.g.json (68%) delete mode 100644 test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs delete mode 100644 test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledFaker.cs delete mode 100644 test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/RequestTests.cs delete mode 100644 test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs delete mode 100644 test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledFaker.cs delete mode 100644 test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/RequestTests.cs create mode 100644 test/OpenApiTests/ResourceFieldValidation/EmptyResource.cs rename test/OpenApiTests/{ResourceFieldsValidation => ResourceFieldValidation}/ModelStateValidationDisabledStartup.cs (90%) create mode 100644 test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs create mode 100644 test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/RequiredTests.cs rename test/OpenApiTests/{ResourceFieldsValidation/NullableReferenceTypesDisabled => ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled}/NullabilityTests.cs (56%) create mode 100644 test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/RequiredTests.cs create mode 100644 test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/NrtDisabledResource.cs create mode 100644 test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs create mode 100644 test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs create mode 100644 test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/RequiredTests.cs create mode 100644 test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs create mode 100644 test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/RequiredTests.cs create mode 100644 test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/NrtEnabledResource.cs create mode 100644 test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs delete mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Chicken.cs delete mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/HenHouse.cs delete mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs delete mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationDisabledTests.cs delete mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationEnabledTests.cs delete mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Cow.cs delete mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/CowStable.cs delete mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs delete mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs delete mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationDisabledTests.cs delete mode 100644 test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationEnabledTests.cs diff --git a/src/JsonApiDotNetCore.OpenApi.Client/IJsonApiClient.cs b/src/JsonApiDotNetCore.OpenApi.Client/IJsonApiClient.cs index 18c90fdfd2..5cb98a7b9a 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client/IJsonApiClient.cs +++ b/src/JsonApiDotNetCore.OpenApi.Client/IJsonApiClient.cs @@ -35,7 +35,7 @@ public interface IJsonApiClient /// An to clear the current registration. For efficient memory usage, it is recommended to wrap calls to this method in a /// using statement, so the registrations are cleaned up after executing the request. /// - IDisposable OmitDefaultValuesForAttributesInRequestDocument(TRequestDocument requestDocument, + IDisposable WithPartialAttributeSerialization(TRequestDocument requestDocument, params Expression>[] alwaysIncludedAttributeSelectors) where TRequestDocument : class; } diff --git a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs index f6d9dbdb2d..f119396747 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs +++ b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs @@ -24,7 +24,7 @@ protected void SetSerializerSettingsForJsonApi(JsonSerializerSettings settings) } /// - public IDisposable OmitDefaultValuesForAttributesInRequestDocument(TRequestDocument requestDocument, + public IDisposable WithPartialAttributeSerialization(TRequestDocument requestDocument, params Expression>[] alwaysIncludedAttributeSelectors) where TRequestDocument : class { @@ -40,12 +40,12 @@ public IDisposable OmitDefaultValuesForAttributesInRequestDocument article.Title'."); + throw new ArgumentException( + $"The expression '{nameof(alwaysIncludedAttributeSelectors)}' should select a single property. For example: 'article => article.Title'."); } } - _jsonApiJsonConverter.RegisterRequestDocumentForAttributesOmission(requestDocument, - new AttributesObjectInfo(attributeNames, typeof(TAttributesObject))); + _jsonApiJsonConverter.RegisterDocument(requestDocument, new AttributeNamesContainer(attributeNames, typeof(TAttributesObject))); return new RequestDocumentRegistrationScope(_jsonApiJsonConverter, requestDocument); } @@ -69,15 +69,15 @@ private static Expression RemoveConvert(Expression expression) private sealed class JsonApiJsonConverter : JsonConverter { - private readonly Dictionary _attributesObjectInfoByRequestDocument = new(); + private readonly Dictionary _alwaysIncludedAttributesByRequestDocument = new(); private readonly Dictionary> _requestDocumentsByType = new(); private SerializationScope? _serializationScope; public override bool CanRead => false; - public void RegisterRequestDocumentForAttributesOmission(object requestDocument, AttributesObjectInfo attributesObjectInfo) + public void RegisterDocument(object requestDocument, AttributeNamesContainer alwaysIncludedAttributes) { - _attributesObjectInfoByRequestDocument[requestDocument] = attributesObjectInfo; + _alwaysIncludedAttributesByRequestDocument[requestDocument] = alwaysIncludedAttributes; Type requestDocumentType = requestDocument.GetType(); @@ -91,9 +91,9 @@ public void RegisterRequestDocumentForAttributesOmission(object requestDocument, public void RemoveRegistration(object requestDocument) { - if (_attributesObjectInfoByRequestDocument.ContainsKey(requestDocument)) + if (_alwaysIncludedAttributesByRequestDocument.ContainsKey(requestDocument)) { - _attributesObjectInfoByRequestDocument.Remove(requestDocument); + _alwaysIncludedAttributesByRequestDocument.Remove(requestDocument); Type requestDocumentType = requestDocument.GetType(); _requestDocumentsByType[requestDocumentType].Remove(requestDocument); @@ -136,7 +136,7 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer } else { - AttributesObjectInfo? attributesObjectInfo = _serializationScope.AttributesObjectInScope; + AttributeNamesContainer? attributesObjectInfo = _serializationScope.AttributesObjectInScope; AssertObjectMatchesSerializationScope(attributesObjectInfo, value); @@ -158,7 +158,7 @@ private void SerializeRequestDocument(JsonWriter writer, object value, JsonSeria { _serializationScope = new SerializationScope(); - if (_attributesObjectInfoByRequestDocument.TryGetValue(value, out AttributesObjectInfo? attributesObjectInfo)) + if (_alwaysIncludedAttributesByRequestDocument.TryGetValue(value, out AttributeNamesContainer? attributesObjectInfo)) { _serializationScope.AttributesObjectInScope = attributesObjectInfo; } @@ -173,17 +173,18 @@ private void SerializeRequestDocument(JsonWriter writer, object value, JsonSeria } } - private static void AssertObjectMatchesSerializationScope([SysNotNull] AttributesObjectInfo? attributesObjectInfo, object value) + private static void AssertObjectMatchesSerializationScope([SysNotNull] AttributeNamesContainer? attributesObjectInfo, object value) { Type objectType = value.GetType(); - if (attributesObjectInfo == null || !attributesObjectInfo.MatchesType(objectType)) + if (attributesObjectInfo == null || !attributesObjectInfo.MatchesAttributesObjectType(objectType)) { throw new UnreachableCodeException(); } } - private static void SerializeAttributesObject(AttributesObjectInfo alwaysIncludedAttributes, JsonWriter writer, object value, JsonSerializer serializer) + private static void SerializeAttributesObject(AttributeNamesContainer alwaysIncludedAttributes, JsonWriter writer, object value, + JsonSerializer serializer) { AssertRequiredPropertiesAreNotExcluded(value, alwaysIncludedAttributes, writer); @@ -191,13 +192,13 @@ private static void SerializeAttributesObject(AttributesObjectInfo alwaysInclude serializer.Serialize(writer, value); } - private static void AssertRequiredPropertiesAreNotExcluded(object value, AttributesObjectInfo alwaysIncludedAttributes, JsonWriter jsonWriter) + private static void AssertRequiredPropertiesAreNotExcluded(object value, AttributeNamesContainer alwaysIncludedAttributes, JsonWriter jsonWriter) { PropertyInfo[] propertyInfos = value.GetType().GetProperties(); foreach (PropertyInfo attributesPropertyInfo in propertyInfos) { - bool isExplicitlyIncluded = alwaysIncludedAttributes.IsAttributeMarkedForInclusion(attributesPropertyInfo.Name); + bool isExplicitlyIncluded = alwaysIncludedAttributes.ContainsAttribute(attributesPropertyInfo.Name); if (isExplicitlyIncluded) { @@ -212,7 +213,7 @@ private static void AssertRequiredPropertyIsNotIgnored(object value, PropertyInf { JsonPropertyAttribute jsonPropertyForAttribute = attribute.GetCustomAttributes().Single(); - if (jsonPropertyForAttribute.Required != Required.Always) + if (jsonPropertyForAttribute.Required is not (Required.Always or Required.AllowNull)) { return; } @@ -221,8 +222,7 @@ private static void AssertRequiredPropertyIsNotIgnored(object value, PropertyInf if (isPropertyIgnored) { - throw new JsonSerializationException( - $"Ignored property '{jsonPropertyForAttribute.PropertyName}' must have a value because it is required. Path '{path}'."); + throw new InvalidOperationException($"The following property should not be omitted: {path}.{jsonPropertyForAttribute.PropertyName}."); } } @@ -248,7 +248,7 @@ private static bool DefaultValueEqualsCurrentValue(PropertyInfo propertyInfo, ob private sealed class SerializationScope { private bool _isFirstAttemptToConvertAttributes = true; - public AttributesObjectInfo? AttributesObjectInScope { get; set; } + public AttributeNamesContainer? AttributesObjectInScope { get; set; } public bool ShouldConvertAsAttributesObject(Type type) { @@ -257,7 +257,7 @@ public bool ShouldConvertAsAttributesObject(Type type) return false; } - if (!AttributesObjectInScope.MatchesType(type)) + if (!AttributesObjectInScope.MatchesAttributesObjectType(type)) { return false; } @@ -267,26 +267,26 @@ public bool ShouldConvertAsAttributesObject(Type type) } } - private sealed class AttributesObjectInfo + private sealed class AttributeNamesContainer { - private readonly ISet _attributesMarkedForInclusion; + private readonly ISet _attributeNames; private readonly Type _attributesObjectType; - public AttributesObjectInfo(ISet attributesMarkedForInclusion, Type attributesObjectType) + public AttributeNamesContainer(ISet attributeNames, Type attributesObjectType) { - ArgumentGuard.NotNull(attributesMarkedForInclusion); + ArgumentGuard.NotNull(attributeNames); ArgumentGuard.NotNull(attributesObjectType); - _attributesMarkedForInclusion = attributesMarkedForInclusion; + _attributeNames = attributeNames; _attributesObjectType = attributesObjectType; } - public bool IsAttributeMarkedForInclusion(string name) + public bool ContainsAttribute(string name) { - return _attributesMarkedForInclusion.Contains(name); + return _attributeNames.Contains(name); } - public bool MatchesType(Type type) + public bool MatchesAttributesObjectType(Type type) { return _attributesObjectType == type; } @@ -314,22 +314,24 @@ public void Dispose() private sealed class JsonApiAttributeContractResolver : DefaultContractResolver { - private readonly AttributesObjectInfo _attributesObjectInfo; + private readonly AttributeNamesContainer _alwaysIncludedAttributes; - public JsonApiAttributeContractResolver(AttributesObjectInfo attributesObjectInfo) + public JsonApiAttributeContractResolver(AttributeNamesContainer alwaysIncludedAttributes) { - ArgumentGuard.NotNull(attributesObjectInfo); + ArgumentGuard.NotNull(alwaysIncludedAttributes); - _attributesObjectInfo = attributesObjectInfo; + _alwaysIncludedAttributes = alwaysIncludedAttributes; } protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); - if (_attributesObjectInfo.MatchesType(property.DeclaringType!)) + bool canOmitAttribute = property.Required != Required.Always; + + if (canOmitAttribute && _alwaysIncludedAttributes.MatchesAttributesObjectType(property.DeclaringType!)) { - if (_attributesObjectInfo.IsAttributeMarkedForInclusion(property.UnderlyingName!)) + if (_alwaysIncludedAttributes.ContainsAttribute(property.UnderlyingName!)) { property.NullValueHandling = NullValueHandling.Include; property.DefaultValueHandling = DefaultValueHandling.Include; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiActionDescriptorCollectionProvider.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiActionDescriptorCollectionProvider.cs index 98ae9938b4..16b173dd17 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiActionDescriptorCollectionProvider.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiActionDescriptorCollectionProvider.cs @@ -22,13 +22,14 @@ internal sealed class JsonApiActionDescriptorCollectionProvider : IActionDescrip public ActionDescriptorCollection ActionDescriptors => GetActionDescriptors(); - public JsonApiActionDescriptorCollectionProvider(IControllerResourceMapping controllerResourceMapping, IActionDescriptorCollectionProvider defaultProvider) + public JsonApiActionDescriptorCollectionProvider(IControllerResourceMapping controllerResourceMapping, IActionDescriptorCollectionProvider defaultProvider, + ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider) { ArgumentGuard.NotNull(controllerResourceMapping); ArgumentGuard.NotNull(defaultProvider); _defaultProvider = defaultProvider; - _jsonApiEndpointMetadataProvider = new JsonApiEndpointMetadataProvider(controllerResourceMapping); + _jsonApiEndpointMetadataProvider = new JsonApiEndpointMetadataProvider(controllerResourceMapping, resourceFieldValidationMetadataProvider); } private ActionDescriptorCollection GetActionDescriptors() diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/JsonApiEndpointMetadataProvider.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/JsonApiEndpointMetadataProvider.cs index d0fe2f1234..0dcb87cabb 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/JsonApiEndpointMetadataProvider.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/JsonApiEndpointMetadataProvider.cs @@ -15,11 +15,13 @@ internal sealed class JsonApiEndpointMetadataProvider { private readonly IControllerResourceMapping _controllerResourceMapping; private readonly EndpointResolver _endpointResolver = new(); + private readonly NonPrimaryDocumentTypeFactory _nonPrimaryDocumentTypeFactory; - public JsonApiEndpointMetadataProvider(IControllerResourceMapping controllerResourceMapping) + public JsonApiEndpointMetadataProvider(IControllerResourceMapping controllerResourceMapping, + ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider) { ArgumentGuard.NotNull(controllerResourceMapping); - + _nonPrimaryDocumentTypeFactory = new NonPrimaryDocumentTypeFactory(resourceFieldValidationMetadataProvider); _controllerResourceMapping = controllerResourceMapping; } @@ -85,12 +87,12 @@ private static PrimaryRequestMetadata GetPatchRequestMetadata(Type resourceClrTy return new PrimaryRequestMetadata(documentType); } - private static RelationshipRequestMetadata GetRelationshipRequestMetadata(IEnumerable relationships, bool ignoreHasOneRelationships) + private RelationshipRequestMetadata GetRelationshipRequestMetadata(IEnumerable relationships, bool ignoreHasOneRelationships) { IEnumerable relationshipsOfEndpoint = ignoreHasOneRelationships ? relationships.OfType() : relationships; IDictionary requestDocumentTypesByRelationshipName = relationshipsOfEndpoint.ToDictionary(relationship => relationship.PublicName, - NonPrimaryDocumentTypeFactory.Instance.GetForRelationshipRequest); + _nonPrimaryDocumentTypeFactory.GetForRelationshipRequest); return new RelationshipRequestMetadata(requestDocumentTypesByRelationshipName); } @@ -129,18 +131,18 @@ private static PrimaryResponseMetadata GetPrimaryResponseMetadata(Type resourceC return new PrimaryResponseMetadata(documentType); } - private static SecondaryResponseMetadata GetSecondaryResponseMetadata(IEnumerable relationships) + private SecondaryResponseMetadata GetSecondaryResponseMetadata(IEnumerable relationships) { IDictionary responseDocumentTypesByRelationshipName = relationships.ToDictionary(relationship => relationship.PublicName, - NonPrimaryDocumentTypeFactory.Instance.GetForSecondaryResponse); + _nonPrimaryDocumentTypeFactory.GetForSecondaryResponse); return new SecondaryResponseMetadata(responseDocumentTypesByRelationshipName); } - private static RelationshipResponseMetadata GetRelationshipResponseMetadata(IEnumerable relationships) + private RelationshipResponseMetadata GetRelationshipResponseMetadata(IEnumerable relationships) { IDictionary responseDocumentTypesByRelationshipName = relationships.ToDictionary(relationship => relationship.PublicName, - NonPrimaryDocumentTypeFactory.Instance.GetForRelationshipResponse); + _nonPrimaryDocumentTypeFactory.GetForRelationshipResponse); return new RelationshipResponseMetadata(responseDocumentTypesByRelationshipName); } diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NonPrimaryDocumentTypeFactory.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NonPrimaryDocumentTypeFactory.cs index 95d7e2f6e5..387c181f8b 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NonPrimaryDocumentTypeFactory.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NonPrimaryDocumentTypeFactory.cs @@ -15,10 +15,13 @@ internal sealed class NonPrimaryDocumentTypeFactory private static readonly DocumentOpenTypes RelationshipResponseDocumentOpenTypes = new(typeof(ResourceIdentifierCollectionResponseDocument<>), typeof(NullableResourceIdentifierResponseDocument<>), typeof(ResourceIdentifierResponseDocument<>)); - public static NonPrimaryDocumentTypeFactory Instance { get; } = new(); + private readonly ResourceFieldValidationMetadataProvider _resourceFieldValidationMetadataProvider; - private NonPrimaryDocumentTypeFactory() + public NonPrimaryDocumentTypeFactory(ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider) { + ArgumentGuard.NotNull(resourceFieldValidationMetadataProvider); + + _resourceFieldValidationMetadataProvider = resourceFieldValidationMetadataProvider; } public Type GetForSecondaryResponse(RelationshipAttribute relationship) @@ -42,13 +45,13 @@ public Type GetForRelationshipResponse(RelationshipAttribute relationship) return Get(relationship, RelationshipResponseDocumentOpenTypes); } - private static Type Get(RelationshipAttribute relationship, DocumentOpenTypes types) + private Type Get(RelationshipAttribute relationship, DocumentOpenTypes types) { // @formatter:nested_ternary_style expanded Type documentOpenType = relationship is HasManyAttribute ? types.ManyDataOpenType - : relationship.IsNullable() + : _resourceFieldValidationMetadataProvider.IsNullable(relationship) ? types.NullableSingleDataOpenType : types.SingleDataOpenType; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/RelationshipTypeFactory.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/RelationshipTypeFactory.cs index 7593c9f9f5..06efd7622f 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/RelationshipTypeFactory.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/RelationshipTypeFactory.cs @@ -5,17 +5,20 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects; internal sealed class RelationshipTypeFactory { - public static RelationshipTypeFactory Instance { get; } = new(); + private readonly ResourceFieldValidationMetadataProvider _resourceFieldValidationMetadataProvider; + private readonly NonPrimaryDocumentTypeFactory _nonPrimaryDocumentTypeFactory; - private RelationshipTypeFactory() + public RelationshipTypeFactory(ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider) { + _nonPrimaryDocumentTypeFactory = new NonPrimaryDocumentTypeFactory(resourceFieldValidationMetadataProvider); + _resourceFieldValidationMetadataProvider = resourceFieldValidationMetadataProvider; } public Type GetForRequest(RelationshipAttribute relationship) { ArgumentGuard.NotNull(relationship); - return NonPrimaryDocumentTypeFactory.Instance.GetForRelationshipRequest(relationship); + return _nonPrimaryDocumentTypeFactory.GetForRelationshipRequest(relationship); } public Type GetForResponse(RelationshipAttribute relationship) @@ -26,7 +29,7 @@ public Type GetForResponse(RelationshipAttribute relationship) Type relationshipDataOpenType = relationship is HasManyAttribute ? typeof(ToManyRelationshipInResponse<>) - : relationship.IsNullable() + : _resourceFieldValidationMetadataProvider.IsNullable(relationship) ? typeof(NullableToOneRelationshipInResponse<>) : typeof(ToOneRelationshipInResponse<>); diff --git a/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs b/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs deleted file mode 100644 index b1274cbe97..0000000000 --- a/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Reflection; -using JsonApiDotNetCore.Resources.Annotations; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace JsonApiDotNetCore.OpenApi; - -internal static class ResourceFieldAttributeExtensions -{ - public static bool IsNullable(this ResourceFieldAttribute source) - { - bool hasRequiredAttribute = source.Property.HasAttribute(); - - if (hasRequiredAttribute) - { - // Reflects the following cases, independent of NRT setting - // `[Required] int? Number` => not nullable - // `[Required] int Number` => not nullable - // `[Required] string Text` => not nullable - // `[Required] string? Text` => not nullable - // `[Required] string Text` => not nullable - return false; - } - - NullabilityInfoContext nullabilityContext = new(); - NullabilityInfo nullabilityInfo = nullabilityContext.Create(source.Property); - - // Reflects the following cases: - // Independent of NRT: - // `int? Number` => nullable - // `int Number` => not nullable - // If NRT is enabled: - // `string? Text` => nullable - // `string Text` => not nullable - // If NRT is disabled: - // `string Text` => nullable - return nullabilityInfo.ReadState is not NullabilityState.NotNull; - } -} diff --git a/src/JsonApiDotNetCore.OpenApi/ResourceFieldValidationMetadataProvider.cs b/src/JsonApiDotNetCore.OpenApi/ResourceFieldValidationMetadataProvider.cs new file mode 100644 index 0000000000..bfbe32bb31 --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/ResourceFieldValidationMetadataProvider.cs @@ -0,0 +1,92 @@ +using System.ComponentModel.DataAnnotations; +using System.Reflection; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Resources.Annotations; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace JsonApiDotNetCore.OpenApi; + +internal sealed class ResourceFieldValidationMetadataProvider +{ + private readonly bool _validateModelState; + private readonly NullabilityInfoContext _nullabilityContext; + private readonly IModelMetadataProvider _modelMetadataProvider; + + public ResourceFieldValidationMetadataProvider(IJsonApiOptions options, IModelMetadataProvider modelMetadataProvider) + { + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(modelMetadataProvider); + + _validateModelState = options.ValidateModelState; + _modelMetadataProvider = modelMetadataProvider; + _nullabilityContext = new NullabilityInfoContext(); + } + + public bool IsNullable(ResourceFieldAttribute field) + { + ArgumentGuard.NotNull(field); + + if (field is HasManyAttribute) + { + return false; + } + + bool hasRequiredAttribute = field.Property.HasAttribute(); + NullabilityInfo nullabilityInfo = _nullabilityContext.Create(field.Property); + + if (field is HasManyAttribute) + { + return false; + } + + if (hasRequiredAttribute && _validateModelState && nullabilityInfo.ReadState != NullabilityState.NotNull) + { + return false; + } + + return nullabilityInfo.ReadState != NullabilityState.NotNull; + } + + public bool IsRequired(ResourceFieldAttribute field) + { + ArgumentGuard.NotNull(field); + + bool hasRequiredAttribute = field.Property.HasAttribute(); + + if (!_validateModelState) + { + return hasRequiredAttribute; + } + + if (field is HasManyAttribute) + { + return false; + } + + bool isNotNull = HasNullabilityStateNotNull(field); + bool isRequiredValueType = field.Property.PropertyType.IsValueType && hasRequiredAttribute && isNotNull; + + if (isRequiredValueType) + { + return false; + } + + return IsModelStateValidationRequired(field); + } + + private bool IsModelStateValidationRequired(ResourceFieldAttribute field) + { + ModelMetadata resourceFieldModelMetadata = _modelMetadataProvider.GetMetadataForProperties(field.Type.ClrType) + .Single(modelMetadata => modelMetadata.PropertyName! == field.Property.Name); + + return resourceFieldModelMetadata.ValidatorMetadata.Any(validatorMetadata => validatorMetadata is RequiredAttribute); + } + + private bool HasNullabilityStateNotNull(ResourceFieldAttribute field) + { + NullabilityInfo resourceFieldNullabilityInfo = _nullabilityContext.Create(field.Property); + bool hasNullabilityStateNotNull = resourceFieldNullabilityInfo is { ReadState: NullabilityState.NotNull, WriteState: NullabilityState.NotNull }; + return hasNullabilityStateNotNull; + } +} diff --git a/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs index f223c7d994..def25eb0dd 100644 --- a/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs @@ -34,13 +34,18 @@ public static void AddOpenApi(this IServiceCollection services, IMvcCoreBuilder private static void AddCustomApiExplorer(IServiceCollection services, IMvcCoreBuilder mvcBuilder) { + services.AddSingleton(); + services.AddSingleton(provider => { var controllerResourceMapping = provider.GetRequiredService(); var actionDescriptorCollectionProvider = provider.GetRequiredService(); var apiDescriptionProviders = provider.GetRequiredService>(); + var resourceFieldValidationMetadataProvider = provider.GetRequiredService(); + + JsonApiActionDescriptorCollectionProvider descriptorCollectionProviderWrapper = + new(controllerResourceMapping, actionDescriptorCollectionProvider, resourceFieldValidationMetadataProvider); - JsonApiActionDescriptorCollectionProvider descriptorCollectionProviderWrapper = new(controllerResourceMapping, actionDescriptorCollectionProvider); return new ApiDescriptionGroupCollectionProvider(descriptorCollectionProviderWrapper, apiDescriptionProviders); }); diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs index 2df640379a..1e8efa8452 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs @@ -39,7 +39,8 @@ internal sealed class JsonApiSchemaGenerator : ISchemaGenerator private readonly NullableReferenceSchemaGenerator _nullableReferenceSchemaGenerator; private readonly SchemaRepositoryAccessor _schemaRepositoryAccessor = new(); - public JsonApiSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IResourceGraph resourceGraph, IJsonApiOptions options) + public JsonApiSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IResourceGraph resourceGraph, IJsonApiOptions options, + ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider) { ArgumentGuard.NotNull(defaultSchemaGenerator); ArgumentGuard.NotNull(resourceGraph); @@ -47,7 +48,9 @@ public JsonApiSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IResourceG _defaultSchemaGenerator = defaultSchemaGenerator; _nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(_schemaRepositoryAccessor, options.SerializerOptions.PropertyNamingPolicy); - _resourceObjectSchemaGenerator = new ResourceObjectSchemaGenerator(defaultSchemaGenerator, resourceGraph, options, _schemaRepositoryAccessor); + + _resourceObjectSchemaGenerator = new ResourceObjectSchemaGenerator(defaultSchemaGenerator, resourceGraph, options, _schemaRepositoryAccessor, + resourceFieldValidationMetadataProvider); } public OpenApiSchema GenerateSchema(Type modelType, SchemaRepository schemaRepository, MemberInfo? memberInfo = null, ParameterInfo? parameterInfo = null, diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs index 7c9cb75a15..678229864c 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs @@ -1,5 +1,3 @@ -using System.ComponentModel.DataAnnotations; -using System.Reflection; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.OpenApi.JsonApiObjects; using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships; @@ -29,27 +27,31 @@ internal sealed class ResourceFieldObjectSchemaBuilder private readonly ISchemaRepositoryAccessor _schemaRepositoryAccessor; private readonly SchemaGenerator _defaultSchemaGenerator; private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator; - private readonly IJsonApiOptions _options; private readonly SchemaRepository _resourceSchemaRepository = new(); private readonly NullableReferenceSchemaGenerator _nullableReferenceSchemaGenerator; private readonly IDictionary _schemasForResourceFields; + private readonly ResourceFieldValidationMetadataProvider _resourceFieldValidationMetadataProvider; + private readonly RelationshipTypeFactory _relationshipTypeFactory; public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISchemaRepositoryAccessor schemaRepositoryAccessor, - SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, IJsonApiOptions options) + SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, IJsonApiOptions options, + ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider) { ArgumentGuard.NotNull(resourceTypeInfo); ArgumentGuard.NotNull(schemaRepositoryAccessor); ArgumentGuard.NotNull(defaultSchemaGenerator); ArgumentGuard.NotNull(resourceTypeSchemaGenerator); ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(resourceFieldValidationMetadataProvider); _resourceTypeInfo = resourceTypeInfo; _schemaRepositoryAccessor = schemaRepositoryAccessor; _defaultSchemaGenerator = defaultSchemaGenerator; _resourceTypeSchemaGenerator = resourceTypeSchemaGenerator; - _options = options; + _resourceFieldValidationMetadataProvider = resourceFieldValidationMetadataProvider; _nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(schemaRepositoryAccessor, options.SerializerOptions.PropertyNamingPolicy); + _relationshipTypeFactory = new RelationshipTypeFactory(resourceFieldValidationMetadataProvider); _schemasForResourceFields = GetFieldSchemas(); } @@ -76,7 +78,7 @@ public void SetMembersOfAttributesObject(OpenApiSchema fullSchemaForAttributesOb { AddAttributeSchemaToResourceObject(matchingAttribute, fullSchemaForAttributesObject, resourceFieldSchema); - resourceFieldSchema.Nullable = matchingAttribute.IsNullable(); + resourceFieldSchema.Nullable = _resourceFieldValidationMetadataProvider.IsNullable(matchingAttribute); if (IsFieldRequired(matchingAttribute)) { @@ -112,29 +114,9 @@ private void ExposeSchema(OpenApiReference openApiReference, Type typeRepresente private bool IsFieldRequired(ResourceFieldAttribute field) { - if (_resourceTypeInfo.ResourceObjectOpenType != typeof(ResourceObjectInPostRequest<>)) - { - return false; - } - - if (field.Property.HasAttribute()) - { - return true; - } + bool isSchemaForUpdateResourceEndpoint = _resourceTypeInfo.ResourceObjectOpenType == typeof(ResourceObjectInPatchRequest<>); - if (field is HasManyAttribute) - { - return false; - } - - NullabilityInfoContext nullabilityContext = new(); - NullabilityInfo nullabilityInfo = nullabilityContext.Create(field.Property); - - return field.Property.PropertyType.IsValueType switch - { - true => false, - false => _options.ValidateModelState && nullabilityInfo.ReadState == NullabilityState.NotNull - }; + return !isSchemaForUpdateResourceEndpoint && _resourceFieldValidationMetadataProvider.IsRequired(field); } public void SetMembersOfRelationshipsObject(OpenApiSchema fullSchemaForRelationshipsObject) @@ -192,11 +174,11 @@ private void AddRelationshipSchemaToResourceObject(RelationshipAttribute relatio } } - private static Type GetRelationshipSchemaType(RelationshipAttribute relationship, Type resourceObjectType) + private Type GetRelationshipSchemaType(RelationshipAttribute relationship, Type resourceObjectType) { return resourceObjectType.GetGenericTypeDefinition().IsAssignableTo(typeof(ResourceObjectInResponse<>)) - ? RelationshipTypeFactory.Instance.GetForResponse(relationship) - : RelationshipTypeFactory.Instance.GetForRequest(relationship); + ? _relationshipTypeFactory.GetForResponse(relationship) + : _relationshipTypeFactory.GetForRequest(relationship); } private OpenApiSchema? GetReferenceSchemaForRelationship(Type relationshipSchemaType) diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs index f7db9e86ef..8e061fff57 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs @@ -15,7 +15,7 @@ internal sealed class ResourceObjectSchemaGenerator private readonly Func _resourceFieldObjectSchemaBuilderFactory; public ResourceObjectSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IResourceGraph resourceGraph, IJsonApiOptions options, - ISchemaRepositoryAccessor schemaRepositoryAccessor) + ISchemaRepositoryAccessor schemaRepositoryAccessor, ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider) { ArgumentGuard.NotNull(defaultSchemaGenerator); ArgumentGuard.NotNull(resourceGraph); @@ -31,7 +31,7 @@ public ResourceObjectSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IRe _allowClientGeneratedIds = options.AllowClientGeneratedIds; _resourceFieldObjectSchemaBuilderFactory = resourceTypeInfo => new ResourceFieldObjectSchemaBuilder(resourceTypeInfo, schemaRepositoryAccessor, - defaultSchemaGenerator, _resourceTypeSchemaGenerator, options); + defaultSchemaGenerator, _resourceTypeSchemaGenerator, options, resourceFieldValidationMetadataProvider); } public OpenApiSchema GenerateSchema(Type resourceObjectType) diff --git a/test/OpenApiClientTests/FakerFactory.cs b/test/OpenApiClientTests/FakerFactory.cs new file mode 100644 index 0000000000..c28adbaa60 --- /dev/null +++ b/test/OpenApiClientTests/FakerFactory.cs @@ -0,0 +1,41 @@ +using System.Reflection; +using AutoBogus; + +namespace OpenApiClientTests; + +internal sealed class FakerFactory +{ + public static FakerFactory Instance { get; } = new(); + + private FakerFactory() + { + } + + public AutoFaker Create() + where TTarget : class + { + return new AutoFaker(); + } + + public AutoFaker CreateForObjectWithResourceId() + where TTarget : class + { + return new AutoFaker().Configure(builder => builder.WithOverride(new ResourceStringIdOverride())); + } + + private sealed class ResourceStringIdOverride : AutoGeneratorOverride + { + private readonly IAutoFaker _idFaker = AutoFaker.Create(); + + public override bool CanOverride(AutoGenerateContext context) + { + PropertyInfo? resourceIdPropertyInfo = context.GenerateType.GetProperty("Id"); + return resourceIdPropertyInfo != null && resourceIdPropertyInfo.PropertyType == typeof(string); + } + + public override void Generate(AutoGenerateOverrideContext context) + { + ((dynamic)context.Instance).Id = _idFaker.Generate()!.ToString()!; + } + } +} diff --git a/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs b/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs index 55e882fedf..b6855c06d2 100644 --- a/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs +++ b/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs @@ -27,7 +27,7 @@ public async Task Disposed_request_document_registration_does_not_affect_request } }; - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + using (apiClient.WithPartialAttributeSerialization(requestDocument, airplane => airplane.AirtimeInHours)) { _ = await ApiResponse.TranslateAsync(async () => await apiClient.PatchAirplaneAsync(airplaneId, requestDocument)); @@ -72,7 +72,7 @@ public async Task Request_document_registration_can_be_used_for_multiple_request } }; - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + using (apiClient.WithPartialAttributeSerialization(requestDocument, airplane => airplane.AirtimeInHours)) { _ = await ApiResponse.TranslateAsync(async () => await apiClient.PatchAirplaneAsync(airplaneId, requestDocument)); @@ -128,10 +128,10 @@ public async Task Request_is_unaffected_by_request_document_registration_of_diff } }; - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument1, + using (apiClient.WithPartialAttributeSerialization(requestDocument1, airplane => airplane.AirtimeInHours)) { - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument2, + using (apiClient.WithPartialAttributeSerialization(requestDocument2, airplane => airplane.SerialNumber)) { } @@ -174,7 +174,7 @@ public async Task Attribute_values_can_be_changed_after_request_document_registr } }; - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + using (apiClient.WithPartialAttributeSerialization(requestDocument, airplane => airplane.IsInMaintenance)) { requestDocument.Data.Attributes.IsInMaintenance = false; @@ -223,10 +223,10 @@ public async Task Request_document_registration_is_unaffected_by_successive_regi } }; - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument1, + using (apiClient.WithPartialAttributeSerialization(requestDocument1, airplane => airplane.IsInMaintenance)) { - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument2, + using (apiClient.WithPartialAttributeSerialization(requestDocument2, airplane => airplane.AirtimeInHours)) { // Act @@ -265,7 +265,7 @@ public async Task Request_document_registration_is_unaffected_by_preceding_dispo } }; - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument1, + using (apiClient.WithPartialAttributeSerialization(requestDocument1, airplane => airplane.AirtimeInHours)) { _ = await ApiResponse.TranslateAsync(async () => await apiClient.PatchAirplaneAsync(airplaneId1, requestDocument1)); @@ -288,7 +288,7 @@ public async Task Request_document_registration_is_unaffected_by_preceding_dispo wrapper.ChangeResponse(HttpStatusCode.NoContent, null); - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument2, + using (apiClient.WithPartialAttributeSerialization(requestDocument2, airplane => airplane.SerialNumber)) { // Act @@ -327,7 +327,7 @@ public async Task Request_document_registration_is_unaffected_by_preceding_dispo } }; - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument1, + using (apiClient.WithPartialAttributeSerialization(requestDocument1, airplane => airplane.AirtimeInHours)) { _ = await ApiResponse.TranslateAsync(async () => await apiClient.PostAirplaneAsync(requestDocument1)); @@ -350,7 +350,7 @@ public async Task Request_document_registration_is_unaffected_by_preceding_dispo wrapper.ChangeResponse(HttpStatusCode.NoContent, null); - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument2, + using (apiClient.WithPartialAttributeSerialization(requestDocument2, airplane => airplane.SerialNumber)) { // Act @@ -401,10 +401,10 @@ public async Task Request_document_registration_is_unaffected_by_preceding_regis } }; - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument1, + using (apiClient.WithPartialAttributeSerialization(requestDocument1, airplane => airplane.SerialNumber)) { - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument2, + using (apiClient.WithPartialAttributeSerialization(requestDocument2, airplane => airplane.IsInMaintenance, airplane => airplane.AirtimeInHours)) { // Act diff --git a/test/OpenApiClientTests/LegacyClient/RequestTests.cs b/test/OpenApiClientTests/LegacyClient/RequestTests.cs index 8bb0c3f350..ab58b101e6 100644 --- a/test/OpenApiClientTests/LegacyClient/RequestTests.cs +++ b/test/OpenApiClientTests/LegacyClient/RequestTests.cs @@ -152,7 +152,7 @@ public async Task Partial_posting_resource_produces_expected_request() } }; - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + using (apiClient.WithPartialAttributeSerialization(requestDocument, airplane => airplane.SerialNumber)) { // Act @@ -203,7 +203,7 @@ public async Task Partial_patching_resource_produces_expected_request() } }; - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, + using (apiClient.WithPartialAttributeSerialization(requestDocument, airplane => airplane.SerialNumber, airplane => airplane.LastServicedAt, airplane => airplane.IsInMaintenance, airplane => airplane.AirtimeInHours)) { // Act diff --git a/test/OpenApiClientTests/LegacyClient/ResponseTests.cs b/test/OpenApiClientTests/LegacyClient/ResponseTests.cs index 10f86adca5..b112a23d05 100644 --- a/test/OpenApiClientTests/LegacyClient/ResponseTests.cs +++ b/test/OpenApiClientTests/LegacyClient/ResponseTests.cs @@ -149,7 +149,9 @@ public async Task Getting_resource_translates_response() // Arrange const string flightId = "ZvuH1"; const string departsAtInZuluTime = "2021-06-08T12:53:30.554Z"; + const string flightDestination = "Amsterdam"; const string arrivesAtWithUtcOffset = "2019-02-20T11:56:33.0721266+01:00"; + const string flightServiceOnBoard = "Movies"; const string responseBody = @"{ ""links"": { @@ -160,7 +162,9 @@ public async Task Getting_resource_translates_response() ""id"": """ + flightId + @""", ""attributes"": { ""departs-at"": """ + departsAtInZuluTime + @""", - ""arrives-at"": """ + arrivesAtWithUtcOffset + @""" + ""arrives-at"": """ + arrivesAtWithUtcOffset + @""", + ""final-destination"": """ + flightDestination + @""", + ""services-on-board"": [""" + flightServiceOnBoard + @"""] }, ""links"": { ""self"": """ + HostPrefix + @"flights/" + flightId + @""" @@ -181,8 +185,8 @@ public async Task Getting_resource_translates_response() document.Data.Relationships.Should().BeNull(); document.Data.Attributes.DepartsAt.Should().Be(DateTimeOffset.Parse(departsAtInZuluTime)); document.Data.Attributes.ArrivesAt.Should().Be(DateTimeOffset.Parse(arrivesAtWithUtcOffset)); - document.Data.Attributes.ServicesOnBoard.Should().BeNull(); - document.Data.Attributes.FinalDestination.Should().BeNull(); + document.Data.Attributes.ServicesOnBoard.Should().Contain(flightServiceOnBoard); + document.Data.Attributes.FinalDestination.Should().Be(flightDestination); document.Data.Attributes.StopOverDestination.Should().BeNull(); document.Data.Attributes.OperatedBy.Should().Be(default); } diff --git a/test/OpenApiClientTests/LegacyClient/swagger.g.json b/test/OpenApiClientTests/LegacyClient/swagger.g.json index f95214e8d4..043f6394eb 100644 --- a/test/OpenApiClientTests/LegacyClient/swagger.g.json +++ b/test/OpenApiClientTests/LegacyClient/swagger.g.json @@ -1970,6 +1970,9 @@ "additionalProperties": false }, "airplane-attributes-in-response": { + "required": [ + "name" + ], "type": "object", "properties": { "name": { @@ -2238,6 +2241,10 @@ "additionalProperties": false }, "flight-attendant-attributes-in-response": { + "required": [ + "email-address", + "profile-image-url" + ], "type": "object", "properties": { "email-address": { @@ -2557,6 +2564,10 @@ "additionalProperties": false }, "flight-attributes-in-response": { + "required": [ + "final-destination", + "services-on-board" + ], "type": "object", "properties": { "final-destination": { @@ -2815,6 +2826,9 @@ "additionalProperties": false }, "flight-relationships-in-response": { + "required": [ + "purser" + ], "type": "object", "properties": { "cabin-crew-members": { diff --git a/test/OpenApiClientTests/NamingConventions/CamelCase/swagger.g.json b/test/OpenApiClientTests/NamingConventions/CamelCase/swagger.g.json index d2850c032d..5d2569050d 100644 --- a/test/OpenApiClientTests/NamingConventions/CamelCase/swagger.g.json +++ b/test/OpenApiClientTests/NamingConventions/CamelCase/swagger.g.json @@ -1001,6 +1001,9 @@ "additionalProperties": false }, "staffMemberAttributesInResponse": { + "required": [ + "name" + ], "type": "object", "properties": { "name": { @@ -1190,6 +1193,9 @@ "additionalProperties": false }, "supermarketAttributesInResponse": { + "required": [ + "nameOfCity" + ], "type": "object", "properties": { "nameOfCity": { @@ -1380,6 +1386,9 @@ "additionalProperties": false }, "supermarketRelationshipsInResponse": { + "required": [ + "storeManager" + ], "type": "object", "properties": { "storeManager": { diff --git a/test/OpenApiClientTests/NamingConventions/KebabCase/swagger.g.json b/test/OpenApiClientTests/NamingConventions/KebabCase/swagger.g.json index 1d7feb51f0..9d7656c612 100644 --- a/test/OpenApiClientTests/NamingConventions/KebabCase/swagger.g.json +++ b/test/OpenApiClientTests/NamingConventions/KebabCase/swagger.g.json @@ -1001,6 +1001,9 @@ "additionalProperties": false }, "staff-member-attributes-in-response": { + "required": [ + "name" + ], "type": "object", "properties": { "name": { @@ -1190,6 +1193,9 @@ "additionalProperties": false }, "supermarket-attributes-in-response": { + "required": [ + "name-of-city" + ], "type": "object", "properties": { "name-of-city": { @@ -1380,6 +1386,9 @@ "additionalProperties": false }, "supermarket-relationships-in-response": { + "required": [ + "store-manager" + ], "type": "object", "properties": { "store-manager": { diff --git a/test/OpenApiClientTests/NamingConventions/PascalCase/swagger.g.json b/test/OpenApiClientTests/NamingConventions/PascalCase/swagger.g.json index fa5041afad..ae79e8470b 100644 --- a/test/OpenApiClientTests/NamingConventions/PascalCase/swagger.g.json +++ b/test/OpenApiClientTests/NamingConventions/PascalCase/swagger.g.json @@ -1001,6 +1001,9 @@ "additionalProperties": false }, "StaffMemberAttributesInResponse": { + "required": [ + "Name" + ], "type": "object", "properties": { "Name": { @@ -1190,6 +1193,9 @@ "additionalProperties": false }, "SupermarketAttributesInResponse": { + "required": [ + "NameOfCity" + ], "type": "object", "properties": { "NameOfCity": { @@ -1380,6 +1386,9 @@ "additionalProperties": false }, "SupermarketRelationshipsInResponse": { + "required": [ + "StoreManager" + ], "type": "object", "properties": { "StoreManager": { diff --git a/test/OpenApiClientTests/ObjectExtensions.cs b/test/OpenApiClientTests/ObjectExtensions.cs new file mode 100644 index 0000000000..cdf729c09b --- /dev/null +++ b/test/OpenApiClientTests/ObjectExtensions.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using JsonApiDotNetCore.OpenApi.Client; + +namespace OpenApiClientTests; + +internal static class ObjectExtensions +{ + public static object? GetPropertyValue(this object source, string propertyName) + { + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(propertyName); + + PropertyInfo propertyInfo = source.GetType().GetProperties().Single(attribute => attribute.Name == propertyName); + + return propertyInfo.GetValue(source); + } + + public static void SetPropertyValue(this object source, string propertyName, object? value) + { + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(propertyName); + + PropertyInfo propertyInfo = source.GetType().GetProperties().Single(attribute => attribute.Name == propertyName); + + propertyInfo.SetValue(source, value); + } + + public static object? GetDefaultValueForProperty(this object source, string propertyName) + { + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(propertyName); + + PropertyInfo propertyInfo = source.GetType().GetProperties().Single(attribute => attribute.Name == propertyName); + + return Activator.CreateInstance(propertyInfo.PropertyType); + } +} diff --git a/test/OpenApiClientTests/OpenApiClientTests.cs b/test/OpenApiClientTests/OpenApiClientTests.cs new file mode 100644 index 0000000000..bb0b5d61a1 --- /dev/null +++ b/test/OpenApiClientTests/OpenApiClientTests.cs @@ -0,0 +1,20 @@ +using System.Linq.Expressions; + +namespace OpenApiClientTests; + +public class OpenApiClientTests +{ + private const string AttributesObjectParameterName = "attributesObject"; + + protected static Expression> CreateIncludedAttributeSelector(string propertyName) + where TAttributesObject : class + { + Type attributesObjectType = typeof(TAttributesObject); + + ParameterExpression parameter = Expression.Parameter(attributesObjectType, AttributesObjectParameterName); + MemberExpression property = Expression.Property(parameter, propertyName); + UnaryExpression toObjectConversion = Expression.Convert(property, typeof(object)); + + return Expression.Lambda>(toObjectConversion, parameter); + } +} diff --git a/test/OpenApiClientTests/OpenApiClientTests.csproj b/test/OpenApiClientTests/OpenApiClientTests.csproj index 4e726ad900..c5e7f0acdd 100644 --- a/test/OpenApiClientTests/OpenApiClientTests.csproj +++ b/test/OpenApiClientTests/OpenApiClientTests.csproj @@ -9,6 +9,7 @@ + @@ -52,19 +53,34 @@ NSwagCSharp /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions - - OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.GeneratedCode - NullableReferenceTypesEnabledClient - NullableReferenceTypesEnabledClient.cs + + NrtOffMsvOffClient + NrtOffMsvOffClient.cs NSwagCSharp - /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true + OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode + /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false - - OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.GeneratedCode - NullableReferenceTypesDisabledClient - NullableReferenceTypesDisabledClient.cs + + NrtOffMsvOnClient + NrtOffMsvOnClient.cs NSwagCSharp + OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false + + NrtOnMsvOffClient + NrtOnMsvOffClient.cs + NSwagCSharp + OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled.GeneratedCode + /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true + + + NrtOnMsvOnClient + NrtOnMsvOnClient.cs + NSwagCSharp + OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled.GeneratedCode + /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true + - \ No newline at end of file + + diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/CreateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/CreateResourceTests.cs new file mode 100644 index 0000000000..8888d8f56d --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/CreateResourceTests.cs @@ -0,0 +1,528 @@ +using System.Linq.Expressions; +using System.Net; +using System.Text.Json; +using FluentAssertions; +using FluentAssertions.Specialized; +using Newtonsoft.Json; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled; + +public sealed class CreateResourceTests : OpenApiClientTests +{ + private readonly ResourceFieldValidationFakers _fakers = new(); + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), "referenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), "requiredReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), "nullableValueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), "requiredNullableValueType")] + public async Task Can_clear_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + Expression> includeAttributeSelector = + CreateIncludedAttributeSelector(attributePropertyName); + + using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null)); + }); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")] + public async Task Can_set_default_value_to_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? defaultValue = requestDocument.Data.Attributes.GetDefaultValueForProperty(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + Expression> includeAttributeSelector = + CreateIncludedAttributeSelector(attributePropertyName); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ShouldBeInteger(0)); + }); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), "referenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), "nullableValueType")] + public async Task Can_exclude_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceAttributesInPostRequest emptyAttributesObject = new(); + object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), "requiredReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), "requiredNullableValueType")] + public async Task Cannot_exclude_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceAttributesInPostRequest emptyAttributesObject = new(); + object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + InvalidOperationException exception = assertion.Subject.Single(); + exception.Message.Should().Be($"The following property should not be omitted: data.attributes.{jsonPropertyName}."); + } + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), nameof(ResourceRelationshipsInPostRequest.ToOne.Data), "toOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), nameof(ResourceRelationshipsInPostRequest.RequiredToOne.Data), "requiredToOne")] + public async Task Can_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + // Act + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject => + { + relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), nameof(ResourceRelationshipsInPostRequest.ToOne.Data), "toOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), nameof(ResourceRelationshipsInPostRequest.RequiredToOne.Data), "requiredToOne")] + public async Task Can_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject => + { + relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")] + public async Task Cannot_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'."); + } + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")] + public async Task Cannot_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'."); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), "toOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")] + public async Task Can_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + // Act + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), "toOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")] + public async Task Can_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), "requiredToOne", Skip = "Known limitation")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany", Skip = "Known limitation")] + public async Task Cannot_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'."); + } + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), "requiredToOne", Skip = "Known limitation")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany", Skip = "Known limitation")] + public async Task Cannot_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'."); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/GeneratedCode/NrtOffMsvOffClient.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/GeneratedCode/NrtOffMsvOffClient.cs new file mode 100644 index 0000000000..4bea9db090 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/GeneratedCode/NrtOffMsvOffClient.cs @@ -0,0 +1,14 @@ +using JsonApiDotNetCore.OpenApi.Client; +using Newtonsoft.Json; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode; + +internal partial class NrtOffMsvOffClient : JsonApiClient +{ + partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) + { + SetSerializerSettingsForJsonApi(settings); + + settings.Formatting = Formatting.Indented; + } +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs new file mode 100644 index 0000000000..8079c4896c --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using FluentAssertions; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled; + +public sealed class NullabilityTests +{ + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), NullabilityState.Unknown)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), NullabilityState.Unknown)] + [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), NullabilityState.Nullable)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), NullabilityState.Nullable)] + public void Nullability_of_generated_property_is_as_expected(string propertyName, NullabilityState expectedState) + { + PropertyInfo[] properties = typeof(ResourceAttributesInPostRequest).GetProperties(); + PropertyInfo property = properties.Single(property => property.Name == propertyName); + property.Should().HaveNullabilityState(expectedState); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs new file mode 100644 index 0000000000..1bc729cbd9 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs @@ -0,0 +1,25 @@ +using Bogus; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode; +using TestBuildingBlocks; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled; + +internal sealed class ResourceFieldValidationFakers +{ + private readonly Lazy> _lazyPostAttributesFaker = new(() => + FakerFactory.Instance.Create()); + + private readonly Lazy> _lazyPatchAttributesFaker = new(() => + FakerFactory.Instance.Create()); + + private readonly Lazy> _lazyNullableToOneFaker = new(() => + FakerFactory.Instance.CreateForObjectWithResourceId()); + + private readonly Lazy> _lazyToManyFaker = new(() => + FakerFactory.Instance.CreateForObjectWithResourceId()); + + public Faker PostAttributes => _lazyPostAttributesFaker.Value; + public Faker PatchAttributes => _lazyPatchAttributesFaker.Value; + public Faker NullableToOne => _lazyNullableToOneFaker.Value; + public Faker ToMany => _lazyToManyFaker.Value; +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/UpdateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/UpdateResourceTests.cs new file mode 100644 index 0000000000..68e71e97b9 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/UpdateResourceTests.cs @@ -0,0 +1,146 @@ +using System.Net; +using System.Text.Json; +using FluentAssertions; +using FluentAssertions.Specialized; +using Newtonsoft.Json; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled; + +public sealed class UpdateResourceTests +{ + private readonly ResourceFieldValidationFakers _fakers = new(); + + [Fact] + public async Task Cannot_exclude_id() + { + // Arrange + var requestDocument = new ResourcePatchRequestDocument + { + Data = new ResourceDataInPatchRequest + { + Id = "1", + Attributes = _fakers.PatchAttributes.Generate(), + Relationships = new ResourceRelationshipsInPatchRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceDataInPatchRequest emptyDataObject = new(); + requestDocument.Data.Id = emptyDataObject.Id; + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + // Act + Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(999, requestDocument)); + + // Assert + await action.Should().ThrowAsync(); + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'id'. Property requires a value. Path 'data'."); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPatchRequest.ReferenceType), "referenceType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredReferenceType), "requiredReferenceType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.ValueType), "valueType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredValueType), "requiredValueType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.NullableValueType), "nullableValueType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredNullableValueType), "requiredNullableValueType")] + public async Task Can_exclude_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePatchRequestDocument + { + Data = new ResourceDataInPatchRequest + { + Id = "1", + Attributes = _fakers.PatchAttributes.Generate(), + Relationships = new ResourceRelationshipsInPatchRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceAttributesInPatchRequest emptyAttributesObject = new(); + object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.ToOne), "toOne")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredToOne), "requiredToOne")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.ToMany), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredToMany), "requiredToMany")] + public async Task Can_exclude_relationship(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePatchRequestDocument + { + Data = new ResourceDataInPatchRequest + { + Id = "1", + Attributes = _fakers.PatchAttributes.Generate(), + Relationships = new ResourceRelationshipsInPatchRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPatchRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath(jsonPropertyName); + }); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/swagger.g.json new file mode 100644 index 0000000000..572f1c7615 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/swagger.g.json @@ -0,0 +1,1677 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenApiTests", + "version": "1.0" + }, + "paths": { + "/Resource": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourceCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourceCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "Resource" + ], + "operationId": "postResource", + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourcePostRequestDocument" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "No Content" + } + } + } + }, + "/Resource/{id}": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResource", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResource", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Resource" + ], + "operationId": "patchResource", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourcePatchRequestDocument" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "Resource" + ], + "operationId": "deleteResource", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/Resource/{id}/requiredToMany": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceRequiredToMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceRequiredToMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" + } + } + } + } + } + } + }, + "/Resource/{id}/relationships/requiredToMany": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceRequiredToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceRequiredToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "Resource" + ], + "operationId": "postResourceRequiredToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "Resource" + ], + "operationId": "patchResourceRequiredToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "Resource" + ], + "operationId": "deleteResourceRequiredToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/Resource/{id}/requiredToOne": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceRequiredToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceRequiredToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/Resource/{id}/relationships/requiredToOne": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceRequiredToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceRequiredToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Resource" + ], + "operationId": "patchResourceRequiredToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/Resource/{id}/toMany": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceToMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceToMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" + } + } + } + } + } + } + }, + "/Resource/{id}/relationships/toMany": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "Resource" + ], + "operationId": "postResourceToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "Resource" + ], + "operationId": "patchResourceToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "Resource" + ], + "operationId": "deleteResourceToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/Resource/{id}/toOne": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/Resource/{id}/relationships/toOne": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Resource" + ], + "operationId": "patchResourceToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "components": { + "schemas": { + "emptyResourceCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/emptyResourceDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "emptyResourceDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/emptyResourceResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "emptyResourceIdentifier": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/emptyResourceResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "emptyResourceIdentifierCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + } + }, + "additionalProperties": false + }, + "emptyResourceResourceType": { + "enum": [ + "emptyResources" + ], + "type": "string" + }, + "jsonapiObject": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "profile": { + "type": "array", + "items": { + "type": "string" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "linksInRelationshipObject": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceCollectionDocument": { + "required": [ + "first", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "first": { + "minLength": 1, + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceDocument": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceIdentifierCollectionDocument": { + "required": [ + "first", + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + }, + "first": { + "minLength": 1, + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceIdentifierDocument": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceObject": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "nullValue": { + "not": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "object" + }, + { + "type": "array" + } + ], + "items": { } + }, + "nullable": true + }, + "nullableEmptyResourceIdentifierResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + }, + "additionalProperties": false + }, + "nullableEmptyResourceSecondaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/emptyResourceDataInResponse" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "nullableToOneEmptyResourceInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + } + }, + "additionalProperties": false + }, + "nullableToOneEmptyResourceInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "resourceAttributesInPatchRequest": { + "type": "object", + "properties": { + "referenceType": { + "type": "string", + "nullable": true + }, + "requiredReferenceType": { + "minLength": 1, + "type": "string", + "nullable": true + }, + "valueType": { + "type": "integer", + "format": "int32" + }, + "requiredValueType": { + "type": "integer", + "format": "int32" + }, + "nullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredNullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false + }, + "resourceAttributesInPostRequest": { + "required": [ + "requiredNullableValueType", + "requiredReferenceType", + "requiredValueType" + ], + "type": "object", + "properties": { + "referenceType": { + "type": "string", + "nullable": true + }, + "requiredReferenceType": { + "minLength": 1, + "type": "string", + "nullable": true + }, + "valueType": { + "type": "integer", + "format": "int32" + }, + "requiredValueType": { + "type": "integer", + "format": "int32" + }, + "nullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredNullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false + }, + "resourceAttributesInResponse": { + "required": [ + "requiredNullableValueType", + "requiredReferenceType", + "requiredValueType" + ], + "type": "object", + "properties": { + "referenceType": { + "type": "string", + "nullable": true + }, + "requiredReferenceType": { + "minLength": 1, + "type": "string", + "nullable": true + }, + "valueType": { + "type": "integer", + "format": "int32" + }, + "requiredValueType": { + "type": "integer", + "format": "int32" + }, + "nullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredNullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false + }, + "resourceCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/resourceDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "resourceDataInPatchRequest": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/resourceResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/resourceAttributesInPatchRequest" + }, + "relationships": { + "$ref": "#/components/schemas/resourceRelationshipsInPatchRequest" + } + }, + "additionalProperties": false + }, + "resourceDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/resourceResourceType" + }, + "attributes": { + "$ref": "#/components/schemas/resourceAttributesInPostRequest" + }, + "relationships": { + "$ref": "#/components/schemas/resourceRelationshipsInPostRequest" + } + }, + "additionalProperties": false + }, + "resourceDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/resourceResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/resourceAttributesInResponse" + }, + "relationships": { + "$ref": "#/components/schemas/resourceRelationshipsInResponse" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "resourcePatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/resourceDataInPatchRequest" + } + }, + "additionalProperties": false + }, + "resourcePostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/resourceDataInPostRequest" + } + }, + "additionalProperties": false + }, + "resourcePrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/resourceDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "resourceRelationshipsInPatchRequest": { + "type": "object", + "properties": { + "toOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + }, + "requiredToOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + }, + "toMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + }, + "requiredToMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + }, + "additionalProperties": false + }, + "resourceRelationshipsInPostRequest": { + "required": [ + "requiredToMany", + "requiredToOne" + ], + "type": "object", + "properties": { + "toOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + }, + "requiredToOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + }, + "toMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + }, + "requiredToMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + }, + "additionalProperties": false + }, + "resourceRelationshipsInResponse": { + "required": [ + "requiredToMany", + "requiredToOne" + ], + "type": "object", + "properties": { + "toOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + }, + "requiredToOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + }, + "toMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + }, + "requiredToMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + } + }, + "additionalProperties": false + }, + "resourceResourceType": { + "enum": [ + "Resource" + ], + "type": "string" + }, + "toManyEmptyResourceInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + } + }, + "additionalProperties": false + }, + "toManyEmptyResourceInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + } + } + } +} \ No newline at end of file diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/CreateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/CreateResourceTests.cs new file mode 100644 index 0000000000..f3a5130486 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/CreateResourceTests.cs @@ -0,0 +1,569 @@ +using System.Linq.Expressions; +using System.Net; +using System.Text.Json; +using FluentAssertions; +using FluentAssertions.Specialized; +using Newtonsoft.Json; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled; + +public sealed class CreateResourceTests : OpenApiClientTests +{ + private readonly ResourceFieldValidationFakers _fakers = new(); + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), "referenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), "nullableValueType")] + public async Task Can_clear_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + Expression> includeAttributeSelector = + CreateIncludedAttributeSelector(attributePropertyName); + + using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null)); + }); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), "requiredNullableValueType")] + public async Task Can_set_default_value_to_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? defaultValue = requestDocument.Data.Attributes.GetDefaultValueForProperty(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + Expression> includeAttributeSelector = + CreateIncludedAttributeSelector(attributePropertyName); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ShouldBeInteger(0)); + }); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), "requiredReferenceType")] + public async Task Cannot_clear_attribute(string clrPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + requestDocument.Data.Attributes.SetPropertyValue(clrPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + Expression> includeAttributeSelector = + CreateIncludedAttributeSelector(clrPropertyName); + + using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Contain($"Cannot write a null value for property '{jsonPropertyName}'."); + } + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), "referenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), "nullableValueType")] + public async Task Can_exclude_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceAttributesInPostRequest emptyAttributesObject = new(); + object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), "requiredReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), "requiredNullableValueType")] + public async Task Cannot_exclude_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceAttributesInPostRequest emptyAttributesObject = new(); + object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + InvalidOperationException exception = assertion.Subject.Single(); + exception.Message.Should().Be($"The following property should not be omitted: data.attributes.{jsonPropertyName}."); + } + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), nameof(ResourceRelationshipsInPostRequest.ToOne.Data), "toOne")] + public async Task Can_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + // Act + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject => + { + relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), nameof(ResourceRelationshipsInPostRequest.ToOne.Data), "toOne")] + public async Task Can_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject => + { + relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), nameof(ResourceRelationshipsInPostRequest.RequiredToOne.Data), "requiredToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")] + public async Task Cannot_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'."); + } + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), nameof(ResourceRelationshipsInPostRequest.RequiredToOne.Data), "requiredToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")] + public async Task Cannot_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'."); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), "toOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany")] + public async Task Can_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + // Act + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), "toOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany")] + public async Task Can_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), "requiredToOne")] + public async Task Cannot_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'."); + } + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), "requiredToOne")] + public async Task Cannot_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'."); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/GeneratedCode/NrtOffMsvOnClient.cs similarity index 58% rename from test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/GeneratedCode/NrtOffMsvOnClient.cs index 8c6b98a237..94bb5efc37 100644 --- a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/GeneratedCode/NullableReferenceTypesDisabledClient.cs +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/GeneratedCode/NrtOffMsvOnClient.cs @@ -1,9 +1,9 @@ using JsonApiDotNetCore.OpenApi.Client; using Newtonsoft.Json; -namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.GeneratedCode; +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode; -internal partial class NullableReferenceTypesDisabledClient : JsonApiClient +internal partial class NrtOffMsvOnClient : JsonApiClient { partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) { diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs new file mode 100644 index 0000000000..53252ee439 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using FluentAssertions; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled; + +public sealed class NullabilityTests +{ + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), NullabilityState.Unknown)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), NullabilityState.Unknown)] + [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), NullabilityState.Nullable)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), NullabilityState.NotNull)] + public void Nullability_of_generated_property_is_as_expected(string propertyName, NullabilityState expectedState) + { + PropertyInfo[] properties = typeof(ResourceAttributesInPostRequest).GetProperties(); + PropertyInfo property = properties.Single(property => property.Name == propertyName); + property.Should().HaveNullabilityState(expectedState); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs new file mode 100644 index 0000000000..2960441a6c --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs @@ -0,0 +1,29 @@ +using Bogus; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode; +using TestBuildingBlocks; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled; + +internal sealed class ResourceFieldValidationFakers +{ + private readonly Lazy> _lazyPostAttributesFaker = new(() => + FakerFactory.Instance.Create()); + + private readonly Lazy> _lazyPatchAttributesFaker = new(() => + FakerFactory.Instance.Create()); + + private readonly Lazy> _lazyToOneFaker = new(() => + FakerFactory.Instance.CreateForObjectWithResourceId()); + + private readonly Lazy> _lazyNullableToOneFaker = new(() => + FakerFactory.Instance.CreateForObjectWithResourceId()); + + private readonly Lazy> _lazyToManyFaker = new(() => + FakerFactory.Instance.CreateForObjectWithResourceId()); + + public Faker PostAttributes => _lazyPostAttributesFaker.Value; + public Faker PatchAttributes => _lazyPatchAttributesFaker.Value; + public Faker ToOne => _lazyToOneFaker.Value; + public Faker NullableToOne => _lazyNullableToOneFaker.Value; + public Faker ToMany => _lazyToManyFaker.Value; +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/UpdateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/UpdateResourceTests.cs new file mode 100644 index 0000000000..81f54f6dd0 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/UpdateResourceTests.cs @@ -0,0 +1,146 @@ +using System.Net; +using System.Text.Json; +using FluentAssertions; +using FluentAssertions.Specialized; +using Newtonsoft.Json; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled; + +public sealed class UpdateResourceTests +{ + private readonly ResourceFieldValidationFakers _fakers = new(); + + [Fact] + public async Task Cannot_exclude_id() + { + // Arrange + var requestDocument = new ResourcePatchRequestDocument + { + Data = new ResourceDataInPatchRequest + { + Id = "1", + Attributes = _fakers.PatchAttributes.Generate(), + Relationships = new ResourceRelationshipsInPatchRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceDataInPatchRequest emptyDataObject = new(); + requestDocument.Data.Id = emptyDataObject.Id; + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + // Act + Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(999, requestDocument)); + + // Assert + await action.Should().ThrowAsync(); + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'id'. Property requires a value. Path 'data'."); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPatchRequest.ReferenceType), "referenceType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredReferenceType), "requiredReferenceType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.ValueType), "valueType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredValueType), "requiredValueType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.NullableValueType), "nullableValueType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredNullableValueType), "requiredNullableValueType")] + public async Task Can_exclude_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePatchRequestDocument + { + Data = new ResourceDataInPatchRequest + { + Id = "1", + Attributes = _fakers.PatchAttributes.Generate(), + Relationships = new ResourceRelationshipsInPatchRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceAttributesInPatchRequest emptyAttributesObject = new(); + object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.ToOne), "toOne")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredToOne), "requiredToOne")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.ToMany), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredToMany), "requiredToMany")] + public async Task Can_exclude_relationship(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePatchRequestDocument + { + Data = new ResourceDataInPatchRequest + { + Id = "1", + Attributes = _fakers.PatchAttributes.Generate(), + Relationships = new ResourceRelationshipsInPatchRequest + { + ToOne = _fakers.NullableToOne.Generate(), + RequiredToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPatchRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath(jsonPropertyName); + }); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/swagger.g.json new file mode 100644 index 0000000000..f366cc19e2 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/swagger.g.json @@ -0,0 +1,1744 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenApiTests", + "version": "1.0" + }, + "paths": { + "/Resource": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourceCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceCollection", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourceCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "Resource" + ], + "operationId": "postResource", + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourcePostRequestDocument" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "No Content" + } + } + } + }, + "/Resource/{id}": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResource", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResource", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Resource" + ], + "operationId": "patchResource", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourcePatchRequestDocument" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "Resource" + ], + "operationId": "deleteResource", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/Resource/{id}/requiredToMany": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceRequiredToMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceRequiredToMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" + } + } + } + } + } + } + }, + "/Resource/{id}/relationships/requiredToMany": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceRequiredToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceRequiredToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "Resource" + ], + "operationId": "postResourceRequiredToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "Resource" + ], + "operationId": "patchResourceRequiredToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "Resource" + ], + "operationId": "deleteResourceRequiredToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/Resource/{id}/requiredToOne": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceRequiredToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceRequiredToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/Resource/{id}/relationships/requiredToOne": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceRequiredToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceRequiredToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Resource" + ], + "operationId": "patchResourceRequiredToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/Resource/{id}/toMany": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceToMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceToMany", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" + } + } + } + } + } + } + }, + "/Resource/{id}/relationships/toMany": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" + } + } + } + } + } + }, + "post": { + "tags": [ + "Resource" + ], + "operationId": "postResourceToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "tags": [ + "Resource" + ], + "operationId": "patchResourceToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "tags": [ + "Resource" + ], + "operationId": "deleteResourceToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/Resource/{id}/toOne": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument" + } + } + } + } + } + } + }, + "/Resource/{id}/relationships/toOne": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument" + } + } + } + } + } + }, + "head": { + "tags": [ + "Resource" + ], + "operationId": "headResourceToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Resource" + ], + "operationId": "patchResourceToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "components": { + "schemas": { + "emptyResourceCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/emptyResourceDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "emptyResourceDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/emptyResourceResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "emptyResourceIdentifier": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/emptyResourceResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "emptyResourceIdentifierCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + } + }, + "additionalProperties": false + }, + "emptyResourceIdentifierResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/emptyResourceIdentifier" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + }, + "additionalProperties": false + }, + "emptyResourceResourceType": { + "enum": [ + "emptyResources" + ], + "type": "string" + }, + "emptyResourceSecondaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/emptyResourceDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "jsonapiObject": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "profile": { + "type": "array", + "items": { + "type": "string" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "linksInRelationshipObject": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceCollectionDocument": { + "required": [ + "first", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "first": { + "minLength": 1, + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceDocument": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceIdentifierCollectionDocument": { + "required": [ + "first", + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + }, + "first": { + "minLength": 1, + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceIdentifierDocument": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceObject": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "nullValue": { + "not": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "object" + }, + { + "type": "array" + } + ], + "items": { } + }, + "nullable": true + }, + "nullableEmptyResourceIdentifierResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + }, + "additionalProperties": false + }, + "nullableEmptyResourceSecondaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/emptyResourceDataInResponse" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "nullableToOneEmptyResourceInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + } + }, + "additionalProperties": false + }, + "nullableToOneEmptyResourceInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + }, + { + "$ref": "#/components/schemas/nullValue" + } + ] + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "resourceAttributesInPatchRequest": { + "type": "object", + "properties": { + "referenceType": { + "type": "string", + "nullable": true + }, + "requiredReferenceType": { + "minLength": 1, + "type": "string" + }, + "valueType": { + "type": "integer", + "format": "int32" + }, + "requiredValueType": { + "type": "integer", + "format": "int32" + }, + "nullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredNullableValueType": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "resourceAttributesInPostRequest": { + "required": [ + "requiredNullableValueType", + "requiredReferenceType" + ], + "type": "object", + "properties": { + "referenceType": { + "type": "string", + "nullable": true + }, + "requiredReferenceType": { + "minLength": 1, + "type": "string" + }, + "valueType": { + "type": "integer", + "format": "int32" + }, + "requiredValueType": { + "type": "integer", + "format": "int32" + }, + "nullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredNullableValueType": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "resourceAttributesInResponse": { + "required": [ + "requiredNullableValueType", + "requiredReferenceType" + ], + "type": "object", + "properties": { + "referenceType": { + "type": "string", + "nullable": true + }, + "requiredReferenceType": { + "minLength": 1, + "type": "string" + }, + "valueType": { + "type": "integer", + "format": "int32" + }, + "requiredValueType": { + "type": "integer", + "format": "int32" + }, + "nullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredNullableValueType": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "resourceCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/resourceDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "resourceDataInPatchRequest": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/resourceResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/resourceAttributesInPatchRequest" + }, + "relationships": { + "$ref": "#/components/schemas/resourceRelationshipsInPatchRequest" + } + }, + "additionalProperties": false + }, + "resourceDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/resourceResourceType" + }, + "attributes": { + "$ref": "#/components/schemas/resourceAttributesInPostRequest" + }, + "relationships": { + "$ref": "#/components/schemas/resourceRelationshipsInPostRequest" + } + }, + "additionalProperties": false + }, + "resourceDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/resourceResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/resourceAttributesInResponse" + }, + "relationships": { + "$ref": "#/components/schemas/resourceRelationshipsInResponse" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "resourcePatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/resourceDataInPatchRequest" + } + }, + "additionalProperties": false + }, + "resourcePostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/resourceDataInPostRequest" + } + }, + "additionalProperties": false + }, + "resourcePrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/resourceDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "resourceRelationshipsInPatchRequest": { + "type": "object", + "properties": { + "toOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + }, + "requiredToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + }, + "toMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + }, + "requiredToMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + }, + "additionalProperties": false + }, + "resourceRelationshipsInPostRequest": { + "required": [ + "requiredToOne" + ], + "type": "object", + "properties": { + "toOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + }, + "requiredToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + }, + "toMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + }, + "requiredToMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + }, + "additionalProperties": false + }, + "resourceRelationshipsInResponse": { + "required": [ + "requiredToOne" + ], + "type": "object", + "properties": { + "toOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + }, + "requiredToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + }, + "toMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + }, + "requiredToMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + } + }, + "additionalProperties": false + }, + "resourceResourceType": { + "enum": [ + "Resource" + ], + "type": "string" + }, + "toManyEmptyResourceInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + } + }, + "additionalProperties": false + }, + "toManyEmptyResourceInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "toOneEmptyResourceInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + }, + "additionalProperties": false + }, + "toOneEmptyResourceInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/emptyResourceIdentifier" + }, + "links": { + "$ref": "#/components/schemas/linksInRelationshipObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + } + } + } +} \ No newline at end of file diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/CreateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/CreateResourceTests.cs new file mode 100644 index 0000000000..27e9c06b5c --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/CreateResourceTests.cs @@ -0,0 +1,613 @@ +using System.Linq.Expressions; +using System.Net; +using System.Text.Json; +using FluentAssertions; +using FluentAssertions.Specialized; +using Newtonsoft.Json; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled; + +public sealed class CreateResourceTests : OpenApiClientTests +{ + private readonly ResourceFieldValidationFakers _fakers = new(); + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableReferenceType), "nullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableReferenceType), "requiredNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), "nullableValueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), "requiredNullableValueType")] + public async Task Can_clear_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + Expression> includeAttributeSelector = + CreateIncludedAttributeSelector(attributePropertyName); + + using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null)); + }); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")] + public async Task Can_set_default_value_to_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? defaultValue = requestDocument.Data.Attributes.GetDefaultValueForProperty(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + Expression> includeAttributeSelector = + CreateIncludedAttributeSelector(attributePropertyName); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ShouldBeInteger(0)); + }); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.NonNullableReferenceType), "nonNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNonNullableReferenceType), "requiredNonNullableReferenceType")] + public async Task Cannot_clear_attribute(string clrPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + requestDocument.Data.Attributes.SetPropertyValue(clrPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + Expression> includeAttributeSelector = + CreateIncludedAttributeSelector(clrPropertyName); + + using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Contain($"Cannot write a null value for property '{jsonPropertyName}'."); + } + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.NonNullableReferenceType), "nonNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableReferenceType), "nullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), "nullableValueType")] + public async Task Can_exclude_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceAttributesInPostRequest emptyAttributesObject = new(); + object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNonNullableReferenceType), "requiredNonNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableReferenceType), "requiredNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), "requiredNullableValueType")] + public async Task Cannot_exclude_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceAttributesInPostRequest emptyAttributesObject = new(); + object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + InvalidOperationException exception = assertion.Subject.Single(); + exception.Message.Should().Be($"The following property should not be omitted: data.attributes.{jsonPropertyName}."); + } + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), nameof(ResourceRelationshipsInPostRequest.NullableToOne.Data), "nullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne.Data), + "requiredNullableToOne")] + public async Task Can_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + // Act + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject => + { + relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), nameof(ResourceRelationshipsInPostRequest.NullableToOne.Data), "nullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne.Data), + "requiredNullableToOne")] + public async Task Can_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject => + { + relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), nameof(ResourceRelationshipsInPostRequest.NonNullableToOne.Data), + "nonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne.Data), + "requiredNonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")] + public async Task Cannot_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'."); + } + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), nameof(ResourceRelationshipsInPostRequest.NonNullableToOne.Data), + "nonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne.Data), + "requiredNonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")] + public async Task Cannot_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'."); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), "nonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), "nullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")] + public async Task Can_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + // Act + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), "nonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), "nullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")] + public async Task Can_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), "requiredNonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), "requiredNullableToOne", Skip = "Known limitation")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany", Skip = "Known limitation")] + public async Task Cannot_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'."); + } + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), "requiredNonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), "requiredNullableToOne", Skip = "Known limitation")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany", Skip = "Known limitation")] + public async Task Cannot_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'."); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/GeneratedCode/NrtOnMsvOffClient.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/GeneratedCode/NrtOnMsvOffClient.cs new file mode 100644 index 0000000000..c44cdbe3d7 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/GeneratedCode/NrtOnMsvOffClient.cs @@ -0,0 +1,14 @@ +using JsonApiDotNetCore.OpenApi.Client; +using Newtonsoft.Json; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled.GeneratedCode; + +internal partial class NrtOnMsvOffClient : JsonApiClient +{ + partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) + { + SetSerializerSettingsForJsonApi(settings); + + settings.Formatting = Formatting.Indented; + } +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs new file mode 100644 index 0000000000..dc2bc84ccd --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using FluentAssertions; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled.GeneratedCode; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled; + +public sealed class NullabilityTests +{ + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.NonNullableReferenceType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNonNullableReferenceType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableReferenceType), NullabilityState.Nullable)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableReferenceType), NullabilityState.Nullable)] + [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), NullabilityState.Nullable)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), NullabilityState.Nullable)] + public void Nullability_of_generated_property_is_as_expected(string propertyName, NullabilityState expectedState) + { + PropertyInfo[] properties = typeof(ResourceAttributesInPostRequest).GetProperties(); + PropertyInfo property = properties.Single(property => property.Name == propertyName); + property.Should().HaveNullabilityState(expectedState); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs new file mode 100644 index 0000000000..1c31f040b8 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs @@ -0,0 +1,29 @@ +using Bogus; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled.GeneratedCode; +using TestBuildingBlocks; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled; + +internal sealed class ResourceFieldValidationFakers +{ + private readonly Lazy> _lazyPostAttributesFaker = new(() => + FakerFactory.Instance.Create()); + + private readonly Lazy> _lazyPatchAttributesFaker = new(() => + FakerFactory.Instance.Create()); + + private readonly Lazy> _lazyToOneFaker = new(() => + FakerFactory.Instance.CreateForObjectWithResourceId()); + + private readonly Lazy> _lazyNullableToOneFaker = new(() => + FakerFactory.Instance.CreateForObjectWithResourceId()); + + private readonly Lazy> _lazyToManyFaker = new(() => + FakerFactory.Instance.CreateForObjectWithResourceId()); + + public Faker PostAttributes => _lazyPostAttributesFaker.Value; + public Faker PatchAttributes => _lazyPatchAttributesFaker.Value; + public Faker ToOne => _lazyToOneFaker.Value; + public Faker NullableToOne => _lazyNullableToOneFaker.Value; + public Faker ToMany => _lazyToManyFaker.Value; +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/UpdateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/UpdateResourceTests.cs new file mode 100644 index 0000000000..5596c17962 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/UpdateResourceTests.cs @@ -0,0 +1,156 @@ +using System.Net; +using System.Text.Json; +using FluentAssertions; +using FluentAssertions.Specialized; +using Newtonsoft.Json; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled; + +public sealed class UpdateResourceTests +{ + private readonly ResourceFieldValidationFakers _fakers = new(); + + [Fact] + public async Task Cannot_exclude_id() + { + // Arrange + var requestDocument = new ResourcePatchRequestDocument + { + Data = new ResourceDataInPatchRequest + { + Id = "1", + Attributes = _fakers.PatchAttributes.Generate(), + Relationships = new ResourceRelationshipsInPatchRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceDataInPatchRequest emptyDataObject = new(); + requestDocument.Data.Id = emptyDataObject.Id; + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + // Act + Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(999, requestDocument)); + + // Assert + await action.Should().ThrowAsync(); + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'id'. Property requires a value. Path 'data'."); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPatchRequest.NonNullableReferenceType), "nonNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredNonNullableReferenceType), "requiredNonNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.NullableReferenceType), "nullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredNullableReferenceType), "requiredNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.ValueType), "valueType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredValueType), "requiredValueType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.NullableValueType), "nullableValueType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredNullableValueType), "requiredNullableValueType")] + public async Task Can_exclude_attribute_that_is_required_in_create_resource(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePatchRequestDocument + { + Data = new ResourceDataInPatchRequest + { + Id = "1", + Attributes = _fakers.PatchAttributes.Generate(), + Relationships = new ResourceRelationshipsInPatchRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceAttributesInPatchRequest emptyAttributesObject = new(); + object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.NonNullableToOne), "nonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredNonNullableToOne), "requiredNonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.NullableToOne), "nullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredNullableToOne), "requiredNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.ToMany), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredToMany), "requiredToMany")] + public async Task Can_exclude_relationship_that_is_required_in_create_resource(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePatchRequestDocument + { + Data = new ResourceDataInPatchRequest + { + Id = "1", + Attributes = _fakers.PatchAttributes.Generate(), + Relationships = new ResourceRelationshipsInPatchRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.NullableToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPatchRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath(jsonPropertyName); + }); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/swagger.g.json similarity index 67% rename from test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/swagger.g.json rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/swagger.g.json index 1f2d189d46..8619a357c8 100644 --- a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/swagger.g.json +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/swagger.g.json @@ -5,19 +5,19 @@ "version": "1.0" }, "paths": { - "/cows": { + "/Resource": { "get": { "tags": [ - "cows" + "Resource" ], - "operationId": "getCowCollection", + "operationId": "getResourceCollection", "responses": { "200": { "description": "Success", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowCollectionResponseDocument" + "$ref": "#/components/schemas/resourceCollectionResponseDocument" } } } @@ -26,16 +26,16 @@ }, "head": { "tags": [ - "cows" + "Resource" ], - "operationId": "headCowCollection", + "operationId": "headResourceCollection", "responses": { "200": { "description": "Success", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowCollectionResponseDocument" + "$ref": "#/components/schemas/resourceCollectionResponseDocument" } } } @@ -44,14 +44,14 @@ }, "post": { "tags": [ - "cows" + "Resource" ], - "operationId": "postCow", + "operationId": "postResource", "requestBody": { "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowPostRequestDocument" + "$ref": "#/components/schemas/resourcePostRequestDocument" } } } @@ -62,7 +62,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowPrimaryResponseDocument" + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" } } } @@ -73,12 +73,12 @@ } } }, - "/cows/{id}": { + "/Resource/{id}": { "get": { "tags": [ - "cows" + "Resource" ], - "operationId": "getCow", + "operationId": "getResource", "parameters": [ { "name": "id", @@ -96,7 +96,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowPrimaryResponseDocument" + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" } } } @@ -105,9 +105,9 @@ }, "head": { "tags": [ - "cows" + "Resource" ], - "operationId": "headCow", + "operationId": "headResource", "parameters": [ { "name": "id", @@ -125,7 +125,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowPrimaryResponseDocument" + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" } } } @@ -134,9 +134,9 @@ }, "patch": { "tags": [ - "cows" + "Resource" ], - "operationId": "patchCow", + "operationId": "patchResource", "parameters": [ { "name": "id", @@ -152,7 +152,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowPatchRequestDocument" + "$ref": "#/components/schemas/resourcePatchRequestDocument" } } } @@ -163,7 +163,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowPrimaryResponseDocument" + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" } } } @@ -175,9 +175,9 @@ }, "delete": { "tags": [ - "cows" + "Resource" ], - "operationId": "deleteCow", + "operationId": "deleteResource", "parameters": [ { "name": "id", @@ -196,80 +196,72 @@ } } }, - "/cowStables": { + "/Resource/{id}/nonNullableToOne": { "get": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStableCollection", - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/cowStableCollectionResponseDocument" - } - } + "operationId": "getResourceNonNullableToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" } } - } - }, - "head": { - "tags": [ - "cowStables" ], - "operationId": "headCowStableCollection", "responses": { "200": { "description": "Success", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowStableCollectionResponseDocument" + "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument" } } } } } }, - "post": { + "head": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "postCowStable", - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/cowStablePostRequestDocument" - } + "operationId": "headResourceNonNullableToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" } } - }, + ], "responses": { - "201": { - "description": "Created", + "200": { + "description": "Success", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowStablePrimaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument" } } } - }, - "204": { - "description": "No Content" } } } }, - "/cowStables/{id}": { + "/Resource/{id}/relationships/nonNullableToOne": { "get": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStable", + "operationId": "getResourceNonNullableToOneRelationship", "parameters": [ { "name": "id", @@ -287,7 +279,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowStablePrimaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument" } } } @@ -296,9 +288,9 @@ }, "head": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "headCowStable", + "operationId": "headResourceNonNullableToOneRelationship", "parameters": [ { "name": "id", @@ -316,7 +308,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowStablePrimaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument" } } } @@ -325,9 +317,9 @@ }, "patch": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "patchCowStable", + "operationId": "patchResourceNonNullableToOneRelationship", "parameters": [ { "name": "id", @@ -343,43 +335,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowStablePatchRequestDocument" + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" } } } }, - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/cowStablePrimaryResponseDocument" - } - } - } - }, - "204": { - "description": "No Content" - } - } - }, - "delete": { - "tags": [ - "cowStables" - ], - "operationId": "deleteCowStable", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], "responses": { "204": { "description": "No Content" @@ -387,12 +347,12 @@ } } }, - "/cowStables/{id}/albinoCow": { + "/Resource/{id}/nullableToOne": { "get": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStableAlbinoCow", + "operationId": "getResourceNullableToOne", "parameters": [ { "name": "id", @@ -410,7 +370,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableCowSecondaryResponseDocument" + "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument" } } } @@ -419,9 +379,9 @@ }, "head": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "headCowStableAlbinoCow", + "operationId": "headResourceNullableToOne", "parameters": [ { "name": "id", @@ -439,7 +399,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableCowSecondaryResponseDocument" + "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument" } } } @@ -447,12 +407,12 @@ } } }, - "/cowStables/{id}/relationships/albinoCow": { + "/Resource/{id}/relationships/nullableToOne": { "get": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStableAlbinoCowRelationship", + "operationId": "getResourceNullableToOneRelationship", "parameters": [ { "name": "id", @@ -470,7 +430,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableCowIdentifierResponseDocument" + "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument" } } } @@ -479,9 +439,9 @@ }, "head": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "headCowStableAlbinoCowRelationship", + "operationId": "headResourceNullableToOneRelationship", "parameters": [ { "name": "id", @@ -499,7 +459,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableCowIdentifierResponseDocument" + "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument" } } } @@ -508,9 +468,9 @@ }, "patch": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "patchCowStableAlbinoCowRelationship", + "operationId": "patchResourceNullableToOneRelationship", "parameters": [ { "name": "id", @@ -526,7 +486,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableToOneCowInRequest" + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" } } } @@ -538,12 +498,12 @@ } } }, - "/cowStables/{id}/allCows": { + "/Resource/{id}/requiredNonNullableToOne": { "get": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStableAllCows", + "operationId": "getResourceRequiredNonNullableToOne", "parameters": [ { "name": "id", @@ -561,7 +521,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowCollectionResponseDocument" + "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument" } } } @@ -570,9 +530,9 @@ }, "head": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "headCowStableAllCows", + "operationId": "headResourceRequiredNonNullableToOne", "parameters": [ { "name": "id", @@ -590,7 +550,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowCollectionResponseDocument" + "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument" } } } @@ -598,12 +558,12 @@ } } }, - "/cowStables/{id}/relationships/allCows": { + "/Resource/{id}/relationships/requiredNonNullableToOne": { "get": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStableAllCowsRelationship", + "operationId": "getResourceRequiredNonNullableToOneRelationship", "parameters": [ { "name": "id", @@ -621,7 +581,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowIdentifierCollectionResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument" } } } @@ -630,9 +590,9 @@ }, "head": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "headCowStableAllCowsRelationship", + "operationId": "headResourceRequiredNonNullableToOneRelationship", "parameters": [ { "name": "id", @@ -650,80 +610,18 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowIdentifierCollectionResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument" } } } } } }, - "post": { - "tags": [ - "cowStables" - ], - "operationId": "postCowStableAllCowsRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyCowInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - }, "patch": { "tags": [ - "cowStables" - ], - "operationId": "patchCowStableAllCowsRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyCowInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" - } - } - }, - "delete": { - "tags": [ - "cowStables" + "Resource" ], - "operationId": "deleteCowStableAllCowsRelationship", + "operationId": "patchResourceRequiredNonNullableToOneRelationship", "parameters": [ { "name": "id", @@ -739,7 +637,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyCowInRequest" + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" } } } @@ -751,12 +649,12 @@ } } }, - "/cowStables/{id}/cowsReadyForMilking": { + "/Resource/{id}/requiredNullableToOne": { "get": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStableCowsReadyForMilking", + "operationId": "getResourceRequiredNullableToOne", "parameters": [ { "name": "id", @@ -774,7 +672,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowCollectionResponseDocument" + "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument" } } } @@ -783,9 +681,9 @@ }, "head": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "headCowStableCowsReadyForMilking", + "operationId": "headResourceRequiredNullableToOne", "parameters": [ { "name": "id", @@ -803,7 +701,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowCollectionResponseDocument" + "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument" } } } @@ -811,12 +709,12 @@ } } }, - "/cowStables/{id}/relationships/cowsReadyForMilking": { + "/Resource/{id}/relationships/requiredNullableToOne": { "get": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStableCowsReadyForMilkingRelationship", + "operationId": "getResourceRequiredNullableToOneRelationship", "parameters": [ { "name": "id", @@ -834,7 +732,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowIdentifierCollectionResponseDocument" + "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument" } } } @@ -843,9 +741,9 @@ }, "head": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "headCowStableCowsReadyForMilkingRelationship", + "operationId": "headResourceRequiredNullableToOneRelationship", "parameters": [ { "name": "id", @@ -863,18 +761,18 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowIdentifierCollectionResponseDocument" + "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument" } } } } } }, - "post": { + "patch": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "postCowStableCowsReadyForMilkingRelationship", + "operationId": "patchResourceRequiredNullableToOneRelationship", "parameters": [ { "name": "id", @@ -890,7 +788,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyCowInRequest" + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" } } } @@ -900,12 +798,14 @@ "description": "No Content" } } - }, - "patch": { + } + }, + "/Resource/{id}/requiredToMany": { + "get": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "patchCowStableCowsReadyForMilkingRelationship", + "operationId": "getResourceRequiredToMany", "parameters": [ { "name": "id", @@ -917,26 +817,24 @@ } } ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyCowInRequest" + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" + } } } } - }, - "responses": { - "204": { - "description": "No Content" - } } }, - "delete": { + "head": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "deleteCowStableCowsReadyForMilkingRelationship", + "operationId": "headResourceRequiredToMany", "parameters": [ { "name": "id", @@ -948,28 +846,26 @@ } } ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyCowInRequest" + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" + } } } } - }, - "responses": { - "204": { - "description": "No Content" - } } } }, - "/cowStables/{id}/favoriteCow": { + "/Resource/{id}/relationships/requiredToMany": { "get": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStableFavoriteCow", + "operationId": "getResourceRequiredToManyRelationship", "parameters": [ { "name": "id", @@ -987,7 +883,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowSecondaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" } } } @@ -996,9 +892,9 @@ }, "head": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "headCowStableFavoriteCow", + "operationId": "headResourceRequiredToManyRelationship", "parameters": [ { "name": "id", @@ -1016,20 +912,18 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowSecondaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" } } } } } - } - }, - "/cowStables/{id}/relationships/favoriteCow": { - "get": { + }, + "post": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStableFavoriteCowRelationship", + "operationId": "postResourceRequiredToManyRelationship", "parameters": [ { "name": "id", @@ -1041,24 +935,26 @@ } } ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/cowIdentifierResponseDocument" - } + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" } } } + }, + "responses": { + "204": { + "description": "No Content" + } } }, - "head": { + "patch": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "headCowStableFavoriteCowRelationship", + "operationId": "patchResourceRequiredToManyRelationship", "parameters": [ { "name": "id", @@ -1070,24 +966,26 @@ } } ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/cowIdentifierResponseDocument" - } + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" } } } + }, + "responses": { + "204": { + "description": "No Content" + } } }, - "patch": { + "delete": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "patchCowStableFavoriteCowRelationship", + "operationId": "deleteResourceRequiredToManyRelationship", "parameters": [ { "name": "id", @@ -1103,7 +1001,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toOneCowInRequest" + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" } } } @@ -1115,12 +1013,12 @@ } } }, - "/cowStables/{id}/firstCow": { + "/Resource/{id}/toMany": { "get": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStableFirstCow", + "operationId": "getResourceToMany", "parameters": [ { "name": "id", @@ -1138,7 +1036,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowSecondaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" } } } @@ -1147,9 +1045,9 @@ }, "head": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "headCowStableFirstCow", + "operationId": "headResourceToMany", "parameters": [ { "name": "id", @@ -1167,7 +1065,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowSecondaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" } } } @@ -1175,12 +1073,12 @@ } } }, - "/cowStables/{id}/relationships/firstCow": { + "/Resource/{id}/relationships/toMany": { "get": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStableFirstCowRelationship", + "operationId": "getResourceToManyRelationship", "parameters": [ { "name": "id", @@ -1198,7 +1096,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowIdentifierResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" } } } @@ -1207,9 +1105,9 @@ }, "head": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "headCowStableFirstCowRelationship", + "operationId": "headResourceToManyRelationship", "parameters": [ { "name": "id", @@ -1227,18 +1125,18 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/cowIdentifierResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" } } } } } }, - "patch": { + "post": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "patchCowStableFirstCowRelationship", + "operationId": "postResourceToManyRelationship", "parameters": [ { "name": "id", @@ -1254,7 +1152,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toOneCowInRequest" + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" } } } @@ -1264,14 +1162,12 @@ "description": "No Content" } } - } - }, - "/cowStables/{id}/oldestCow": { - "get": { + }, + "patch": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "getCowStableOldestCow", + "operationId": "patchResourceToManyRelationship", "parameters": [ { "name": "id", @@ -1283,24 +1179,26 @@ } } ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/cowSecondaryResponseDocument" - } + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" } } } + }, + "responses": { + "204": { + "description": "No Content" + } } }, - "head": { + "delete": { "tags": [ - "cowStables" + "Resource" ], - "operationId": "headCowStableOldestCow", + "operationId": "deleteResourceToManyRelationship", "parameters": [ { "name": "id", @@ -1312,107 +1210,18 @@ } } ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/cowSecondaryResponseDocument" - } + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" } } - } - } - } - }, - "/cowStables/{id}/relationships/oldestCow": { - "get": { - "tags": [ - "cowStables" - ], - "operationId": "getCowStableOldestCowRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/cowIdentifierResponseDocument" - } - } - } - } - } - }, - "head": { - "tags": [ - "cowStables" - ], - "operationId": "headCowStableOldestCowRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/cowIdentifierResponseDocument" - } - } - } - } - } - }, - "patch": { - "tags": [ - "cowStables" - ], - "operationId": "patchCowStableOldestCowRelationship", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toOneCowInRequest" - } - } - } - }, - "responses": { - "204": { - "description": "No Content" + } + }, + "responses": { + "204": { + "description": "No Content" } } } @@ -1420,125 +1229,7 @@ }, "components": { "schemas": { - "cowAttributesInPatchRequest": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "nameOfCurrentFarm": { - "minLength": 1, - "type": "string" - }, - "nameOfPreviousFarm": { - "type": "string", - "nullable": true - }, - "nickname": { - "minLength": 1, - "type": "string" - }, - "age": { - "type": "integer", - "format": "int32" - }, - "weight": { - "type": "integer", - "format": "int32" - }, - "timeAtCurrentFarmInDays": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "hasProducedMilk": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "cowAttributesInPostRequest": { - "required": [ - "hasProducedMilk", - "name", - "nameOfCurrentFarm", - "nickname", - "weight" - ], - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "nameOfCurrentFarm": { - "minLength": 1, - "type": "string" - }, - "nameOfPreviousFarm": { - "type": "string", - "nullable": true - }, - "nickname": { - "minLength": 1, - "type": "string" - }, - "age": { - "type": "integer", - "format": "int32" - }, - "weight": { - "type": "integer", - "format": "int32" - }, - "timeAtCurrentFarmInDays": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "hasProducedMilk": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "cowAttributesInResponse": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "nameOfCurrentFarm": { - "minLength": 1, - "type": "string" - }, - "nameOfPreviousFarm": { - "type": "string", - "nullable": true - }, - "nickname": { - "minLength": 1, - "type": "string" - }, - "age": { - "type": "integer", - "format": "int32" - }, - "weight": { - "type": "integer", - "format": "int32" - }, - "timeAtCurrentFarmInDays": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "hasProducedMilk": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "cowCollectionResponseDocument": { + "emptyResourceCollectionResponseDocument": { "required": [ "data", "links" @@ -1548,7 +1239,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/cowDataInResponse" + "$ref": "#/components/schemas/emptyResourceDataInResponse" } }, "meta": { @@ -1564,42 +1255,7 @@ }, "additionalProperties": false }, - "cowDataInPatchRequest": { - "required": [ - "id", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/cowResourceType" - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "$ref": "#/components/schemas/cowAttributesInPatchRequest" - } - }, - "additionalProperties": false - }, - "cowDataInPostRequest": { - "required": [ - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/cowResourceType" - }, - "attributes": { - "$ref": "#/components/schemas/cowAttributesInPostRequest" - } - }, - "additionalProperties": false - }, - "cowDataInResponse": { + "emptyResourceDataInResponse": { "required": [ "id", "links", @@ -1608,15 +1264,12 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/cowResourceType" + "$ref": "#/components/schemas/emptyResourceResourceType" }, "id": { "minLength": 1, "type": "string" }, - "attributes": { - "$ref": "#/components/schemas/cowAttributesInResponse" - }, "links": { "$ref": "#/components/schemas/linksInResourceObject" }, @@ -1627,7 +1280,7 @@ }, "additionalProperties": false }, - "cowIdentifier": { + "emptyResourceIdentifier": { "required": [ "id", "type" @@ -1635,7 +1288,7 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/cowResourceType" + "$ref": "#/components/schemas/emptyResourceResourceType" }, "id": { "minLength": 1, @@ -1644,7 +1297,7 @@ }, "additionalProperties": false }, - "cowIdentifierCollectionResponseDocument": { + "emptyResourceIdentifierCollectionResponseDocument": { "required": [ "data", "links" @@ -1654,7 +1307,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/cowIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" } }, "meta": { @@ -1670,7 +1323,7 @@ }, "additionalProperties": false }, - "cowIdentifierResponseDocument": { + "emptyResourceIdentifierResponseDocument": { "required": [ "data", "links" @@ -1678,7 +1331,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/cowIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" }, "meta": { "type": "object", @@ -1693,60 +1346,13 @@ }, "additionalProperties": false }, - "cowPatchRequestDocument": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/cowDataInPatchRequest" - } - }, - "additionalProperties": false - }, - "cowPostRequestDocument": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/cowDataInPostRequest" - } - }, - "additionalProperties": false - }, - "cowPrimaryResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/cowDataInResponse" - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceDocument" - } - }, - "additionalProperties": false - }, - "cowResourceType": { + "emptyResourceResourceType": { "enum": [ - "cows" + "emptyResources" ], "type": "string" }, - "cowSecondaryResponseDocument": { + "emptyResourceSecondaryResponseDocument": { "required": [ "data", "links" @@ -1754,7 +1360,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/cowDataInResponse" + "$ref": "#/components/schemas/emptyResourceDataInResponse" }, "meta": { "type": "object", @@ -1769,252 +1375,32 @@ }, "additionalProperties": false }, - "cowStableCollectionResponseDocument": { - "required": [ - "data", - "links" - ], + "jsonapiObject": { "type": "object", "properties": { - "data": { + "version": { + "type": "string" + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "profile": { "type": "array", "items": { - "$ref": "#/components/schemas/cowStableDataInResponse" + "type": "string" } }, "meta": { "type": "object", "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" } }, "additionalProperties": false }, - "cowStableDataInPatchRequest": { - "required": [ - "id", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/cowStableResourceType" - }, - "id": { - "minLength": 1, - "type": "string" - }, - "relationships": { - "$ref": "#/components/schemas/cowStableRelationshipsInPatchRequest" - } - }, - "additionalProperties": false - }, - "cowStableDataInPostRequest": { - "required": [ - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/cowStableResourceType" - }, - "relationships": { - "$ref": "#/components/schemas/cowStableRelationshipsInPostRequest" - } - }, - "additionalProperties": false - }, - "cowStableDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/cowStableResourceType" - }, - "id": { - "minLength": 1, - "type": "string" - }, - "relationships": { - "$ref": "#/components/schemas/cowStableRelationshipsInResponse" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceObject" - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "cowStablePatchRequestDocument": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/cowStableDataInPatchRequest" - } - }, - "additionalProperties": false - }, - "cowStablePostRequestDocument": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/cowStableDataInPostRequest" - } - }, - "additionalProperties": false - }, - "cowStablePrimaryResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/cowStableDataInResponse" - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceDocument" - } - }, - "additionalProperties": false - }, - "cowStableRelationshipsInPatchRequest": { - "type": "object", - "properties": { - "oldestCow": { - "$ref": "#/components/schemas/toOneCowInRequest" - }, - "firstCow": { - "$ref": "#/components/schemas/toOneCowInRequest" - }, - "albinoCow": { - "$ref": "#/components/schemas/nullableToOneCowInRequest" - }, - "favoriteCow": { - "$ref": "#/components/schemas/toOneCowInRequest" - }, - "cowsReadyForMilking": { - "$ref": "#/components/schemas/toManyCowInRequest" - }, - "allCows": { - "$ref": "#/components/schemas/toManyCowInRequest" - } - }, - "additionalProperties": false - }, - "cowStableRelationshipsInPostRequest": { - "required": [ - "allCows", - "favoriteCow", - "firstCow", - "oldestCow" - ], - "type": "object", - "properties": { - "oldestCow": { - "$ref": "#/components/schemas/toOneCowInRequest" - }, - "firstCow": { - "$ref": "#/components/schemas/toOneCowInRequest" - }, - "albinoCow": { - "$ref": "#/components/schemas/nullableToOneCowInRequest" - }, - "favoriteCow": { - "$ref": "#/components/schemas/toOneCowInRequest" - }, - "cowsReadyForMilking": { - "$ref": "#/components/schemas/toManyCowInRequest" - }, - "allCows": { - "$ref": "#/components/schemas/toManyCowInRequest" - } - }, - "additionalProperties": false - }, - "cowStableRelationshipsInResponse": { - "type": "object", - "properties": { - "oldestCow": { - "$ref": "#/components/schemas/toOneCowInResponse" - }, - "firstCow": { - "$ref": "#/components/schemas/toOneCowInResponse" - }, - "albinoCow": { - "$ref": "#/components/schemas/nullableToOneCowInResponse" - }, - "favoriteCow": { - "$ref": "#/components/schemas/toOneCowInResponse" - }, - "cowsReadyForMilking": { - "$ref": "#/components/schemas/toManyCowInResponse" - }, - "allCows": { - "$ref": "#/components/schemas/toManyCowInResponse" - } - }, - "additionalProperties": false - }, - "cowStableResourceType": { - "enum": [ - "cowStables" - ], - "type": "string" - }, - "jsonapiObject": { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "ext": { - "type": "array", - "items": { - "type": "string" - } - }, - "profile": { - "type": "array", - "items": { - "type": "string" - } - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "linksInRelationshipObject": { + "linksInRelationshipObject": { "required": [ "related", "self" @@ -2170,7 +1556,7 @@ }, "nullable": true }, - "nullableCowIdentifierResponseDocument": { + "nullableEmptyResourceIdentifierResponseDocument": { "required": [ "data", "links" @@ -2180,7 +1566,7 @@ "data": { "oneOf": [ { - "$ref": "#/components/schemas/cowIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" }, { "$ref": "#/components/schemas/nullValue" @@ -2200,7 +1586,7 @@ }, "additionalProperties": false }, - "nullableCowSecondaryResponseDocument": { + "nullableEmptyResourceSecondaryResponseDocument": { "required": [ "data", "links" @@ -2210,7 +1596,7 @@ "data": { "oneOf": [ { - "$ref": "#/components/schemas/cowDataInResponse" + "$ref": "#/components/schemas/emptyResourceDataInResponse" }, { "$ref": "#/components/schemas/nullValue" @@ -2230,7 +1616,7 @@ }, "additionalProperties": false }, - "nullableToOneCowInRequest": { + "nullableToOneEmptyResourceInRequest": { "required": [ "data" ], @@ -2239,7 +1625,7 @@ "data": { "oneOf": [ { - "$ref": "#/components/schemas/cowIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" }, { "$ref": "#/components/schemas/nullValue" @@ -2249,7 +1635,7 @@ }, "additionalProperties": false }, - "nullableToOneCowInResponse": { + "nullableToOneEmptyResourceInResponse": { "required": [ "links" ], @@ -2258,7 +1644,7 @@ "data": { "oneOf": [ { - "$ref": "#/components/schemas/cowIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" }, { "$ref": "#/components/schemas/nullValue" @@ -2275,7 +1661,372 @@ }, "additionalProperties": false }, - "toManyCowInRequest": { + "resourceAttributesInPatchRequest": { + "type": "object", + "properties": { + "nonNullableReferenceType": { + "type": "string" + }, + "requiredNonNullableReferenceType": { + "minLength": 1, + "type": "string" + }, + "nullableReferenceType": { + "type": "string", + "nullable": true + }, + "requiredNullableReferenceType": { + "minLength": 1, + "type": "string", + "nullable": true + }, + "valueType": { + "type": "integer", + "format": "int32" + }, + "requiredValueType": { + "type": "integer", + "format": "int32" + }, + "nullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredNullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false + }, + "resourceAttributesInPostRequest": { + "required": [ + "requiredNonNullableReferenceType", + "requiredNullableReferenceType", + "requiredNullableValueType", + "requiredValueType" + ], + "type": "object", + "properties": { + "nonNullableReferenceType": { + "type": "string" + }, + "requiredNonNullableReferenceType": { + "minLength": 1, + "type": "string" + }, + "nullableReferenceType": { + "type": "string", + "nullable": true + }, + "requiredNullableReferenceType": { + "minLength": 1, + "type": "string", + "nullable": true + }, + "valueType": { + "type": "integer", + "format": "int32" + }, + "requiredValueType": { + "type": "integer", + "format": "int32" + }, + "nullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredNullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false + }, + "resourceAttributesInResponse": { + "required": [ + "requiredNonNullableReferenceType", + "requiredNullableReferenceType", + "requiredNullableValueType", + "requiredValueType" + ], + "type": "object", + "properties": { + "nonNullableReferenceType": { + "type": "string" + }, + "requiredNonNullableReferenceType": { + "minLength": 1, + "type": "string" + }, + "nullableReferenceType": { + "type": "string", + "nullable": true + }, + "requiredNullableReferenceType": { + "minLength": 1, + "type": "string", + "nullable": true + }, + "valueType": { + "type": "integer", + "format": "int32" + }, + "requiredValueType": { + "type": "integer", + "format": "int32" + }, + "nullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredNullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false + }, + "resourceCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/resourceDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "resourceDataInPatchRequest": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/resourceResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/resourceAttributesInPatchRequest" + }, + "relationships": { + "$ref": "#/components/schemas/resourceRelationshipsInPatchRequest" + } + }, + "additionalProperties": false + }, + "resourceDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/resourceResourceType" + }, + "attributes": { + "$ref": "#/components/schemas/resourceAttributesInPostRequest" + }, + "relationships": { + "$ref": "#/components/schemas/resourceRelationshipsInPostRequest" + } + }, + "additionalProperties": false + }, + "resourceDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/resourceResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/resourceAttributesInResponse" + }, + "relationships": { + "$ref": "#/components/schemas/resourceRelationshipsInResponse" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "resourcePatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/resourceDataInPatchRequest" + } + }, + "additionalProperties": false + }, + "resourcePostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/resourceDataInPostRequest" + } + }, + "additionalProperties": false + }, + "resourcePrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/resourceDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "resourceRelationshipsInPatchRequest": { + "type": "object", + "properties": { + "nonNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + }, + "requiredNonNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + }, + "nullableToOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + }, + "requiredNullableToOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + }, + "toMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + }, + "requiredToMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + }, + "additionalProperties": false + }, + "resourceRelationshipsInPostRequest": { + "required": [ + "requiredNonNullableToOne", + "requiredNullableToOne", + "requiredToMany" + ], + "type": "object", + "properties": { + "nonNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + }, + "requiredNonNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + }, + "nullableToOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + }, + "requiredNullableToOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + }, + "toMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + }, + "requiredToMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + }, + "additionalProperties": false + }, + "resourceRelationshipsInResponse": { + "required": [ + "requiredNonNullableToOne", + "requiredNullableToOne", + "requiredToMany" + ], + "type": "object", + "properties": { + "nonNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + }, + "requiredNonNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + }, + "nullableToOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + }, + "requiredNullableToOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + }, + "toMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + }, + "requiredToMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + } + }, + "additionalProperties": false + }, + "resourceResourceType": { + "enum": [ + "Resource" + ], + "type": "string" + }, + "toManyEmptyResourceInRequest": { "required": [ "data" ], @@ -2284,13 +2035,13 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/cowIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" } } }, "additionalProperties": false }, - "toManyCowInResponse": { + "toManyEmptyResourceInResponse": { "required": [ "links" ], @@ -2299,7 +2050,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/cowIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" } }, "links": { @@ -2312,26 +2063,26 @@ }, "additionalProperties": false }, - "toOneCowInRequest": { + "toOneEmptyResourceInRequest": { "required": [ "data" ], "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/cowIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" } }, "additionalProperties": false }, - "toOneCowInResponse": { + "toOneEmptyResourceInResponse": { "required": [ "links" ], "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/cowIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" }, "links": { "$ref": "#/components/schemas/linksInRelationshipObject" diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/CreateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/CreateResourceTests.cs new file mode 100644 index 0000000000..44aa5da920 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/CreateResourceTests.cs @@ -0,0 +1,613 @@ +using System.Linq.Expressions; +using System.Net; +using System.Text.Json; +using FluentAssertions; +using FluentAssertions.Specialized; +using Newtonsoft.Json; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled; + +public sealed class CreateResourceTests : OpenApiClientTests +{ + private readonly ResourceFieldValidationFakers _fakers = new(); + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableReferenceType), "nullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), "nullableValueType")] + public async Task Can_clear_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + Expression> includeAttributeSelector = + CreateIncludedAttributeSelector(attributePropertyName); + + using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null)); + }); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), "requiredNullableValueType")] + public async Task Can_set_default_value_to_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? defaultValue = requestDocument.Data.Attributes.GetDefaultValueForProperty(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + Expression> includeAttributeSelector = + CreateIncludedAttributeSelector(attributePropertyName); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ShouldBeInteger(0)); + }); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.NonNullableReferenceType), "nonNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNonNullableReferenceType), "requiredNonNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableReferenceType), "requiredNullableReferenceType")] + public async Task Cannot_clear_attribute(string clrPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + requestDocument.Data.Attributes.SetPropertyValue(clrPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + Expression> includeAttributeSelector = + CreateIncludedAttributeSelector(clrPropertyName); + + using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Contain($"Cannot write a null value for property '{jsonPropertyName}'."); + } + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableReferenceType), "nullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), "nullableValueType")] + public async Task Can_exclude_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceAttributesInPostRequest emptyAttributesObject = new(); + object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.NonNullableReferenceType), "nonNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNonNullableReferenceType), "requiredNonNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableReferenceType), "requiredNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), "requiredNullableValueType")] + public async Task Cannot_exclude_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceAttributesInPostRequest emptyAttributesObject = new(); + object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + InvalidOperationException exception = assertion.Subject.Single(); + exception.Message.Should().Be($"The following property should not be omitted: data.attributes.{jsonPropertyName}."); + } + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), nameof(ResourceRelationshipsInPostRequest.NullableToOne.Data), "nullableToOne")] + public async Task Can_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + // Act + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject => + { + relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), nameof(ResourceRelationshipsInPostRequest.NullableToOne.Data), "nullableToOne")] + public async Task Can_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject => + { + relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), nameof(ResourceRelationshipsInPostRequest.NonNullableToOne.Data), + "nonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne.Data), + "requiredNonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne.Data), + "requiredNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")] + public async Task Cannot_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'."); + } + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), nameof(ResourceRelationshipsInPostRequest.NonNullableToOne.Data), + "nonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne.Data), + "requiredNonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne.Data), + "requiredNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")] + public async Task Cannot_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName, + string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName); + relationshipObject!.SetPropertyValue(dataPropertyName, null); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'."); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), "nullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany")] + public async Task Can_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + // Act + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), "nullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany")] + public async Task Can_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), "nonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), "requiredNonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), "requiredNullableToOne")] + public async Task Cannot_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'."); + } + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), "nonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), "requiredNonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), "requiredNullableToOne")] + public async Task Cannot_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePostRequestDocument + { + Data = new ResourceDataInPostRequest + { + Attributes = _fakers.PostAttributes.Generate(), + Relationships = new ResourceRelationshipsInPostRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + // Act + Func> action = async () => + await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should() + .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'."); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/GeneratedCode/NrtOnMsvOnClient.cs similarity index 59% rename from test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/GeneratedCode/NrtOnMsvOnClient.cs index aa15aa3c91..1eed829acb 100644 --- a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/GeneratedCode/NullableReferenceTypesEnabledClient.cs +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/GeneratedCode/NrtOnMsvOnClient.cs @@ -1,9 +1,9 @@ using JsonApiDotNetCore.OpenApi.Client; using Newtonsoft.Json; -namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.GeneratedCode; +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled.GeneratedCode; -internal partial class NullableReferenceTypesEnabledClient : JsonApiClient +internal partial class NrtOnMsvOnClient : JsonApiClient { partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) { diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs new file mode 100644 index 0000000000..e3c1a5ce31 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using FluentAssertions; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled.GeneratedCode; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled; + +public sealed class NullabilityTests +{ + [Theory] + [InlineData(nameof(ResourceAttributesInPostRequest.NonNullableReferenceType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNonNullableReferenceType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableReferenceType), NullabilityState.Nullable)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableReferenceType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), NullabilityState.NotNull)] + [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), NullabilityState.Nullable)] + [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), NullabilityState.NotNull)] + public void Nullability_of_generated_property_is_as_expected(string propertyName, NullabilityState expectedState) + { + PropertyInfo[] properties = typeof(ResourceAttributesInPostRequest).GetProperties(); + PropertyInfo property = properties.Single(property => property.Name == propertyName); + property.Should().HaveNullabilityState(expectedState); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs new file mode 100644 index 0000000000..8389b8bd2c --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs @@ -0,0 +1,29 @@ +using Bogus; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled.GeneratedCode; +using TestBuildingBlocks; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled; + +internal sealed class ResourceFieldValidationFakers +{ + private readonly Lazy> _lazyPostAttributesFaker = new(() => + FakerFactory.Instance.Create()); + + private readonly Lazy> _lazyPatchAttributesFaker = new(() => + FakerFactory.Instance.Create()); + + private readonly Lazy> _lazyToOneFaker = new(() => + FakerFactory.Instance.CreateForObjectWithResourceId()); + + private readonly Lazy> _lazyNullableToOneFaker = new(() => + FakerFactory.Instance.CreateForObjectWithResourceId()); + + private readonly Lazy> _lazyToManyFaker = new(() => + FakerFactory.Instance.CreateForObjectWithResourceId()); + + public Faker PostAttributes => _lazyPostAttributesFaker.Value; + public Faker PatchAttributes => _lazyPatchAttributesFaker.Value; + public Faker ToOne => _lazyToOneFaker.Value; + public Faker NullableToOne => _lazyNullableToOneFaker.Value; + public Faker ToMany => _lazyToManyFaker.Value; +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/UpdateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/UpdateResourceTests.cs new file mode 100644 index 0000000000..d0695d6cf7 --- /dev/null +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/UpdateResourceTests.cs @@ -0,0 +1,156 @@ +using System.Net; +using System.Text.Json; +using FluentAssertions; +using FluentAssertions.Specialized; +using Newtonsoft.Json; +using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled.GeneratedCode; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled; + +public sealed class UpdateResourceTests +{ + private readonly ResourceFieldValidationFakers _fakers = new(); + + [Fact] + public async Task Cannot_exclude_id() + { + // Arrange + var requestDocument = new ResourcePatchRequestDocument + { + Data = new ResourceDataInPatchRequest + { + Id = "1", + Attributes = _fakers.PatchAttributes.Generate(), + Relationships = new ResourceRelationshipsInPatchRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceDataInPatchRequest emptyDataObject = new(); + requestDocument.Data.Id = emptyDataObject.Id; + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + // Act + Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(999, requestDocument)); + + // Assert + await action.Should().ThrowAsync(); + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + JsonSerializationException exception = assertion.Subject.Single(); + + exception.Message.Should().Be("Cannot write a null value for property 'id'. Property requires a value. Path 'data'."); + } + + [Theory] + [InlineData(nameof(ResourceAttributesInPatchRequest.NonNullableReferenceType), "nonNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredNonNullableReferenceType), "requiredNonNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.NullableReferenceType), "nullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredNullableReferenceType), "requiredNullableReferenceType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.ValueType), "valueType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredValueType), "requiredValueType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.NullableValueType), "nullableValueType")] + [InlineData(nameof(ResourceAttributesInPatchRequest.RequiredNullableValueType), "requiredNullableValueType")] + public async Task Can_exclude_attribute(string attributePropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePatchRequestDocument + { + Data = new ResourceDataInPatchRequest + { + Id = "1", + Attributes = _fakers.PatchAttributes.Generate(), + Relationships = new ResourceRelationshipsInPatchRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceAttributesInPatchRequest emptyAttributesObject = new(); + object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName); + requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.attributes").With(attributesObject => + { + attributesObject.ShouldNotContainPath(jsonPropertyName); + }); + } + + [Theory] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.NonNullableToOne), "nonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredNonNullableToOne), "requiredNonNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.NullableToOne), "nullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredNullableToOne), "requiredNullableToOne")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.ToMany), "toMany")] + [InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredToMany), "requiredToMany")] + public async Task Can_exclude_relationship(string relationshipPropertyName, string jsonPropertyName) + { + // Arrange + var requestDocument = new ResourcePatchRequestDocument + { + Data = new ResourceDataInPatchRequest + { + Id = "1", + Attributes = _fakers.PatchAttributes.Generate(), + Relationships = new ResourceRelationshipsInPatchRequest + { + NonNullableToOne = _fakers.ToOne.Generate(), + RequiredNonNullableToOne = _fakers.ToOne.Generate(), + NullableToOne = _fakers.NullableToOne.Generate(), + RequiredNullableToOne = _fakers.ToOne.Generate(), + ToMany = _fakers.ToMany.Generate(), + RequiredToMany = _fakers.ToMany.Generate() + } + } + }; + + ResourceRelationshipsInPatchRequest emptyRelationshipsObject = new(); + object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); + requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); + + using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); + var apiClient = new NrtOnMsvOnClient(wrapper.HttpClient); + + using (apiClient.WithPartialAttributeSerialization(requestDocument)) + { + // Act + await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument)); + } + + // Assert + JsonElement document = wrapper.ParseRequestBody(); + + document.ShouldContainPath("data.relationships").With(relationshipsObject => + { + relationshipsObject.ShouldNotContainPath(jsonPropertyName); + }); + } +} diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/swagger.g.json similarity index 68% rename from test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/swagger.g.json rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/swagger.g.json index 8357d61d05..897983660d 100644 --- a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/swagger.g.json +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/swagger.g.json @@ -5,19 +5,19 @@ "version": "1.0" }, "paths": { - "/chickens": { + "/Resource": { "get": { "tags": [ - "chickens" + "Resource" ], - "operationId": "getChickenCollection", + "operationId": "getResourceCollection", "responses": { "200": { "description": "Success", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenCollectionResponseDocument" + "$ref": "#/components/schemas/resourceCollectionResponseDocument" } } } @@ -26,16 +26,16 @@ }, "head": { "tags": [ - "chickens" + "Resource" ], - "operationId": "headChickenCollection", + "operationId": "headResourceCollection", "responses": { "200": { "description": "Success", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenCollectionResponseDocument" + "$ref": "#/components/schemas/resourceCollectionResponseDocument" } } } @@ -44,14 +44,14 @@ }, "post": { "tags": [ - "chickens" + "Resource" ], - "operationId": "postChicken", + "operationId": "postResource", "requestBody": { "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenPostRequestDocument" + "$ref": "#/components/schemas/resourcePostRequestDocument" } } } @@ -62,7 +62,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenPrimaryResponseDocument" + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" } } } @@ -73,12 +73,12 @@ } } }, - "/chickens/{id}": { + "/Resource/{id}": { "get": { "tags": [ - "chickens" + "Resource" ], - "operationId": "getChicken", + "operationId": "getResource", "parameters": [ { "name": "id", @@ -96,7 +96,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenPrimaryResponseDocument" + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" } } } @@ -105,9 +105,9 @@ }, "head": { "tags": [ - "chickens" + "Resource" ], - "operationId": "headChicken", + "operationId": "headResource", "parameters": [ { "name": "id", @@ -125,7 +125,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenPrimaryResponseDocument" + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" } } } @@ -134,9 +134,9 @@ }, "patch": { "tags": [ - "chickens" + "Resource" ], - "operationId": "patchChicken", + "operationId": "patchResource", "parameters": [ { "name": "id", @@ -152,7 +152,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenPatchRequestDocument" + "$ref": "#/components/schemas/resourcePatchRequestDocument" } } } @@ -163,7 +163,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenPrimaryResponseDocument" + "$ref": "#/components/schemas/resourcePrimaryResponseDocument" } } } @@ -175,9 +175,9 @@ }, "delete": { "tags": [ - "chickens" + "Resource" ], - "operationId": "deleteChicken", + "operationId": "deleteResource", "parameters": [ { "name": "id", @@ -196,80 +196,72 @@ } } }, - "/henHouses": { + "/Resource/{id}/nonNullableToOne": { "get": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "getHenHouseCollection", - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/henHouseCollectionResponseDocument" - } - } + "operationId": "getResourceNonNullableToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" } } - } - }, - "head": { - "tags": [ - "henHouses" ], - "operationId": "headHenHouseCollection", "responses": { "200": { "description": "Success", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/henHouseCollectionResponseDocument" + "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument" } } } } } }, - "post": { + "head": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "postHenHouse", - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/henHousePostRequestDocument" - } + "operationId": "headResourceNonNullableToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" } } - }, + ], "responses": { - "201": { - "description": "Created", + "200": { + "description": "Success", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/henHousePrimaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument" } } } - }, - "204": { - "description": "No Content" } } } }, - "/henHouses/{id}": { + "/Resource/{id}/relationships/nonNullableToOne": { "get": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "getHenHouse", + "operationId": "getResourceNonNullableToOneRelationship", "parameters": [ { "name": "id", @@ -287,7 +279,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/henHousePrimaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument" } } } @@ -296,9 +288,9 @@ }, "head": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "headHenHouse", + "operationId": "headResourceNonNullableToOneRelationship", "parameters": [ { "name": "id", @@ -316,7 +308,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/henHousePrimaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument" } } } @@ -325,9 +317,9 @@ }, "patch": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "patchHenHouse", + "operationId": "patchResourceNonNullableToOneRelationship", "parameters": [ { "name": "id", @@ -343,32 +335,53 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/henHousePatchRequestDocument" + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" } } } }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/Resource/{id}/nullableToOne": { + "get": { + "tags": [ + "Resource" + ], + "operationId": "getResourceNullableToOne", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], "responses": { "200": { "description": "Success", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/henHousePrimaryResponseDocument" + "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument" } } } - }, - "204": { - "description": "No Content" } } }, - "delete": { + "head": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "deleteHenHouse", + "operationId": "headResourceNullableToOne", "parameters": [ { "name": "id", @@ -381,18 +394,25 @@ } ], "responses": { - "204": { - "description": "No Content" + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument" + } + } + } } } } }, - "/henHouses/{id}/allChickens": { + "/Resource/{id}/relationships/nullableToOne": { "get": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "getHenHouseAllChickens", + "operationId": "getResourceNullableToOneRelationship", "parameters": [ { "name": "id", @@ -410,7 +430,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenCollectionResponseDocument" + "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument" } } } @@ -419,9 +439,9 @@ }, "head": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "headHenHouseAllChickens", + "operationId": "headResourceNullableToOneRelationship", "parameters": [ { "name": "id", @@ -439,20 +459,51 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenCollectionResponseDocument" + "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument" } } } } } + }, + "patch": { + "tags": [ + "Resource" + ], + "operationId": "patchResourceNullableToOneRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + } + } } }, - "/henHouses/{id}/relationships/allChickens": { + "/Resource/{id}/requiredNonNullableToOne": { "get": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "getHenHouseAllChickensRelationship", + "operationId": "getResourceRequiredNonNullableToOne", "parameters": [ { "name": "id", @@ -470,7 +521,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenIdentifierCollectionResponseDocument" + "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument" } } } @@ -479,9 +530,9 @@ }, "head": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "headHenHouseAllChickensRelationship", + "operationId": "headResourceRequiredNonNullableToOne", "parameters": [ { "name": "id", @@ -499,18 +550,20 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenIdentifierCollectionResponseDocument" + "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument" } } } } } - }, - "post": { + } + }, + "/Resource/{id}/relationships/requiredNonNullableToOne": { + "get": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "postHenHouseAllChickensRelationship", + "operationId": "getResourceRequiredNonNullableToOneRelationship", "parameters": [ { "name": "id", @@ -522,26 +575,24 @@ } } ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyChickenInRequest" + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument" + } } } } - }, - "responses": { - "204": { - "description": "No Content" - } } }, - "patch": { + "head": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "patchHenHouseAllChickensRelationship", + "operationId": "headResourceRequiredNonNullableToOneRelationship", "parameters": [ { "name": "id", @@ -553,26 +604,24 @@ } } ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyChickenInRequest" + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument" + } } } } - }, - "responses": { - "204": { - "description": "No Content" - } } }, - "delete": { + "patch": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "deleteHenHouseAllChickensRelationship", + "operationId": "patchResourceRequiredNonNullableToOneRelationship", "parameters": [ { "name": "id", @@ -588,7 +637,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyChickenInRequest" + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" } } } @@ -600,12 +649,12 @@ } } }, - "/henHouses/{id}/chickensReadyForLaying": { + "/Resource/{id}/requiredNullableToOne": { "get": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "getHenHouseChickensReadyForLaying", + "operationId": "getResourceRequiredNullableToOne", "parameters": [ { "name": "id", @@ -623,7 +672,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenCollectionResponseDocument" + "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument" } } } @@ -632,9 +681,9 @@ }, "head": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "headHenHouseChickensReadyForLaying", + "operationId": "headResourceRequiredNullableToOne", "parameters": [ { "name": "id", @@ -652,7 +701,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenCollectionResponseDocument" + "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument" } } } @@ -660,12 +709,12 @@ } } }, - "/henHouses/{id}/relationships/chickensReadyForLaying": { + "/Resource/{id}/relationships/requiredNullableToOne": { "get": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "getHenHouseChickensReadyForLayingRelationship", + "operationId": "getResourceRequiredNullableToOneRelationship", "parameters": [ { "name": "id", @@ -683,7 +732,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenIdentifierCollectionResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument" } } } @@ -692,9 +741,9 @@ }, "head": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "headHenHouseChickensReadyForLayingRelationship", + "operationId": "headResourceRequiredNullableToOneRelationship", "parameters": [ { "name": "id", @@ -712,18 +761,18 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenIdentifierCollectionResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument" } } } } } }, - "post": { + "patch": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "postHenHouseChickensReadyForLayingRelationship", + "operationId": "patchResourceRequiredNullableToOneRelationship", "parameters": [ { "name": "id", @@ -739,7 +788,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyChickenInRequest" + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" } } } @@ -749,12 +798,14 @@ "description": "No Content" } } - }, - "patch": { + } + }, + "/Resource/{id}/requiredToMany": { + "get": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "patchHenHouseChickensReadyForLayingRelationship", + "operationId": "getResourceRequiredToMany", "parameters": [ { "name": "id", @@ -766,26 +817,24 @@ } } ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyChickenInRequest" + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" + } } } } - }, - "responses": { - "204": { - "description": "No Content" - } } }, - "delete": { + "head": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "deleteHenHouseChickensReadyForLayingRelationship", + "operationId": "headResourceRequiredToMany", "parameters": [ { "name": "id", @@ -797,28 +846,26 @@ } } ], - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/toManyChickenInRequest" + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" + } } } } - }, - "responses": { - "204": { - "description": "No Content" - } } } }, - "/henHouses/{id}/firstChicken": { + "/Resource/{id}/relationships/requiredToMany": { "get": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "getHenHouseFirstChicken", + "operationId": "getResourceRequiredToManyRelationship", "parameters": [ { "name": "id", @@ -836,7 +883,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenSecondaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" } } } @@ -845,9 +892,9 @@ }, "head": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "headHenHouseFirstChicken", + "operationId": "headResourceRequiredToManyRelationship", "parameters": [ { "name": "id", @@ -865,20 +912,18 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/chickenSecondaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" } } } } } - } - }, - "/henHouses/{id}/relationships/firstChicken": { - "get": { + }, + "post": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "getHenHouseFirstChickenRelationship", + "operationId": "postResourceRequiredToManyRelationship", "parameters": [ { "name": "id", @@ -890,24 +935,26 @@ } } ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/chickenIdentifierResponseDocument" - } + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" } } } + }, + "responses": { + "204": { + "description": "No Content" + } } }, - "head": { + "patch": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "headHenHouseFirstChickenRelationship", + "operationId": "patchResourceRequiredToManyRelationship", "parameters": [ { "name": "id", @@ -919,24 +966,26 @@ } } ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/chickenIdentifierResponseDocument" - } + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" } } } + }, + "responses": { + "204": { + "description": "No Content" + } } }, - "patch": { + "delete": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "patchHenHouseFirstChickenRelationship", + "operationId": "deleteResourceRequiredToManyRelationship", "parameters": [ { "name": "id", @@ -952,7 +1001,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toOneChickenInRequest" + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" } } } @@ -964,12 +1013,12 @@ } } }, - "/henHouses/{id}/oldestChicken": { + "/Resource/{id}/toMany": { "get": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "getHenHouseOldestChicken", + "operationId": "getResourceToMany", "parameters": [ { "name": "id", @@ -987,7 +1036,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableChickenSecondaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" } } } @@ -996,9 +1045,9 @@ }, "head": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "headHenHouseOldestChicken", + "operationId": "headResourceToMany", "parameters": [ { "name": "id", @@ -1016,7 +1065,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableChickenSecondaryResponseDocument" + "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument" } } } @@ -1024,12 +1073,12 @@ } } }, - "/henHouses/{id}/relationships/oldestChicken": { + "/Resource/{id}/relationships/toMany": { "get": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "getHenHouseOldestChickenRelationship", + "operationId": "getResourceToManyRelationship", "parameters": [ { "name": "id", @@ -1047,7 +1096,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableChickenIdentifierResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" } } } @@ -1056,9 +1105,9 @@ }, "head": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "headHenHouseOldestChickenRelationship", + "operationId": "headResourceToManyRelationship", "parameters": [ { "name": "id", @@ -1076,18 +1125,18 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableChickenIdentifierResponseDocument" + "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument" } } } } } }, - "patch": { + "post": { "tags": [ - "henHouses" + "Resource" ], - "operationId": "patchHenHouseOldestChickenRelationship", + "operationId": "postResourceToManyRelationship", "parameters": [ { "name": "id", @@ -1103,7 +1152,7 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableToOneChickenInRequest" + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" } } } @@ -1113,107 +1162,74 @@ "description": "No Content" } } - } - } - }, - "components": { - "schemas": { - "chickenAttributesInPatchRequest": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true - }, - "nameOfCurrentFarm": { - "minLength": 1, - "type": "string" - }, - "age": { - "type": "integer", - "format": "int32" - }, - "weight": { - "type": "integer", - "format": "int32" - }, - "timeAtCurrentFarmInDays": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "hasProducedEggs": { - "type": "boolean" - } - }, - "additionalProperties": false }, - "chickenAttributesInPostRequest": { - "required": [ - "hasProducedEggs", - "nameOfCurrentFarm", - "weight" + "patch": { + "tags": [ + "Resource" ], - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true - }, - "nameOfCurrentFarm": { - "minLength": 1, - "type": "string" - }, - "age": { - "type": "integer", - "format": "int32" - }, - "weight": { - "type": "integer", - "format": "int32" - }, - "timeAtCurrentFarmInDays": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "hasProducedEggs": { - "type": "boolean" + "operationId": "patchResourceToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } } }, - "additionalProperties": false + "responses": { + "204": { + "description": "No Content" + } + } }, - "chickenAttributesInResponse": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true - }, - "nameOfCurrentFarm": { - "minLength": 1, - "type": "string" - }, - "age": { - "type": "integer", - "format": "int32" - }, - "weight": { - "type": "integer", - "format": "int32" - }, - "timeAtCurrentFarmInDays": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "hasProducedEggs": { - "type": "boolean" + "delete": { + "tags": [ + "Resource" + ], + "operationId": "deleteResourceToManyRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + } } }, - "additionalProperties": false - }, - "chickenCollectionResponseDocument": { + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "components": { + "schemas": { + "emptyResourceCollectionResponseDocument": { "required": [ "data", "links" @@ -1223,7 +1239,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/chickenDataInResponse" + "$ref": "#/components/schemas/emptyResourceDataInResponse" } }, "meta": { @@ -1239,42 +1255,7 @@ }, "additionalProperties": false }, - "chickenDataInPatchRequest": { - "required": [ - "id", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/chickenResourceType" - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "$ref": "#/components/schemas/chickenAttributesInPatchRequest" - } - }, - "additionalProperties": false - }, - "chickenDataInPostRequest": { - "required": [ - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/chickenResourceType" - }, - "attributes": { - "$ref": "#/components/schemas/chickenAttributesInPostRequest" - } - }, - "additionalProperties": false - }, - "chickenDataInResponse": { + "emptyResourceDataInResponse": { "required": [ "id", "links", @@ -1283,15 +1264,12 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/chickenResourceType" + "$ref": "#/components/schemas/emptyResourceResourceType" }, "id": { "minLength": 1, "type": "string" }, - "attributes": { - "$ref": "#/components/schemas/chickenAttributesInResponse" - }, "links": { "$ref": "#/components/schemas/linksInResourceObject" }, @@ -1302,7 +1280,7 @@ }, "additionalProperties": false }, - "chickenIdentifier": { + "emptyResourceIdentifier": { "required": [ "id", "type" @@ -1310,7 +1288,7 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/chickenResourceType" + "$ref": "#/components/schemas/emptyResourceResourceType" }, "id": { "minLength": 1, @@ -1319,7 +1297,7 @@ }, "additionalProperties": false }, - "chickenIdentifierCollectionResponseDocument": { + "emptyResourceIdentifierCollectionResponseDocument": { "required": [ "data", "links" @@ -1329,7 +1307,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/chickenIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" } }, "meta": { @@ -1345,7 +1323,7 @@ }, "additionalProperties": false }, - "chickenIdentifierResponseDocument": { + "emptyResourceIdentifierResponseDocument": { "required": [ "data", "links" @@ -1353,7 +1331,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/chickenIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" }, "meta": { "type": "object", @@ -1368,60 +1346,13 @@ }, "additionalProperties": false }, - "chickenPatchRequestDocument": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/chickenDataInPatchRequest" - } - }, - "additionalProperties": false - }, - "chickenPostRequestDocument": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/chickenDataInPostRequest" - } - }, - "additionalProperties": false - }, - "chickenPrimaryResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/chickenDataInResponse" - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceDocument" - } - }, - "additionalProperties": false - }, - "chickenResourceType": { + "emptyResourceResourceType": { "enum": [ - "chickens" + "emptyResources" ], "type": "string" }, - "chickenSecondaryResponseDocument": { + "emptyResourceSecondaryResponseDocument": { "required": [ "data", "links" @@ -1429,7 +1360,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/chickenDataInResponse" + "$ref": "#/components/schemas/emptyResourceDataInResponse" }, "meta": { "type": "object", @@ -1444,296 +1375,96 @@ }, "additionalProperties": false }, - "henHouseCollectionResponseDocument": { - "required": [ - "data", - "links" - ], + "jsonapiObject": { "type": "object", "properties": { - "data": { + "version": { + "type": "string" + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "profile": { "type": "array", "items": { - "$ref": "#/components/schemas/henHouseDataInResponse" + "type": "string" } }, "meta": { "type": "object", "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" } }, "additionalProperties": false }, - "henHouseDataInPatchRequest": { + "linksInRelationshipObject": { "required": [ - "id", - "type" + "related", + "self" ], "type": "object", "properties": { - "type": { - "$ref": "#/components/schemas/henHouseResourceType" - }, - "id": { + "self": { "minLength": 1, "type": "string" }, - "relationships": { - "$ref": "#/components/schemas/henHouseRelationshipsInPatchRequest" + "related": { + "minLength": 1, + "type": "string" } }, "additionalProperties": false }, - "henHouseDataInPostRequest": { + "linksInResourceCollectionDocument": { "required": [ - "type" + "first", + "self" ], "type": "object", "properties": { - "type": { - "$ref": "#/components/schemas/henHouseResourceType" + "self": { + "minLength": 1, + "type": "string" }, - "relationships": { - "$ref": "#/components/schemas/henHouseRelationshipsInPostRequest" - } - }, - "additionalProperties": false - }, - "henHouseDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/henHouseResourceType" + "describedby": { + "type": "string" }, - "id": { + "first": { "minLength": 1, "type": "string" }, - "relationships": { - "$ref": "#/components/schemas/henHouseRelationshipsInResponse" + "last": { + "type": "string" }, - "links": { - "$ref": "#/components/schemas/linksInResourceObject" + "prev": { + "type": "string" }, - "meta": { - "type": "object", - "additionalProperties": { } + "next": { + "type": "string" } }, "additionalProperties": false }, - "henHousePatchRequestDocument": { + "linksInResourceDocument": { "required": [ - "data" + "self" ], "type": "object", "properties": { - "data": { - "$ref": "#/components/schemas/henHouseDataInPatchRequest" + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" } }, "additionalProperties": false }, - "henHousePostRequestDocument": { - "required": [ - "data" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/henHouseDataInPostRequest" - } - }, - "additionalProperties": false - }, - "henHousePrimaryResponseDocument": { - "required": [ - "data", - "links" - ], - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/henHouseDataInResponse" - }, - "meta": { - "type": "object", - "additionalProperties": { } - }, - "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" - }, - "links": { - "$ref": "#/components/schemas/linksInResourceDocument" - } - }, - "additionalProperties": false - }, - "henHouseRelationshipsInPatchRequest": { - "type": "object", - "properties": { - "oldestChicken": { - "$ref": "#/components/schemas/nullableToOneChickenInRequest" - }, - "firstChicken": { - "$ref": "#/components/schemas/toOneChickenInRequest" - }, - "allChickens": { - "$ref": "#/components/schemas/toManyChickenInRequest" - }, - "chickensReadyForLaying": { - "$ref": "#/components/schemas/toManyChickenInRequest" - } - }, - "additionalProperties": false - }, - "henHouseRelationshipsInPostRequest": { - "required": [ - "chickensReadyForLaying", - "firstChicken" - ], - "type": "object", - "properties": { - "oldestChicken": { - "$ref": "#/components/schemas/nullableToOneChickenInRequest" - }, - "firstChicken": { - "$ref": "#/components/schemas/toOneChickenInRequest" - }, - "allChickens": { - "$ref": "#/components/schemas/toManyChickenInRequest" - }, - "chickensReadyForLaying": { - "$ref": "#/components/schemas/toManyChickenInRequest" - } - }, - "additionalProperties": false - }, - "henHouseRelationshipsInResponse": { - "type": "object", - "properties": { - "oldestChicken": { - "$ref": "#/components/schemas/nullableToOneChickenInResponse" - }, - "firstChicken": { - "$ref": "#/components/schemas/toOneChickenInResponse" - }, - "allChickens": { - "$ref": "#/components/schemas/toManyChickenInResponse" - }, - "chickensReadyForLaying": { - "$ref": "#/components/schemas/toManyChickenInResponse" - } - }, - "additionalProperties": false - }, - "henHouseResourceType": { - "enum": [ - "henHouses" - ], - "type": "string" - }, - "jsonapiObject": { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "ext": { - "type": "array", - "items": { - "type": "string" - } - }, - "profile": { - "type": "array", - "items": { - "type": "string" - } - }, - "meta": { - "type": "object", - "additionalProperties": { } - } - }, - "additionalProperties": false - }, - "linksInRelationshipObject": { - "required": [ - "related", - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "related": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceCollectionDocument": { - "required": [ - "first", - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "describedby": { - "type": "string" - }, - "first": { - "minLength": 1, - "type": "string" - }, - "last": { - "type": "string" - }, - "prev": { - "type": "string" - }, - "next": { - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceDocument": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "describedby": { - "type": "string" - } - }, - "additionalProperties": false - }, - "linksInResourceIdentifierCollectionDocument": { + "linksInResourceIdentifierCollectionDocument": { "required": [ "first", "related", @@ -1825,7 +1556,7 @@ }, "nullable": true }, - "nullableChickenIdentifierResponseDocument": { + "nullableEmptyResourceIdentifierResponseDocument": { "required": [ "data", "links" @@ -1835,7 +1566,7 @@ "data": { "oneOf": [ { - "$ref": "#/components/schemas/chickenIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" }, { "$ref": "#/components/schemas/nullValue" @@ -1855,7 +1586,7 @@ }, "additionalProperties": false }, - "nullableChickenSecondaryResponseDocument": { + "nullableEmptyResourceSecondaryResponseDocument": { "required": [ "data", "links" @@ -1865,7 +1596,7 @@ "data": { "oneOf": [ { - "$ref": "#/components/schemas/chickenDataInResponse" + "$ref": "#/components/schemas/emptyResourceDataInResponse" }, { "$ref": "#/components/schemas/nullValue" @@ -1885,7 +1616,7 @@ }, "additionalProperties": false }, - "nullableToOneChickenInRequest": { + "nullableToOneEmptyResourceInRequest": { "required": [ "data" ], @@ -1894,7 +1625,7 @@ "data": { "oneOf": [ { - "$ref": "#/components/schemas/chickenIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" }, { "$ref": "#/components/schemas/nullValue" @@ -1904,7 +1635,7 @@ }, "additionalProperties": false }, - "nullableToOneChickenInResponse": { + "nullableToOneEmptyResourceInResponse": { "required": [ "links" ], @@ -1913,7 +1644,7 @@ "data": { "oneOf": [ { - "$ref": "#/components/schemas/chickenIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" }, { "$ref": "#/components/schemas/nullValue" @@ -1930,7 +1661,366 @@ }, "additionalProperties": false }, - "toManyChickenInRequest": { + "resourceAttributesInPatchRequest": { + "type": "object", + "properties": { + "nonNullableReferenceType": { + "type": "string" + }, + "requiredNonNullableReferenceType": { + "minLength": 1, + "type": "string" + }, + "nullableReferenceType": { + "type": "string", + "nullable": true + }, + "requiredNullableReferenceType": { + "minLength": 1, + "type": "string" + }, + "valueType": { + "type": "integer", + "format": "int32" + }, + "requiredValueType": { + "type": "integer", + "format": "int32" + }, + "nullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredNullableValueType": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "resourceAttributesInPostRequest": { + "required": [ + "nonNullableReferenceType", + "requiredNonNullableReferenceType", + "requiredNullableReferenceType", + "requiredNullableValueType" + ], + "type": "object", + "properties": { + "nonNullableReferenceType": { + "type": "string" + }, + "requiredNonNullableReferenceType": { + "minLength": 1, + "type": "string" + }, + "nullableReferenceType": { + "type": "string", + "nullable": true + }, + "requiredNullableReferenceType": { + "minLength": 1, + "type": "string" + }, + "valueType": { + "type": "integer", + "format": "int32" + }, + "requiredValueType": { + "type": "integer", + "format": "int32" + }, + "nullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredNullableValueType": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "resourceAttributesInResponse": { + "required": [ + "nonNullableReferenceType", + "requiredNonNullableReferenceType", + "requiredNullableReferenceType", + "requiredNullableValueType" + ], + "type": "object", + "properties": { + "nonNullableReferenceType": { + "type": "string" + }, + "requiredNonNullableReferenceType": { + "minLength": 1, + "type": "string" + }, + "nullableReferenceType": { + "type": "string", + "nullable": true + }, + "requiredNullableReferenceType": { + "minLength": 1, + "type": "string" + }, + "valueType": { + "type": "integer", + "format": "int32" + }, + "requiredValueType": { + "type": "integer", + "format": "int32" + }, + "nullableValueType": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "requiredNullableValueType": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "resourceCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/resourceDataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + }, + "additionalProperties": false + }, + "resourceDataInPatchRequest": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/resourceResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/resourceAttributesInPatchRequest" + }, + "relationships": { + "$ref": "#/components/schemas/resourceRelationshipsInPatchRequest" + } + }, + "additionalProperties": false + }, + "resourceDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/resourceResourceType" + }, + "attributes": { + "$ref": "#/components/schemas/resourceAttributesInPostRequest" + }, + "relationships": { + "$ref": "#/components/schemas/resourceRelationshipsInPostRequest" + } + }, + "additionalProperties": false + }, + "resourceDataInResponse": { + "required": [ + "id", + "links", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/resourceResourceType" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/resourceAttributesInResponse" + }, + "relationships": { + "$ref": "#/components/schemas/resourceRelationshipsInResponse" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceObject" + }, + "meta": { + "type": "object", + "additionalProperties": { } + } + }, + "additionalProperties": false + }, + "resourcePatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/resourceDataInPatchRequest" + } + }, + "additionalProperties": false + }, + "resourcePostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/resourceDataInPostRequest" + } + }, + "additionalProperties": false + }, + "resourcePrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/resourceDataInResponse" + }, + "meta": { + "type": "object", + "additionalProperties": { } + }, + "jsonapi": { + "$ref": "#/components/schemas/jsonapiObject" + }, + "links": { + "$ref": "#/components/schemas/linksInResourceDocument" + } + }, + "additionalProperties": false + }, + "resourceRelationshipsInPatchRequest": { + "type": "object", + "properties": { + "nonNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + }, + "requiredNonNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + }, + "nullableToOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + }, + "requiredNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + }, + "toMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + }, + "requiredToMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + }, + "additionalProperties": false + }, + "resourceRelationshipsInPostRequest": { + "required": [ + "nonNullableToOne", + "requiredNonNullableToOne", + "requiredNullableToOne" + ], + "type": "object", + "properties": { + "nonNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + }, + "requiredNonNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + }, + "nullableToOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + }, + "requiredNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + }, + "toMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + }, + "requiredToMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + }, + "additionalProperties": false + }, + "resourceRelationshipsInResponse": { + "required": [ + "nonNullableToOne", + "requiredNonNullableToOne", + "requiredNullableToOne" + ], + "type": "object", + "properties": { + "nonNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + }, + "requiredNonNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + }, + "nullableToOne": { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + }, + "requiredNullableToOne": { + "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + }, + "toMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + }, + "requiredToMany": { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + } + }, + "additionalProperties": false + }, + "resourceResourceType": { + "enum": [ + "Resource" + ], + "type": "string" + }, + "toManyEmptyResourceInRequest": { "required": [ "data" ], @@ -1939,13 +2029,13 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/chickenIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" } } }, "additionalProperties": false }, - "toManyChickenInResponse": { + "toManyEmptyResourceInResponse": { "required": [ "links" ], @@ -1954,7 +2044,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/chickenIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" } }, "links": { @@ -1967,26 +2057,26 @@ }, "additionalProperties": false }, - "toOneChickenInRequest": { + "toOneEmptyResourceInRequest": { "required": [ "data" ], "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/chickenIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" } }, "additionalProperties": false }, - "toOneChickenInResponse": { + "toOneEmptyResourceInResponse": { "required": [ "links" ], "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/chickenIdentifier" + "$ref": "#/components/schemas/emptyResourceIdentifier" }, "links": { "$ref": "#/components/schemas/linksInRelationshipObject" diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs deleted file mode 100644 index a979e26901..0000000000 --- a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Reflection; -using FluentAssertions; -using OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.GeneratedCode; -using Xunit; - -namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; - -public sealed class NullabilityTests -{ - [Theory] - [InlineData(nameof(ChickenAttributesInResponse.Name), NullabilityState.Unknown)] - [InlineData(nameof(ChickenAttributesInResponse.NameOfCurrentFarm), NullabilityState.Unknown)] - [InlineData(nameof(ChickenAttributesInResponse.Age), NullabilityState.NotNull)] - [InlineData(nameof(ChickenAttributesInResponse.Weight), NullabilityState.NotNull)] - [InlineData(nameof(ChickenAttributesInResponse.TimeAtCurrentFarmInDays), NullabilityState.Nullable)] - [InlineData(nameof(ChickenAttributesInResponse.HasProducedEggs), NullabilityState.NotNull)] - public void Nullability_of_generated_property_is_as_expected(string propertyName, NullabilityState expectedState) - { - PropertyInfo[] properties = typeof(ChickenAttributesInResponse).GetProperties(); - PropertyInfo property = properties.Single(property => property.Name == propertyName); - property.Should().HaveNullabilityState(expectedState); - } -} diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledFaker.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledFaker.cs deleted file mode 100644 index 1f63b62ff9..0000000000 --- a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledFaker.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Bogus; -using OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.GeneratedCode; - -namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; - -// @formatter:wrap_chained_method_calls chop_always -// @formatter:keep_existing_linebreaks true - -internal sealed class NullableReferenceTypesDisabledFaker -{ - private readonly Lazy> _lazyHenHousePostRequestDocumentFaker; - private readonly Lazy> _lazyHenHousePatchRequestDocumentFaker; - - private readonly Lazy> _lazyChickenPostRequestDocumentFaker = new(() => - { - Faker attributesInPostRequestFaker = new Faker() - .RuleFor(attributes => attributes.Name, faker => faker.Name.FirstName()) - .RuleFor(attributes => attributes.NameOfCurrentFarm, faker => faker.Company.CompanyName()) - .RuleFor(attributes => attributes.Age, faker => faker.Random.Int(1, 20)) - .RuleFor(attributes => attributes.Weight, faker => faker.Random.Int(20, 50)) - .RuleFor(attributes => attributes.TimeAtCurrentFarmInDays, faker => faker.Random.Int(1, 356)) - .RuleFor(attributes => attributes.HasProducedEggs, _ => true); - - Faker dataInPostRequestFaker = new Faker() - .RuleFor(data => data.Attributes, _ => attributesInPostRequestFaker.Generate()); - - return new Faker() - .RuleFor(document => document.Data, _ => dataInPostRequestFaker.Generate()); - }); - - private readonly Lazy> _lazyChickenPatchRequestDocumentFaker = new(() => - { - Faker attributesInPatchRequestFaker = new Faker() - .RuleFor(attributes => attributes.Name, faker => faker.Name.FirstName()) - .RuleFor(attributes => attributes.NameOfCurrentFarm, faker => faker.Company.CompanyName()) - .RuleFor(attributes => attributes.Age, faker => faker.Random.Int(1, 20)) - .RuleFor(attributes => attributes.Weight, faker => faker.Random.Int(20, 50)) - .RuleFor(attributes => attributes.TimeAtCurrentFarmInDays, faker => faker.Random.Int(1, 356)) - .RuleFor(attributes => attributes.HasProducedEggs, _ => true); - - Faker dataInPatchRequestFaker = new Faker() - // @formatter:wrap_chained_method_calls chop_if_long - .RuleFor(data => data.Id, faker => faker.Random.Int(1, 100).ToString()) - // @formatter:wrap_chained_method_calls restore - .RuleFor(data => data.Attributes, _ => attributesInPatchRequestFaker.Generate()); - - return new Faker() - .RuleFor(document => document.Data, _ => dataInPatchRequestFaker.Generate()); - }); - - private readonly Lazy> _lazyToOneChickenInRequestFaker = new(() => - new Faker() - .RuleFor(relationship => relationship.Data, faker => new ChickenIdentifier - { - // @formatter:wrap_chained_method_calls chop_if_long - Id = faker.Random.Int(1, 100).ToString() - // @formatter:wrap_chained_method_calls restore - })); - - private readonly Lazy> _lazyNullableToOneChickenInRequestFaker = new(() => - new Faker() - .RuleFor(relationship => relationship.Data, faker => new ChickenIdentifier - { - // @formatter:wrap_chained_method_calls chop_if_long - Id = faker.Random.Int(1, 100).ToString() - // @formatter:wrap_chained_method_calls restore - })); - - private readonly Lazy> _lazyToManyChickenInRequestFaker = new(() => - new Faker() - .RuleFor(relationship => relationship.Data, faker => new List - { - new() - { - // @formatter:wrap_chained_method_calls chop_if_long - Id = faker.Random.Int(1, 100).ToString() - // @formatter:wrap_chained_method_calls restore - } - })); - - public Faker ChickenPostRequestDocument => _lazyChickenPostRequestDocumentFaker.Value; - public Faker ChickenPatchRequestDocument => _lazyChickenPatchRequestDocumentFaker.Value; - public Faker HenHousePostRequestDocument => _lazyHenHousePostRequestDocumentFaker.Value; - public Faker HenHousePatchRequestDocument => _lazyHenHousePatchRequestDocumentFaker.Value; - - public NullableReferenceTypesDisabledFaker() - { - _lazyHenHousePostRequestDocumentFaker = new Lazy>(HenHousePostRequestDocumentFakerFactory); - _lazyHenHousePatchRequestDocumentFaker = new Lazy>(HenHousePatchRequestDocumentFakerFactory); - } - - private Faker HenHousePostRequestDocumentFakerFactory() - { - Faker relationshipsInPostRequestFaker = new Faker() - .RuleFor(relationships => relationships.OldestChicken, _ => _lazyNullableToOneChickenInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.FirstChicken, _ => _lazyToOneChickenInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.ChickensReadyForLaying, _ => _lazyToManyChickenInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.AllChickens, _ => _lazyToManyChickenInRequestFaker.Value.Generate()); - - Faker dataInPostRequestFaker = new Faker() - .RuleFor(data => data.Relationships, _ => relationshipsInPostRequestFaker.Generate()); - - return new Faker() - .RuleFor(document => document.Data, _ => dataInPostRequestFaker.Generate()); - } - - private Faker HenHousePatchRequestDocumentFakerFactory() - { - Faker relationshipsInPatchRequestFaker = new Faker() - .RuleFor(relationships => relationships.OldestChicken, _ => _lazyNullableToOneChickenInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.FirstChicken, _ => _lazyToOneChickenInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.ChickensReadyForLaying, _ => _lazyToManyChickenInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.AllChickens, _ => _lazyToManyChickenInRequestFaker.Value.Generate()); - - Faker dataInPatchRequestFaker = new Faker() - // @formatter:wrap_chained_method_calls chop_if_long - .RuleFor(data => data.Id, faker => faker.Random.Int(1, 100).ToString()) - // @formatter:wrap_chained_method_calls restore - .RuleFor(data => data.Relationships, _ => relationshipsInPatchRequestFaker.Generate()); - - return new Faker() - .RuleFor(document => document.Data, _ => dataInPatchRequestFaker.Generate()); - } -} diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/RequestTests.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/RequestTests.cs deleted file mode 100644 index 75d086e6ce..0000000000 --- a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/RequestTests.cs +++ /dev/null @@ -1,638 +0,0 @@ -using System.Net; -using System.Text.Json; -using FluentAssertions; -using FluentAssertions.Specialized; -using JsonApiDotNetCore.Middleware; -using Microsoft.Net.Http.Headers; -using Newtonsoft.Json; -using OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.GeneratedCode; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; - -public sealed class RequestTests -{ - private readonly NullableReferenceTypesDisabledFaker _fakers = new(); - - [Fact] - public async Task Can_clear_reference_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); - requestDocument.Data.Attributes.Name = null; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, - chicken => chicken.Name)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldContainPath("name").With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null)); - }); - } - - [Fact] - public async Task Can_exclude_reference_type_attribute_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); - requestDocument.Data.Attributes.Name = default; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldNotContainPath("name"); - }); - } - - [Fact] - public async Task Cannot_clear_required_reference_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); - requestDocument.Data.Attributes.NameOfCurrentFarm = default; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Cannot write a null value for property 'nameOfCurrentFarm'. Property requires a value. Path 'data.attributes'."); - } - - [Fact] - public async Task Cannot_exclude_required_reference_type_attribute_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); - requestDocument.Data.Attributes.NameOfCurrentFarm = default!; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'nameOfCurrentFarm' must have a value because it is required. Path 'data.attributes'."); - } - } - - [Fact] - public async Task Can_set_default_value_to_value_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); - requestDocument.Data.Attributes.Age = default; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldContainPath("age").With(attribute => attribute.ShouldBeInteger(0)); - }); - } - - [Fact] - public async Task Can_exclude_value_type_attribute_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); - requestDocument.Data.Attributes.Age = default; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldNotContainPath("age"); - }); - } - - [Fact] - public async Task Can_set_default_value_to_required_value_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); - requestDocument.Data.Attributes.Weight = default; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldContainPath("weight").With(attribute => attribute.ShouldBeInteger(0)); - }); - } - - [Fact] - public async Task Cannot_exclude_required_value_type_attribute_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); - requestDocument.Data.Attributes.Weight = default; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'weight' must have a value because it is required. Path 'data.attributes'."); - } - } - - [Fact] - public async Task Can_clear_nullable_value_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); - requestDocument.Data.Attributes.TimeAtCurrentFarmInDays = null; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, - chicken => chicken.TimeAtCurrentFarmInDays)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldContainPath("timeAtCurrentFarmInDays").With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null)); - }); - } - - [Fact] - public async Task Can_exclude_nullable_value_type_attribute_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); - requestDocument.Data.Attributes.TimeAtCurrentFarmInDays = default; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldNotContainPath("timeAtCurrentFarmInDays"); - }); - } - - [Fact] - public async Task Cannot_exclude_required_nullable_value_type_attribute_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); - requestDocument.Data.Attributes.HasProducedEggs = default; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'hasProducedEggs' must have a value because it is required. Path 'data.attributes'."); - } - } - - [Fact] - public async Task Can_set_default_value_to_required_nullable_value_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPostRequestDocument requestDocument = _fakers.ChickenPostRequestDocument.Generate(); - requestDocument.Data.Attributes.HasProducedEggs = default; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostChickenAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/chickens"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldContainPath("hasProducedEggs").With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.False)); - }); - } - - [Fact] - public async Task Can_clear_has_one_relationship() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); - requestDocument.Data.Relationships.OldestChicken.Data = null; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/henHouses"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.relationships.oldestChicken.data").With(relationshipDataObject => - { - relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null); - }); - } - - [Fact] - public async Task Can_exclude_has_one_relationship_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); - requestDocument.Data.Relationships.OldestChicken = default!; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/henHouses"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.relationships").With(relationshipsObject => - { - relationshipsObject.ShouldNotContainPath("oldestChicken"); - }); - } - - [Fact] - public async Task Cannot_exclude_required_has_one_relationship_without_document_registration_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); - requestDocument.Data.Relationships.FirstChicken = default!; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Cannot write a null value for property 'firstChicken'. Property requires a value. Path 'data.relationships'."); - } - - [Fact] - public async Task Cannot_exclude_required_has_one_relationship_with_document_registration_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); - requestDocument.Data.Relationships.FirstChicken = default!; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'firstChicken' must have a value because it is required. Path 'data.relationships'."); - } - } - - [Fact] - public async Task Can_exclude_has_many_relationship_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); - requestDocument.Data.Relationships.AllChickens = default!; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/henHouses"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.relationships").With(relationshipsObject => - { - relationshipsObject.ShouldNotContainPath("allChickens"); - }); - } - - [Fact] - public async Task Cannot_exclude_required_has_many_relationship_with_document_registration_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); - requestDocument.Data.Relationships.ChickensReadyForLaying = default!; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'chickensReadyForLaying' must have a value because it is required. Path 'data.relationships'."); - } - } - - [Fact] - public async Task Cannot_exclude_required_has_many_relationship_without_document_registration_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHousePostRequestDocument requestDocument = _fakers.HenHousePostRequestDocument.Generate(); - requestDocument.Data.Relationships.ChickensReadyForLaying = default!; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostHenHouseAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Cannot write a null value for property 'chickensReadyForLaying'. Property requires a value. Path 'data.relationships'."); - } - - [Fact] - public async Task Cannot_exclude_id_when_performing_PATCH() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPatchRequestDocument requestDocument = _fakers.ChickenPatchRequestDocument.Generate(); - requestDocument.Data.Id = default!; - - // Act - Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchChickenAsync(999, requestDocument)); - - // Assert - await action.Should().ThrowAsync(); - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Cannot write a null value for property 'id'. Property requires a value. Path 'data'."); - } - - [Fact] - public async Task Attributes_required_in_POST_request_are_not_required_in_PATCH_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - ChickenPatchRequestDocument requestDocument = _fakers.ChickenPatchRequestDocument.Generate(); - requestDocument.Data.Attributes.NameOfCurrentFarm = default!; - requestDocument.Data.Attributes.Weight = default!; - requestDocument.Data.Attributes.HasProducedEggs = default!; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PatchChickenAsync(int.Parse(requestDocument.Data.Id), requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be($"http://localhost/chickens/{int.Parse(requestDocument.Data.Id)}"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldNotContainPath("nameOfCurrentFarm"); - attributesObject.ShouldNotContainPath("weight"); - attributesObject.ShouldNotContainPath("hasProducedMilk"); - }); - } - - [Fact] - public async Task Relationships_required_in_POST_request_are_not_required_in_PATCH_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesDisabledClient(wrapper.HttpClient); - - HenHousePatchRequestDocument requestDocument = _fakers.HenHousePatchRequestDocument.Generate(); - requestDocument.Data.Relationships.OldestChicken = default!; - requestDocument.Data.Relationships.FirstChicken = default!; - requestDocument.Data.Relationships.ChickensReadyForLaying = default!; - requestDocument.Data.Relationships.AllChickens = default!; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PatchHenHouseAsync(int.Parse(requestDocument.Data.Id), requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be($"http://localhost/henHouses/{int.Parse(requestDocument.Data.Id)}"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.relationships").With(relationshipsObject => - { - relationshipsObject.ShouldNotContainPath("oldestChicken"); - relationshipsObject.ShouldNotContainPath("firstChicken"); - relationshipsObject.ShouldNotContainPath("favoriteChicken"); - relationshipsObject.ShouldNotContainPath("allChickens"); - }); - } -} diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs deleted file mode 100644 index e3d01f54d4..0000000000 --- a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Reflection; -using FluentAssertions; -using OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.GeneratedCode; -using Xunit; - -namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; - -public sealed class NullabilityTests -{ - [Theory] - [InlineData(nameof(CowAttributesInResponse.Name), NullabilityState.NotNull)] - [InlineData(nameof(CowAttributesInResponse.NameOfCurrentFarm), NullabilityState.NotNull)] - [InlineData(nameof(CowAttributesInResponse.NameOfPreviousFarm), NullabilityState.Nullable)] - [InlineData(nameof(CowAttributesInResponse.Nickname), NullabilityState.NotNull)] - [InlineData(nameof(CowAttributesInResponse.Age), NullabilityState.NotNull)] - [InlineData(nameof(CowAttributesInResponse.Weight), NullabilityState.NotNull)] - [InlineData(nameof(CowAttributesInResponse.TimeAtCurrentFarmInDays), NullabilityState.Nullable)] - [InlineData(nameof(CowAttributesInResponse.HasProducedMilk), NullabilityState.NotNull)] - public void Nullability_of_generated_property_is_as_expected(string propertyName, NullabilityState expectedState) - { - PropertyInfo[] properties = typeof(CowAttributesInResponse).GetProperties(); - PropertyInfo property = properties.Single(property => property.Name == propertyName); - property.Should().HaveNullabilityState(expectedState); - } -} diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledFaker.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledFaker.cs deleted file mode 100644 index c999cecef2..0000000000 --- a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledFaker.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Bogus; -using OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.GeneratedCode; - -namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; - -// @formatter:wrap_chained_method_calls chop_always -// @formatter:keep_existing_linebreaks true - -internal sealed class NullableReferenceTypesEnabledFaker -{ - private readonly Lazy> _lazyCowStablePostRequestDocumentFaker; - private readonly Lazy> _lazyCowStablePatchRequestDocumentFaker; - - private readonly Lazy> _lazyCowPostRequestDocumentFaker = new(() => - { - Faker attributesInPostRequestFaker = new Faker() - .RuleFor(attributes => attributes.Name, faker => faker.Name.FirstName()) - .RuleFor(attributes => attributes.NameOfCurrentFarm, faker => faker.Company.CompanyName()) - .RuleFor(attributes => attributes.NameOfPreviousFarm, faker => faker.Company.CompanyName()) - .RuleFor(attributes => attributes.Nickname, faker => faker.Internet.UserName()) - .RuleFor(attributes => attributes.Age, faker => faker.Random.Int(1, 20)) - .RuleFor(attributes => attributes.Weight, faker => faker.Random.Int(20, 50)) - .RuleFor(attributes => attributes.TimeAtCurrentFarmInDays, faker => faker.Random.Int(1, 356)) - .RuleFor(attributes => attributes.HasProducedMilk, _ => true); - - Faker dataInPostRequestFaker = new Faker() - .RuleFor(data => data.Attributes, _ => attributesInPostRequestFaker.Generate()); - - return new Faker() - .RuleFor(document => document.Data, _ => dataInPostRequestFaker.Generate()); - }); - - private readonly Lazy> _lazyCowPatchRequestDocumentFaker = new(() => - { - Faker attributesInPatchRequestFaker = new Faker() - .RuleFor(attributes => attributes.Name, faker => faker.Name.FirstName()) - .RuleFor(attributes => attributes.NameOfCurrentFarm, faker => faker.Company.CompanyName()) - .RuleFor(attributes => attributes.NameOfPreviousFarm, faker => faker.Company.CompanyName()) - .RuleFor(attributes => attributes.Nickname, faker => faker.Internet.UserName()) - .RuleFor(attributes => attributes.Age, faker => faker.Random.Int(1, 20)) - .RuleFor(attributes => attributes.Weight, faker => faker.Random.Int(20, 50)) - .RuleFor(attributes => attributes.TimeAtCurrentFarmInDays, faker => faker.Random.Int(1, 356)) - .RuleFor(attributes => attributes.HasProducedMilk, _ => true); - - Faker dataInPatchRequestFaker = new Faker() - // @formatter:wrap_chained_method_calls chop_if_long - .RuleFor(data => data.Id, faker => faker.Random.Int(1, 100).ToString()) - // @formatter:wrap_chained_method_calls restore - .RuleFor(data => data.Attributes, _ => attributesInPatchRequestFaker.Generate()); - - return new Faker() - .RuleFor(document => document.Data, _ => dataInPatchRequestFaker.Generate()); - }); - - private readonly Lazy> _lazyToOneCowInRequestFaker = new(() => - new Faker() - .RuleFor(relationship => relationship.Data, faker => new CowIdentifier - { - // @formatter:wrap_chained_method_calls chop_if_long - Id = faker.Random.Int(1, 100).ToString() - // @formatter:wrap_chained_method_calls restore - })); - - private readonly Lazy> _lazyNullableToOneCowInRequestFaker = new(() => - new Faker() - .RuleFor(relationship => relationship.Data, faker => new CowIdentifier - { - // @formatter:wrap_chained_method_calls chop_if_long - Id = faker.Random.Int(1, 100).ToString() - // @formatter:wrap_chained_method_calls restore - })); - - private readonly Lazy> _lazyToManyCowInRequestFaker = new(() => - new Faker() - .RuleFor(relationship => relationship.Data, faker => new List - { - new() - { - // @formatter:wrap_chained_method_calls chop_if_long - Id = faker.Random.Int(1, 100).ToString() - // @formatter:wrap_chained_method_calls restore - } - })); - - public Faker CowPostRequestDocument => _lazyCowPostRequestDocumentFaker.Value; - public Faker CowPatchRequestDocument => _lazyCowPatchRequestDocumentFaker.Value; - public Faker CowStablePostRequestDocument => _lazyCowStablePostRequestDocumentFaker.Value; - public Faker CowStablePatchRequestDocument => _lazyCowStablePatchRequestDocumentFaker.Value; - - public NullableReferenceTypesEnabledFaker() - { - _lazyCowStablePostRequestDocumentFaker = new Lazy>(CreateCowStablePostRequestDocumentFaker); - _lazyCowStablePatchRequestDocumentFaker = new Lazy>(CreateCowStablePatchRequestDocumentFaker); - } - - private Faker CreateCowStablePostRequestDocumentFaker() - { - Faker relationshipsInPostRequestFaker = new Faker() - .RuleFor(relationships => relationships.OldestCow, _ => _lazyToOneCowInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.FirstCow, _ => _lazyToOneCowInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.AlbinoCow, _ => _lazyNullableToOneCowInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.FavoriteCow, _ => _lazyToOneCowInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.CowsReadyForMilking, _ => _lazyToManyCowInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.AllCows, _ => _lazyToManyCowInRequestFaker.Value.Generate()); - - Faker dataInPostRequestFaker = new Faker() - .RuleFor(data => data.Relationships, _ => relationshipsInPostRequestFaker.Generate()); - - return new Faker() - .RuleFor(document => document.Data, _ => dataInPostRequestFaker.Generate()); - } - - private Faker CreateCowStablePatchRequestDocumentFaker() - { - Faker relationshipsInPatchRequestFaker = new Faker() - .RuleFor(relationships => relationships.OldestCow, _ => _lazyToOneCowInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.FirstCow, _ => _lazyToOneCowInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.AlbinoCow, _ => _lazyNullableToOneCowInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.FavoriteCow, _ => _lazyToOneCowInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.CowsReadyForMilking, _ => _lazyToManyCowInRequestFaker.Value.Generate()) - .RuleFor(relationships => relationships.AllCows, _ => _lazyToManyCowInRequestFaker.Value.Generate()); - - Faker dataInPatchRequestFaker = new Faker() - // @formatter:wrap_chained_method_calls chop_if_long - .RuleFor(data => data.Id, faker => faker.Random.Int(1, 100).ToString()) - // @formatter:wrap_chained_method_calls restore - .RuleFor(data => data.Relationships, _ => relationshipsInPatchRequestFaker.Generate()); - - return new Faker() - .RuleFor(document => document.Data, _ => dataInPatchRequestFaker.Generate()); - } -} diff --git a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/RequestTests.cs b/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/RequestTests.cs deleted file mode 100644 index 0c36b0a137..0000000000 --- a/test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/RequestTests.cs +++ /dev/null @@ -1,728 +0,0 @@ -using System.Net; -using System.Text.Json; -using FluentAssertions; -using FluentAssertions.Specialized; -using JsonApiDotNetCore.Middleware; -using Microsoft.Net.Http.Headers; -using Newtonsoft.Json; -using OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.GeneratedCode; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiClientTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; - -public sealed class RequestTests -{ - private readonly NullableReferenceTypesEnabledFaker _fakers = new(); - - [Fact] - public async Task Cannot_exclude_non_nullable_reference_type_attribute_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); - requestDocument.Data.Attributes.Name = default!; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'name' must have a value because it is required. Path 'data.attributes'."); - } - } - - [Fact] - public async Task Cannot_exclude_required_non_nullable_reference_type_attribute_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); - requestDocument.Data.Attributes.NameOfCurrentFarm = default!; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'nameOfCurrentFarm' must have a value because it is required. Path 'data.attributes'."); - } - } - - [Fact] - public async Task Can_exclude_nullable_reference_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); - requestDocument.Data.Attributes.NameOfPreviousFarm = default; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldNotContainPath("nameOfPreviousFarm"); - }); - } - - [Fact] - public async Task Cannot_exclude_required_nullable_reference_type_attribute_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); - requestDocument.Data.Attributes.Nickname = default!; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'nickname' must have a value because it is required. Path 'data.attributes'."); - } - } - - [Fact] - public async Task Can_set_default_value_to_value_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); - requestDocument.Data.Attributes.Age = default; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldContainPath("age").With(attribute => attribute.ShouldBeInteger(0)); - }); - } - - [Fact] - public async Task Can_exclude_value_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); - requestDocument.Data.Attributes.Age = default; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldNotContainPath("age"); - }); - } - - [Fact] - public async Task Can_set_default_value_to_required_value_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); - requestDocument.Data.Attributes.Weight = default; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldContainPath("weight").With(attribute => attribute.ShouldBeInteger(0)); - }); - } - - [Fact] - public async Task Cannot_exclude_required_value_type_attribute_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); - requestDocument.Data.Attributes.Weight = default; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'weight' must have a value because it is required. Path 'data.attributes'."); - } - } - - [Fact] - public async Task Can_clear_nullable_value_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); - requestDocument.Data.Attributes.TimeAtCurrentFarmInDays = null; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument, - cow => cow.TimeAtCurrentFarmInDays)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldContainPath("timeAtCurrentFarmInDays").With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null)); - }); - } - - [Fact] - public async Task Can_exclude_nullable_value_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); - requestDocument.Data.Attributes.TimeAtCurrentFarmInDays = default; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldNotContainPath("timeAtCurrentFarmInDays"); - }); - } - - [Fact] - public async Task Can_set_default_value_to_required_nullable_value_type_attribute() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); - requestDocument.Data.Attributes.HasProducedMilk = default; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/cows"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldContainPath("hasProducedMilk").With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.False)); - }); - } - - [Fact] - public async Task Cannot_exclude_required_nullable_value_type_attribute_in_POST_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPostRequestDocument requestDocument = _fakers.CowPostRequestDocument.Generate(); - requestDocument.Data.Attributes.HasProducedMilk = default; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'hasProducedMilk' must have a value because it is required. Path 'data.attributes'."); - } - } - - [Fact] - public async Task Cannot_exclude_has_one_relationship_in_POST_request_with_document_registration() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); - requestDocument.Data.Relationships.OldestCow = default!; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'oldestCow' must have a value because it is required. Path 'data.relationships'."); - } - } - - [Fact] - public async Task Cannot_exclude_has_one_relationship_in_POST_request_without_document_registration() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); - requestDocument.Data.Relationships.OldestCow = default!; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Cannot write a null value for property 'oldestCow'. Property requires a value. Path 'data.relationships'."); - } - - [Fact] - public async Task Cannot_exclude_required_has_one_relationship_in_POST_request_with_document_registration() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); - requestDocument.Data.Relationships.FirstCow = default!; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'firstCow' must have a value because it is required. Path 'data.relationships'."); - } - } - - [Fact] - public async Task Cannot_exclude_required_has_one_relationship_in_POST_request_without_document_registration() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); - requestDocument.Data.Relationships.FirstCow = default!; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Cannot write a null value for property 'firstCow'. Property requires a value. Path 'data.relationships'."); - } - - [Fact] - public async Task Can_clear_nullable_has_one_relationship() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); - requestDocument.Data.Relationships.AlbinoCow.Data = null; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/cowStables"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.relationships.albinoCow.data").With(relationshipDataObject => - { - relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null); - }); - } - - [Fact] - public async Task Can_exclude_nullable_has_one_relationship() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); - requestDocument.Data.Relationships.AlbinoCow = default!; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/cowStables"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.relationships").With(relationshipsObject => - { - relationshipsObject.ShouldNotContainPath("albinoCow"); - }); - } - - [Fact] - public async Task Cannot_exclude_required_nullable_has_one_relationship_in_POST_request_with_document_registration() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); - requestDocument.Data.Relationships.FavoriteCow = default!; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'favoriteCow' must have a value because it is required. Path 'data.relationships'."); - } - } - - [Fact] - public async Task Cannot_exclude_required_nullable_has_one_relationship_in_POST_request_without_document_registration() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); - requestDocument.Data.Relationships.FavoriteCow = default!; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Cannot write a null value for property 'favoriteCow'. Property requires a value. Path 'data.relationships'."); - } - - [Fact] - public async Task Can_exclude_has_many_relationship() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); - requestDocument.Data.Relationships.CowsReadyForMilking = default!; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Post); - wrapper.Request.RequestUri.Should().Be("http://localhost/cowStables"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.relationships").With(relationshipsObject => - { - relationshipsObject.ShouldNotContainPath("cowsReadyForMilking"); - }); - } - - [Fact] - public async Task Cannot_exclude_required_has_many_relationship_in_POST_request_with_document_registration() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); - requestDocument.Data.Relationships.AllCows = default!; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Ignored property 'allCows' must have a value because it is required. Path 'data.relationships'."); - } - } - - [Fact] - public async Task Cannot_exclude_required_has_many_relationship_in_POST_request_without_document_registration() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStablePostRequestDocument requestDocument = _fakers.CowStablePostRequestDocument.Generate(); - requestDocument.Data.Relationships.AllCows = default!; - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostCowStableAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Cannot write a null value for property 'allCows'. Property requires a value. Path 'data.relationships'."); - } - - [Fact] - public async Task Cannot_exclude_id_when_performing_PATCH() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPatchRequestDocument requestDocument = _fakers.CowPatchRequestDocument.Generate(); - requestDocument.Data.Id = default!; - - // Act - Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowAsync(999, requestDocument)); - - // Assert - await action.Should().ThrowAsync(); - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should().Be("Cannot write a null value for property 'id'. Property requires a value. Path 'data'."); - } - - [Fact] - public async Task Attributes_required_in_POST_request_are_not_required_in_PATCH_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowPatchRequestDocument requestDocument = _fakers.CowPatchRequestDocument.Generate(); - requestDocument.Data.Attributes.Name = default!; - requestDocument.Data.Attributes.NameOfCurrentFarm = default!; - requestDocument.Data.Attributes.Nickname = default!; - requestDocument.Data.Attributes.Weight = default!; - requestDocument.Data.Attributes.HasProducedMilk = default!; - - using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument)) - { - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowAsync(int.Parse(requestDocument.Data.Id), requestDocument)); - } - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be($"http://localhost/cows/{int.Parse(requestDocument.Data.Id)}"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.attributes").With(attributesObject => - { - attributesObject.ShouldNotContainPath("name"); - attributesObject.ShouldNotContainPath("nameOfCurrentFarm"); - attributesObject.ShouldNotContainPath("nickname"); - attributesObject.ShouldNotContainPath("weight"); - attributesObject.ShouldNotContainPath("hasProducedMilk"); - }); - } - - [Fact] - public async Task Relationships_required_in_POST_request_are_not_required_in_PATCH_request() - { - // Arrange - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NullableReferenceTypesEnabledClient(wrapper.HttpClient); - - CowStablePatchRequestDocument requestDocument = _fakers.CowStablePatchRequestDocument.Generate(); - requestDocument.Data.Relationships.OldestCow = default!; - requestDocument.Data.Relationships.FirstCow = default!; - requestDocument.Data.Relationships.FavoriteCow = default!; - requestDocument.Data.Relationships.AllCows = default!; - - // Act - await ApiResponse.TranslateAsync(async () => await apiClient.PatchCowStableAsync(int.Parse(requestDocument.Data.Id), requestDocument)); - - // Assert - wrapper.Request.ShouldNotBeNull(); - wrapper.Request.Headers.GetValue(HeaderNames.Accept).Should().Be(HeaderConstants.MediaType); - wrapper.Request.Method.Should().Be(HttpMethod.Patch); - wrapper.Request.RequestUri.Should().Be($"http://localhost/cowStables/{int.Parse(requestDocument.Data.Id)}"); - wrapper.Request.Content.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType.Should().NotBeNull(); - wrapper.Request.Content!.Headers.ContentType!.ToString().Should().Be(HeaderConstants.MediaType); - - JsonElement document = wrapper.ParseRequestBody(); - - document.ShouldContainPath("data.relationships").With(relationshipsObject => - { - relationshipsObject.ShouldNotContainPath("oldestCow"); - relationshipsObject.ShouldNotContainPath("firstCow"); - relationshipsObject.ShouldNotContainPath("favoriteCow"); - relationshipsObject.ShouldNotContainPath("allCows"); - }); - } -} diff --git a/test/OpenApiTests/LegacyOpenApiIntegration/swagger.json b/test/OpenApiTests/LegacyOpenApiIntegration/swagger.json index 4b6c140fc4..f8c1dcfcb1 100644 --- a/test/OpenApiTests/LegacyOpenApiIntegration/swagger.json +++ b/test/OpenApiTests/LegacyOpenApiIntegration/swagger.json @@ -1970,6 +1970,9 @@ "additionalProperties": false }, "airplane-attributes-in-response": { + "required": [ + "name" + ], "type": "object", "properties": { "name": { @@ -2238,6 +2241,10 @@ "additionalProperties": false }, "flight-attendant-attributes-in-response": { + "required": [ + "email-address", + "profile-image-url" + ], "type": "object", "properties": { "email-address": { @@ -2557,6 +2564,10 @@ "additionalProperties": false }, "flight-attributes-in-response": { + "required": [ + "final-destination", + "services-on-board" + ], "type": "object", "properties": { "final-destination": { @@ -2815,6 +2826,9 @@ "additionalProperties": false }, "flight-relationships-in-response": { + "required": [ + "purser" + ], "type": "object", "properties": { "cabin-crew-members": { diff --git a/test/OpenApiTests/OpenApiTestContext.cs b/test/OpenApiTests/OpenApiTestContext.cs index 78bbd0f08d..e54d09a269 100644 --- a/test/OpenApiTests/OpenApiTestContext.cs +++ b/test/OpenApiTests/OpenApiTestContext.cs @@ -2,7 +2,6 @@ using System.Text.Json; using JetBrains.Annotations; using TestBuildingBlocks; -using SysNotNull = System.Diagnostics.CodeAnalysis.NotNullAttribute; namespace OpenApiTests; diff --git a/test/OpenApiTests/ResourceFieldValidation/EmptyResource.cs b/test/OpenApiTests/ResourceFieldValidation/EmptyResource.cs new file mode 100644 index 0000000000..0d7ddf5f1c --- /dev/null +++ b/test/OpenApiTests/ResourceFieldValidation/EmptyResource.cs @@ -0,0 +1,9 @@ +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; + +namespace OpenApiTests.ResourceFieldValidation; + +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +public class EmptyResource : Identifiable +{ +} diff --git a/test/OpenApiTests/ResourceFieldsValidation/ModelStateValidationDisabledStartup.cs b/test/OpenApiTests/ResourceFieldValidation/ModelStateValidationDisabledStartup.cs similarity index 90% rename from test/OpenApiTests/ResourceFieldsValidation/ModelStateValidationDisabledStartup.cs rename to test/OpenApiTests/ResourceFieldValidation/ModelStateValidationDisabledStartup.cs index f3e39495c3..0bea8b4d58 100644 --- a/test/OpenApiTests/ResourceFieldsValidation/ModelStateValidationDisabledStartup.cs +++ b/test/OpenApiTests/ResourceFieldValidation/ModelStateValidationDisabledStartup.cs @@ -2,7 +2,7 @@ using JsonApiDotNetCore.Configuration; using TestBuildingBlocks; -namespace OpenApiTests.ResourceFieldsValidation; +namespace OpenApiTests.ResourceFieldValidation; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class ModelStateValidationDisabledStartup : OpenApiStartup diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs new file mode 100644 index 0000000000..d6a4d83647 --- /dev/null +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs @@ -0,0 +1,182 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled; + +public sealed class NullabilityTests + : IClassFixture, NullableReferenceTypesDisabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> + _testContext; + + public NullabilityTests( + OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled"; + } + + [Fact] + public async Task Schema_property_for_reference_type_attribute_is_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("referenceType").With(schemaProperty => + { + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + }); + } + + [Fact] + public async Task Schema_property_for_required_reference_type_attribute_is_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("requiredReferenceType").With(schemaProperty => + { + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + }); + } + + [Fact] + public async Task Schema_property_for_value_type_attribute_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("valueType").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_for_required_value_type_attribute_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("requiredValueType").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_for_nullable_value_type_attribute_is_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("nullableValueType").With(schemaProperty => + { + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + }); + } + + [Fact] + public async Task Schema_property_for_required_nullable_value_type_attribute_is_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("requiredNullableValueType").With(schemaProperty => + { + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + }); + } + + [Fact] + public async Task Schema_property_for_to_one_relationship_is_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("toOne.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + }); + }); + } + + [Fact] + public async Task Schema_property_for_required_to_one_relationship_is_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("requiredToOne.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + }); + }); + } + + [Fact] + public async Task Schema_property_for_to_many_relationship_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("toMany.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } + + [Fact] + public async Task Schema_property_for_required_to_many_relationship_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("requiredToMany.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } +} diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/RequiredTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/RequiredTests.cs new file mode 100644 index 0000000000..218af9b286 --- /dev/null +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/RequiredTests.cs @@ -0,0 +1,180 @@ +using System.Text.Json; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled; + +public sealed class RequiredTests + : IClassFixture, NullableReferenceTypesDisabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> + _testContext; + + public RequiredTests( + OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Fact] + public async Task Schema_property_for_reference_type_attribute_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.referenceType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("referenceType")); + }); + } + + [Fact] + public async Task Schema_property_for_required_reference_type_attribute_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.requiredReferenceType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredReferenceType")); + }); + } + + [Fact] + public async Task Schema_property_for_value_type_attribute_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.valueType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("valueType")); + }); + } + + [Fact] + public async Task Schema_property_for_required_value_type_attribute_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.requiredValueType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredValueType")); + }); + } + + [Fact] + public async Task Schema_property_for_nullable_value_type_attribute_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.nullableValueType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("nullableValueType")); + }); + } + + [Fact] + public async Task Schema_property_for_required_nullable_value_type_attribute_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.requiredNullableValueType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNullableValueType")); + }); + } + + [Fact] + public async Task Schema_property_for_to_one_relationship_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.toOne"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("toOne")); + }); + } + + [Fact] + public async Task Schema_property_for_required_to_one_relationship_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.requiredToOne"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredToOne")); + }); + } + + [Fact] + public async Task Schema_property_for_to_many_relationship_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.toMany"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("toMany")); + }); + } + + [Fact] + public async Task Schema_property_for_required_to_many_relationship_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.requiredToMany"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredToMany")); + }); + } + + [Fact] + public async Task No_schema_properties_for_attributes_are_required_when_updating_resource() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.resourceAttributesInPatchRequest.required"); + } + + [Fact] + public async Task No_schema_properties_for_relationships_when_updating_resource() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.resourceRelationshipsInPatchRequest.required"); + } +} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs similarity index 56% rename from test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs rename to test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs index 4620ebd844..3e2f4d4b08 100644 --- a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullabilityTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs @@ -3,7 +3,7 @@ using TestBuildingBlocks; using Xunit; -namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; +namespace OpenApiTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled; public sealed class NullabilityTests : IClassFixture, NullableReferenceTypesDisabledDbContext>> @@ -14,21 +14,20 @@ public NullabilityTests(OpenApiTestContext(); - testContext.UseController(); - testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesDisabled"; + testContext.UseController(); + testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled"; } [Fact] - public async Task Schema_property_is_nullable_for_reference_type_attribute() + public async Task Schema_property_for_reference_type_attribute_is_nullable() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("name").With(schemaProperty => + schemaProperties.ShouldContainPath("referenceType").With(schemaProperty => { schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); }); @@ -36,15 +35,15 @@ public async Task Schema_property_is_nullable_for_reference_type_attribute() } [Fact] - public async Task Schema_property_is_not_nullable_for_required_reference_type_attribute() + public async Task Schema_property_for_required_reference_type_attribute_is_not_nullable() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("nameOfCurrentFarm").With(schemaProperty => + schemaProperties.ShouldContainPath("requiredReferenceType").With(schemaProperty => { schemaProperty.ShouldNotContainPath("nullable"); }); @@ -52,15 +51,15 @@ public async Task Schema_property_is_not_nullable_for_required_reference_type_at } [Fact] - public async Task Schema_property_is_not_nullable_for_value_type_attribute() + public async Task Schema_property_for_value_type_attribute_is_not_nullable() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("age").With(schemaProperty => + schemaProperties.ShouldContainPath("valueType").With(schemaProperty => { schemaProperty.ShouldNotContainPath("nullable"); }); @@ -68,15 +67,15 @@ public async Task Schema_property_is_not_nullable_for_value_type_attribute() } [Fact] - public async Task Schema_property_is_not_nullable_for_required_value_type_attribute() + public async Task Schema_property_for_required_value_type_attribute_is_not_nullable() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("weight").With(schemaProperty => + schemaProperties.ShouldContainPath("requiredValueType").With(schemaProperty => { schemaProperty.ShouldNotContainPath("nullable"); }); @@ -84,15 +83,15 @@ public async Task Schema_property_is_not_nullable_for_required_value_type_attrib } [Fact] - public async Task Schema_property_is_nullable_for_nullable_value_type_attribute() + public async Task Schema_property_for_nullable_value_type_attribute_is_nullable() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("timeAtCurrentFarmInDays").With(schemaProperty => + schemaProperties.ShouldContainPath("nullableValueType").With(schemaProperty => { schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); }); @@ -100,15 +99,15 @@ public async Task Schema_property_is_nullable_for_nullable_value_type_attribute( } [Fact] - public async Task Schema_property_is_not_nullable_for_required_nullable_value_type_attribute() + public async Task Schema_property_for_required_nullable_value_type_attribute_is_not_nullable() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInResponse.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("hasProducedEggs").With(schemaProperty => + schemaProperties.ShouldContainPath("requiredNullableValueType").With(schemaProperty => { schemaProperty.ShouldNotContainPath("nullable"); }); @@ -116,15 +115,15 @@ public async Task Schema_property_is_not_nullable_for_required_nullable_value_ty } [Fact] - public async Task Schema_property_is_nullable_for_has_one_relationship() + public async Task Schema_property_for_to_one_relationship_is_nullable() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("oldestChicken.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath("toOne.$ref").WithSchemaReferenceId(schemaReferenceId => { document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); }); @@ -132,15 +131,15 @@ public async Task Schema_property_is_nullable_for_has_one_relationship() } [Fact] - public async Task Schema_property_is_not_nullable_for_required_has_one_relationship() + public async Task Schema_property_for_required_to_one_relationship_is_not_nullable() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("firstChicken.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath("requiredToOne.$ref").WithSchemaReferenceId(schemaReferenceId => { document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); }); @@ -148,15 +147,15 @@ public async Task Schema_property_is_not_nullable_for_required_has_one_relations } [Fact] - public async Task Schema_property_is_not_nullable_for_has_many_relationship() + public async Task Schema_property_for_to_many_relationship_is_not_nullable() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("chickensReadyForLaying.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath("toMany.$ref").WithSchemaReferenceId(schemaReferenceId => { document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); }); @@ -164,15 +163,15 @@ public async Task Schema_property_is_not_nullable_for_has_many_relationship() } [Fact] - public async Task Schema_property_is_not_nullable_for_required_has_many_relationship() + public async Task Schema_property_for_required_to_many_relationship_is_not_nullable() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.properties").With(schemaProperties => + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("allChickens.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath("requiredToMany.$ref").WithSchemaReferenceId(schemaReferenceId => { document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); }); diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/RequiredTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/RequiredTests.cs new file mode 100644 index 0000000000..3af1cdc0bc --- /dev/null +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/RequiredTests.cs @@ -0,0 +1,181 @@ +using System.Text.Json; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled; + +public sealed class RequiredTests + : IClassFixture, NullableReferenceTypesDisabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; + + public RequiredTests(OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Fact] + public async Task Schema_property_for_reference_type_attribute_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.referenceType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("referenceType")); + }); + } + + [Fact] + public async Task Schema_property_for_required_reference_type_attribute_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.requiredReferenceType"); + + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredReferenceType")); + }); + } + + [Fact] + public async Task Schema_property_for_value_type_attribute_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.valueType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("valueType")); + }); + } + + [Fact] + public async Task Schema_property_for_required_value_type_attribute_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.requiredValueType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("requiredValueType")); + }); + } + + [Fact] + public async Task Schema_property_for_nullable_value_type_attribute_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.nullableValueType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("nullableValueType")); + }); + } + + [Fact] + public async Task Schema_property_for_required_nullable_value_type_attribute_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.requiredNullableValueType"); + + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNullableValueType")); + }); + } + + [Fact] + public async Task Schema_property_for_to_one_relationship_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.toOne"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("toOne")); + }); + } + + [Fact] + public async Task Schema_property_for_required_to_one_relationship_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.requiredToOne"); + + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredToOne")); + }); + } + + [Fact] + public async Task Schema_property_for_to_many_relationship_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.toMany"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("toMany")); + }); + } + + [Fact] + public async Task Schema_property_for_required_to_many_relationship_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.requiredToMany"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("requiredToMany")); + }); + } + + [Fact] + public async Task No_schema_properties_for_attributes_are_required_when_updating_resource() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.resourceAttributesInPatchRequest.required"); + } + + [Fact] + public async Task No_schema_properties_for_relationships_when_updating_resource() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.resourceRelationshipsInPatchRequest.required"); + } +} diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/NrtDisabledResource.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/NrtDisabledResource.cs new file mode 100644 index 0000000000..4a297cf3e9 --- /dev/null +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/NrtDisabledResource.cs @@ -0,0 +1,48 @@ +#nullable disable + +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace OpenApiTests.ResourceFieldValidation.NullableReferenceTypesDisabled; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource(PublicName = "Resource", ControllerNamespace = "OpenApiTests.ResourceFieldValidation")] +public sealed class NrtDisabledResource : Identifiable +{ + [Attr] + public string ReferenceType { get; set; } + + [Attr] + [Required] + public string RequiredReferenceType { get; set; } + + [Attr] + public int ValueType { get; set; } + + [Attr] + [Required] + public int RequiredValueType { get; set; } + + [Attr] + public int? NullableValueType { get; set; } + + [Attr] + [Required] + public int? RequiredNullableValueType { get; set; } + + [HasOne] + public EmptyResource ToOne { get; set; } + + [Required] + [HasOne] + public EmptyResource RequiredToOne { get; set; } + + [HasMany] + public ICollection ToMany { get; set; } = new HashSet(); + + [Required] + [HasMany] + public ICollection RequiredToMany { get; set; } = new HashSet(); +} diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs new file mode 100644 index 0000000000..1e8691d9de --- /dev/null +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs @@ -0,0 +1,35 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; + +namespace OpenApiTests.ResourceFieldValidation.NullableReferenceTypesDisabled; + +// @formatter:wrap_chained_method_calls chop_always + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class NullableReferenceTypesDisabledDbContext : TestableDbContext +{ + public DbSet NrtDisabledResources => Set(); + + public NullableReferenceTypesDisabledDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(resource => resource.ToOne); + + builder.Entity() + .HasOne(resource => resource.RequiredToOne); + + builder.Entity() + .HasMany(resource => resource.ToMany); + + builder.Entity() + .HasMany(resource => resource.RequiredToMany); + + base.OnModelCreating(builder); + } +} diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs new file mode 100644 index 0000000000..520a9e0172 --- /dev/null +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs @@ -0,0 +1,100 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled; + +public sealed class NullabilityTests + : IClassFixture, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> + _testContext; + + public NullabilityTests( + OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled"; + } + + [Theory] + [InlineData("nonNullableReferenceType")] + [InlineData("requiredNonNullableReferenceType")] + [InlineData("valueType")] + [InlineData("requiredValueType")] + public async Task Schema_property_that_describes_attribute_is_not_nullable(string jsonPropertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath(jsonPropertyName).With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Theory] + [InlineData("nullableReferenceType")] + [InlineData("requiredNullableReferenceType")] + [InlineData("nullableValueType")] + [InlineData("requiredNullableValueType")] + public async Task Schema_property_that_describes_attribute_is_nullable(string jsonPropertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath(jsonPropertyName).With(schemaProperty => + { + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + }); + } + + [Theory] + [InlineData("nonNullableToOne")] + [InlineData("requiredNonNullableToOne")] + [InlineData("toMany")] + [InlineData("requiredToMany")] + public async Task Schema_property_that_describes_relationship_is_not_nullable(string jsonPropertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } + + [Theory] + [InlineData("nullableToOne")] + [InlineData("requiredNullableToOne")] + public async Task Schema_property_that_describes_relationship_is_nullable(string jsonPropertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + }); + }); + } +} diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/RequiredTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/RequiredTests.cs new file mode 100644 index 0000000000..c622636ba6 --- /dev/null +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/RequiredTests.cs @@ -0,0 +1,100 @@ +using System.Text.Json; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled; + +public sealed class RequiredTests + : IClassFixture, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> + _testContext; + + public RequiredTests( + OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Theory] + [InlineData("nonNullableReferenceType")] + [InlineData("nullableReferenceType")] + [InlineData("valueType")] + [InlineData("nullableValueType")] + public async Task Schema_property_that_describes_attribute_in_create_resource_is_not_required(string jsonPropertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement(jsonPropertyName)); + }); + } + + [Theory] + [InlineData("requiredNonNullableReferenceType")] + [InlineData("requiredNullableReferenceType")] + [InlineData("requiredValueType")] + [InlineData("requiredNullableValueType")] + public async Task Schema_property_that_describes_attribute_in_create_resource_is_required(string jsonPropertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement(jsonPropertyName)); + }); + } + + [Theory] + [InlineData("requiredNonNullableToOne")] + [InlineData("requiredNullableToOne")] + [InlineData("requiredToMany")] + public async Task Schema_property_that_describes_relationship_in_create_resource_is_required(string jsonPropertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement(jsonPropertyName)); + }); + } + + [Theory] + [InlineData("nonNullableToOne")] + [InlineData("nullableToOne")] + [InlineData("toMany")] + public async Task Schema_property_that_describes_relationship_in_create_resource_is_not_required(string jsonPropertyName) + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement(jsonPropertyName)); + }); + } + + [Fact] + public async Task No_schema_properties_for_relationships_when_updating_resource() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.resourceRelationshipsInPatchRequest.required"); + } +} diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs new file mode 100644 index 0000000000..9a8cc9b69c --- /dev/null +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs @@ -0,0 +1,244 @@ +using System.Text.Json; +using FluentAssertions; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled; + +public sealed class NullabilityTests + : IClassFixture, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; + + public NullabilityTests(OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled"; + } + + [Fact] + public async Task Schema_property_that_describes_non_nullable_reference_type_attribute_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("nonNullableReferenceType").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_non_nullable_reference_type_attribute_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("requiredNonNullableReferenceType").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_nullable_reference_type_attribute_is_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("nullableReferenceType").With(schemaProperty => + { + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_nullable_reference_type_attribute_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("requiredNullableReferenceType").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_value_type_attribute_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("valueType").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_value_type_attribute_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("requiredValueType").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_nullable_value_type_attribute_is_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("nullableValueType").With(schemaProperty => + { + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_nullable_value_type_attribute_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("requiredNullableValueType").With(schemaProperty => + { + schemaProperty.ShouldNotContainPath("nullable"); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_non_nullable_to_one_relationship_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("nonNullableToOne.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_non_nullable_to_one_relationship_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("requiredNonNullableToOne.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_nullable_to_one_relationship_is_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("nullableToOne.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_nullable_to_one_relationship_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("requiredNullableToOne.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_to_many_relationship_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("toMany.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_to_many_relationship_is_not_nullable() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => + { + schemaProperties.ShouldContainPath("requiredToMany.$ref").WithSchemaReferenceId(schemaReferenceId => + { + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + }); + }); + } +} diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/RequiredTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/RequiredTests.cs new file mode 100644 index 0000000000..467d72684d --- /dev/null +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/RequiredTests.cs @@ -0,0 +1,234 @@ +using System.Text.Json; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled; + +public sealed class RequiredTests + : IClassFixture, NullableReferenceTypesEnabledDbContext>> +{ + private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; + + public RequiredTests(OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Fact] + public async Task Schema_property_that_describes_non_nullable_reference_type_attribute_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.nonNullableReferenceType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("nonNullableReferenceType")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_non_nullable_reference_type_attribute_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.requiredNonNullableReferenceType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNonNullableReferenceType")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_nullable_reference_type_attribute_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.nullableReferenceType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("nullableReferenceType")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_nullable_reference_type_attribute_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.requiredNullableReferenceType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNullableReferenceType")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_value_type_attribute_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.valueType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("valueType")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_value_type_attribute_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.requiredValueType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("requiredValueType")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_nullable_value_type_attribute_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.nullableValueType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("nullableValueType")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_nullable_value_type_attribute_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + { + attributesObjectSchema.ShouldContainPath("properties.requiredNullableValueType"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNullableValueType")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_non_nullable_to_one_relationship_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.nonNullableToOne"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("nonNullableToOne")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_non_nullable_to_one_relationship_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.requiredNonNullableToOne"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNonNullableToOne")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_nullable_to_one_relationship_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.nullableToOne"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("nullableToOne")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_nullable_to_one_relationship_is_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.requiredNullableToOne"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNullableToOne")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_to_many_relationship_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.toMany"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("toMany")); + }); + } + + [Fact] + public async Task Schema_property_that_describes_required_to_many_relationship_is_not_required() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + { + relationshipsObjectSchema.ShouldContainPath("properties.requiredToMany"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("requiredToMany")); + }); + } + + [Fact] + public async Task No_schema_properties_for_attributes_are_required_when_updating_resource() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.resourceAttributesInPatchRequest.required"); + } + + [Fact] + public async Task No_schema_properties_for_relationships_when_updating_resource() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.resourceRelationshipsInPatchRequest.required"); + } +} diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/NrtEnabledResource.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/NrtEnabledResource.cs new file mode 100644 index 0000000000..8908f2a50f --- /dev/null +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/NrtEnabledResource.cs @@ -0,0 +1,60 @@ +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace OpenApiTests.ResourceFieldValidation.NullableReferenceTypesEnabled; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource(PublicName = "Resource", ControllerNamespace = "OpenApiTests.ResourceFieldValidation")] +public sealed class NrtEnabledResource : Identifiable +{ + [Attr] + public string NonNullableReferenceType { get; set; } = null!; + + [Attr] + [Required] + public string RequiredNonNullableReferenceType { get; set; } = null!; + + [Attr] + public string? NullableReferenceType { get; set; } + + [Attr] + [Required] + public string? RequiredNullableReferenceType { get; set; } + + [Attr] + public int ValueType { get; set; } + + [Attr] + [Required] + public int RequiredValueType { get; set; } + + [Attr] + public int? NullableValueType { get; set; } + + [Attr] + [Required] + public int? RequiredNullableValueType { get; set; } + + [HasOne] + public EmptyResource NonNullableToOne { get; set; } = null!; + + [Required] + [HasOne] + public EmptyResource RequiredNonNullableToOne { get; set; } = null!; + + [HasOne] + public EmptyResource? NullableToOne { get; set; } + + [Required] + [HasOne] + public EmptyResource? RequiredNullableToOne { get; set; } + + [HasMany] + public ICollection ToMany { get; set; } = new HashSet(); + + [Required] + [HasMany] + public ICollection RequiredToMany { get; set; } = new HashSet(); +} diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs new file mode 100644 index 0000000000..54f1620db5 --- /dev/null +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs @@ -0,0 +1,41 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; + +namespace OpenApiTests.ResourceFieldValidation.NullableReferenceTypesEnabled; + +// @formatter:wrap_chained_method_calls chop_always + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class NullableReferenceTypesEnabledDbContext : TestableDbContext +{ + public DbSet NullableReferenceTypesEnabledResources => Set(); + + public NullableReferenceTypesEnabledDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(resource => resource.NonNullableToOne); + + builder.Entity() + .HasOne(resource => resource.RequiredNonNullableToOne); + + builder.Entity() + .HasOne(resource => resource.NullableToOne); + + builder.Entity() + .HasOne(resource => resource.RequiredNullableToOne); + + builder.Entity() + .HasMany(resource => resource.ToMany); + + builder.Entity() + .HasMany(resource => resource.RequiredToMany); + + base.OnModelCreating(builder); + } +} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Chicken.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Chicken.cs deleted file mode 100644 index 53c1a17f98..0000000000 --- a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Chicken.cs +++ /dev/null @@ -1,34 +0,0 @@ -#nullable disable - -using System.ComponentModel.DataAnnotations; -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(ControllerNamespace = "OpenApiTests.ResourceFieldsValidation")] -public sealed class Chicken : Identifiable -{ - [Attr] - public string Name { get; set; } - - [Attr] - [Required] - public string NameOfCurrentFarm { get; set; } - - [Attr] - public int Age { get; set; } - - [Attr] - [Required] - public int Weight { get; set; } - - [Attr] - public int? TimeAtCurrentFarmInDays { get; set; } - - [Attr] - [Required] - public bool? HasProducedEggs { get; set; } -} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/HenHouse.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/HenHouse.cs deleted file mode 100644 index 8c24664eec..0000000000 --- a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/HenHouse.cs +++ /dev/null @@ -1,27 +0,0 @@ -#nullable disable - -using System.ComponentModel.DataAnnotations; -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(ControllerNamespace = "OpenApiTests.ResourceFieldsValidation")] -public sealed class HenHouse : Identifiable -{ - [HasOne] - public Chicken OldestChicken { get; set; } - - [Required] - [HasOne] - public Chicken FirstChicken { get; set; } - - [HasMany] - public ICollection AllChickens { get; set; } = new HashSet(); - - [Required] - [HasMany] - public ICollection ChickensReadyForLaying { get; set; } = new HashSet(); -} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs deleted file mode 100644 index 6562979252..0000000000 --- a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs +++ /dev/null @@ -1,36 +0,0 @@ -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; -using TestBuildingBlocks; - -namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesDisabled; - -// @formatter:wrap_chained_method_calls chop_always - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class NullableReferenceTypesDisabledDbContext : TestableDbContext -{ - public DbSet Chicken => Set(); - public DbSet HenHouse => Set(); - - public NullableReferenceTypesDisabledDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - builder.Entity() - .HasOne(resource => resource.OldestChicken); - - builder.Entity() - .HasOne(resource => resource.FirstChicken); - - builder.Entity() - .HasMany(resource => resource.AllChickens); - - builder.Entity() - .HasMany(resource => resource.ChickensReadyForLaying); - - base.OnModelCreating(builder); - } -} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationDisabledTests.cs deleted file mode 100644 index d2d7669b34..0000000000 --- a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationDisabledTests.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.Required; - -public sealed class ModelStateValidationDisabledTests - : IClassFixture, NullableReferenceTypesDisabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> - _testContext; - - public ModelStateValidationDisabledTests( - OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - testContext.UseController(); - } - - [Fact] - public async Task Schema_property_is_not_required_for_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("name"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("nameOfCurrentFarm"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("age"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("weight"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_nullable_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("timeAtCurrentFarmInDays"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_nullable_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("hasProducedEggs"); - }); - } - - [Fact] - public async Task No_schema_properties_for_attributes_are_required_in_PATCH_request() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); - } - - [Fact] - public async Task Schema_property_is_not_required_for_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("oldestChicken"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredAttributes.Should().Contain("firstChicken"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_has_many_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("allChickens"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_has_many_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredAttributes.Should().Contain("chickensReadyForLaying"); - }); - } - - [Fact] - public async Task No_schema_properties_for_relationships_are_required_in_PATCH_request() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.henHouseRelationshipsInPatchRequest.required"); - } -} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationEnabledTests.cs deleted file mode 100644 index 24b01433c2..0000000000 --- a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesDisabled/Required/ModelStateValidationEnabledTests.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesDisabled.Required; - -public sealed class ModelStateValidationEnabledTests - : IClassFixture, NullableReferenceTypesDisabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesDisabledDbContext> _testContext; - - public ModelStateValidationEnabledTests( - OpenApiTestContext, NullableReferenceTypesDisabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - testContext.UseController(); - } - - [Fact] - public async Task Schema_property_is_not_required_for_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("name"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("nameOfCurrentFarm"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("age"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("weight"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_nullable_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("timeAtCurrentFarmInDays"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_nullable_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.chickenAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("hasProducedEggs"); - }); - } - - [Fact] - public async Task No_schema_properties_for_attributes_are_required_in_PATCH_request() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.chickenAttributesInPatchRequest.required"); - } - - [Fact] - public async Task Schema_property_is_not_required_for_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("oldestChicken"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredAttributes.Should().Contain("firstChicken"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_has_many_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("allChickens"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_has_many_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.henHouseRelationshipsInPostRequest.required").With(propertySet => - { - var requiredAttributes = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredAttributes.Should().Contain("chickensReadyForLaying"); - }); - } - - [Fact] - public async Task No_schema_properties_for_relationships_are_required_in_PATCH_request() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.henHouseRelationshipsInPatchRequest.required"); - } -} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Cow.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Cow.cs deleted file mode 100644 index 4a09687602..0000000000 --- a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Cow.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(ControllerNamespace = "OpenApiTests.ResourceFieldsValidation")] -public sealed class Cow : Identifiable -{ - [Attr] - public string Name { get; set; } = null!; - - [Attr] - [Required] - public string NameOfCurrentFarm { get; set; } = null!; - - [Attr] - public string? NameOfPreviousFarm { get; set; } - - [Attr] - [Required] - public string? Nickname { get; set; } - - [Attr] - public int Age { get; set; } - - [Attr] - [Required] - public int Weight { get; set; } - - [Attr] - public int? TimeAtCurrentFarmInDays { get; set; } - - [Attr] - [Required] - public bool? HasProducedMilk { get; set; } -} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/CowStable.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/CowStable.cs deleted file mode 100644 index 6b7af3c5d0..0000000000 --- a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/CowStable.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(ControllerNamespace = "OpenApiTests.ResourceFieldsValidation")] -public sealed class CowStable : Identifiable -{ - [HasOne] - public Cow OldestCow { get; set; } = null!; - - [Required] - [HasOne] - public Cow FirstCow { get; set; } = null!; - - [HasOne] - public Cow? AlbinoCow { get; set; } - - [Required] - [HasOne] - public Cow? FavoriteCow { get; set; } - - [HasMany] - public ICollection CowsReadyForMilking { get; set; } = new HashSet(); - - [Required] - [HasMany] - public ICollection AllCows { get; set; } = new HashSet(); -} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs deleted file mode 100644 index 82b2a94b8c..0000000000 --- a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullabilityTests.cs +++ /dev/null @@ -1,245 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; - -public sealed class NullabilityTests - : IClassFixture, NullableReferenceTypesEnabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; - - public NullabilityTests(OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - testContext.UseController(); - testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/ResourceFieldsValidation/NullableReferenceTypesEnabled"; - } - - [Fact] - public async Task Schema_property_is_not_nullable_for_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("name").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_is_not_nullable_for_required_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("nameOfCurrentFarm").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_is_nullable_for_nullable_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("nameOfPreviousFarm").With(schemaProperty => - { - schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); - }); - }); - } - - [Fact] - public async Task Schema_property_is_not_nullable_for_required_nullable_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("nickname").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_is_not_nullable_for_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("age").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_is_not_nullable_for_required_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("weight").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_is_nullable_for_nullable_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("timeAtCurrentFarmInDays").With(schemaProperty => - { - schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); - }); - }); - } - - [Fact] - public async Task Schema_property_is_not_nullable_for_required_nullable_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("hasProducedMilk").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_is_not_nullable_for_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("oldestCow.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } - - [Fact] - public async Task Schema_property_is_not_nullable_for_required_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("firstCow.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } - - [Fact] - public async Task Schema_property_is_nullable_for_nullable_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("albinoCow.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); - }); - }); - } - - [Fact] - public async Task Schema_property_is_not_nullable_for_required_nullable_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("favoriteCow.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } - - [Fact] - public async Task Schema_property_is_not_nullable_for_has_many_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("cowsReadyForMilking.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } - - [Fact] - public async Task Schema_property_is_not_nullable_for_required_has_many_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("allCows.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } -} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs deleted file mode 100644 index d8ad163bdd..0000000000 --- a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs +++ /dev/null @@ -1,42 +0,0 @@ -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; -using TestBuildingBlocks; - -namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesEnabled; - -// @formatter:wrap_chained_method_calls chop_always - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class NullableReferenceTypesEnabledDbContext : TestableDbContext -{ - public DbSet Cow => Set(); - public DbSet CowStable => Set(); - - public NullableReferenceTypesEnabledDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - builder.Entity() - .HasOne(resource => resource.OldestCow); - - builder.Entity() - .HasOne(resource => resource.FirstCow); - - builder.Entity() - .HasOne(resource => resource.AlbinoCow); - - builder.Entity() - .HasOne(resource => resource.FavoriteCow); - - builder.Entity() - .HasMany(resource => resource.AllCows); - - builder.Entity() - .HasMany(resource => resource.CowsReadyForMilking); - - base.OnModelCreating(builder); - } -} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationDisabledTests.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationDisabledTests.cs deleted file mode 100644 index 17ac968dee..0000000000 --- a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationDisabledTests.cs +++ /dev/null @@ -1,252 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.Required; - -public sealed class ModelStateValidationDisabledTests - : IClassFixture, NullableReferenceTypesEnabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> - _testContext; - - public ModelStateValidationDisabledTests( - OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - testContext.UseController(); - } - - [Fact] - public async Task Schema_property_is_required_for_non_nullable_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("name"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_non_nullable_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("nameOfCurrentFarm"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_nullable_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("nameOfPreviousFarm"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_nullable_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("nickname"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("age"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("weight"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_nullable_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("timeAtCurrentFarmInDays"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_nullable_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("hasProducedMilk"); - }); - } - - [Fact] - public async Task No_schema_properties_for_attributes_are_required_in_PATCH_request() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.cowStableAttributesInPatchRequest.required"); - } - - [Fact] - public async Task Schema_property_is_required_for_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("oldestCow"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("firstCow"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_nullable_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("albinoCow"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_nullable_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("favoriteCow"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_has_many_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("cowsReadyForMilking"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_has_many_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("allCows"); - }); - } - - [Fact] - public async Task No_schema_properties_for_relationships_are_required_in_PATCH_request() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.cowStableRelationshipsInPatchRequest.required"); - } -} diff --git a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationEnabledTests.cs b/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationEnabledTests.cs deleted file mode 100644 index 24146e7bba..0000000000 --- a/test/OpenApiTests/ResourceFieldsValidation/NullableReferenceTypesEnabled/Required/ModelStateValidationEnabledTests.cs +++ /dev/null @@ -1,251 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using TestBuildingBlocks; -using Xunit; - -namespace OpenApiTests.ResourceFieldsValidation.NullableReferenceTypesEnabled.Required; - -public sealed class ModelStateValidationEnabledTests - : IClassFixture, NullableReferenceTypesEnabledDbContext>> -{ - private readonly OpenApiTestContext, NullableReferenceTypesEnabledDbContext> _testContext; - - public ModelStateValidationEnabledTests( - OpenApiTestContext, NullableReferenceTypesEnabledDbContext> testContext) - { - _testContext = testContext; - - testContext.UseController(); - testContext.UseController(); - } - - [Fact] - public async Task Schema_property_is_required_for_non_nullable_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("name"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_non_nullable_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("nameOfCurrentFarm"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_nullable_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("nameOfPreviousFarm"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_nullable_reference_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("nickname"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("age"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("weight"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_nullable_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("timeAtCurrentFarmInDays"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_nullable_value_type_attribute() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowAttributesInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("hasProducedMilk"); - }); - } - - [Fact] - public async Task No_schema_properties_for_attributes_are_required_in_PATCH_request() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.cowStableAttributesInPatchRequest.required"); - } - - [Fact] - public async Task Schema_property_is_required_for_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("oldestCow"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("firstCow"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_nullable_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("albinoCow"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_nullable_has_one_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("favoriteCow"); - }); - } - - [Fact] - public async Task Schema_property_is_not_required_for_has_many_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().NotContain("cowsReadyForMilking"); - }); - } - - [Fact] - public async Task Schema_property_is_required_for_required_has_many_relationship() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.cowStableRelationshipsInPostRequest.required").With(propertySet => - { - var requiredProperties = JsonSerializer.Deserialize>(propertySet.GetRawText()); - - requiredProperties.Should().Contain("allCows"); - }); - } - - [Fact] - public async Task No_schema_properties_for_relationships_are_required_in_PATCH_request() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldNotContainPath("components.schemas.cowStableRelationshipsInPatchRequest.required"); - } -} diff --git a/test/TestBuildingBlocks/JsonElementExtensions.cs b/test/TestBuildingBlocks/JsonElementExtensions.cs index ee5c362489..6eafecc4ca 100644 --- a/test/TestBuildingBlocks/JsonElementExtensions.cs +++ b/test/TestBuildingBlocks/JsonElementExtensions.cs @@ -32,6 +32,22 @@ public static void ShouldBeString(this JsonElement source, string value) source.GetString().Should().Be(value); } + public static void ShouldBeArrayWithElement(this JsonElement source, string value) + { + source.ValueKind.Should().Be(JsonValueKind.Array); + + var deserializedCollection = JsonSerializer.Deserialize>(source.GetRawText()); + deserializedCollection.Should().Contain(value); + } + + public static void ShouldBeArrayWithoutElement(this JsonElement source, string value) + { + source.ValueKind.Should().Be(JsonValueKind.Array); + + var deserializedCollection = JsonSerializer.Deserialize>(source.GetRawText()); + deserializedCollection.Should().NotContain(value); + } + public static void ShouldBeInteger(this JsonElement source, int value) { source.ValueKind.Should().Be(JsonValueKind.Number); From 63ba43da0c171d0b39f211894df5934b77404146 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Wed, 4 Jan 2023 13:35:38 +0100 Subject: [PATCH 23/26] Remove non-sensical testcases. Add caching in ObjectExtensions. --- test/OpenApiClientTests/ObjectExtensions.cs | 28 +++++-- .../CreateResourceTests.cs | 83 ------------------- .../ResourceFieldValidationFakers.cs | 1 - .../ResourceFieldValidationFakers.cs | 1 - .../CreateResourceTests.cs | 4 - .../ResourceFieldValidationFakers.cs | 1 - .../ResourceFieldValidationFakers.cs | 1 - 7 files changed, 22 insertions(+), 97 deletions(-) diff --git a/test/OpenApiClientTests/ObjectExtensions.cs b/test/OpenApiClientTests/ObjectExtensions.cs index cdf729c09b..7225702d5c 100644 --- a/test/OpenApiClientTests/ObjectExtensions.cs +++ b/test/OpenApiClientTests/ObjectExtensions.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Reflection; using JsonApiDotNetCore.OpenApi.Client; @@ -5,14 +6,29 @@ namespace OpenApiClientTests; internal static class ObjectExtensions { + private static readonly ConcurrentDictionary PropertyInfoCache = new(); + public static object? GetPropertyValue(this object source, string propertyName) { ArgumentGuard.NotNull(source); ArgumentGuard.NotNull(propertyName); - PropertyInfo propertyInfo = source.GetType().GetProperties().Single(attribute => attribute.Name == propertyName); + string cacheKey = EnsurePropertyInfoIsCached(source, propertyName); + + return PropertyInfoCache[cacheKey].GetValue(source); + } + + private static string EnsurePropertyInfoIsCached(object source, string propertyName) + { + string cacheKey = $"{source.GetType().FullName}-{propertyName}"; + + if (!PropertyInfoCache.ContainsKey(cacheKey)) + { + PropertyInfo propertyInfo = source.GetType().GetProperties().Single(attribute => attribute.Name == propertyName); + PropertyInfoCache[cacheKey] = propertyInfo; + } - return propertyInfo.GetValue(source); + return cacheKey; } public static void SetPropertyValue(this object source, string propertyName, object? value) @@ -20,9 +36,9 @@ public static void SetPropertyValue(this object source, string propertyName, obj ArgumentGuard.NotNull(source); ArgumentGuard.NotNull(propertyName); - PropertyInfo propertyInfo = source.GetType().GetProperties().Single(attribute => attribute.Name == propertyName); + string cacheKey = EnsurePropertyInfoIsCached(source, propertyName); - propertyInfo.SetValue(source, value); + PropertyInfoCache[cacheKey].SetValue(source, value); } public static object? GetDefaultValueForProperty(this object source, string propertyName) @@ -30,8 +46,8 @@ public static void SetPropertyValue(this object source, string propertyName, obj ArgumentGuard.NotNull(source); ArgumentGuard.NotNull(propertyName); - PropertyInfo propertyInfo = source.GetType().GetProperties().Single(attribute => attribute.Name == propertyName); + string cacheKey = EnsurePropertyInfoIsCached(source, propertyName); - return Activator.CreateInstance(propertyInfo.PropertyType); + return Activator.CreateInstance(PropertyInfoCache[cacheKey].PropertyType); } } diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/CreateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/CreateResourceTests.cs index 8888d8f56d..5dfa347b05 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/CreateResourceTests.cs +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/CreateResourceTests.cs @@ -442,87 +442,4 @@ public async Task Can_exclude_relationship_without_partial_attribute_serializati relationshipsObject.ShouldNotContainPath(jsonPropertyName); }); } - - [Theory] - [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), "requiredToOne", Skip = "Known limitation")] - [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany", Skip = "Known limitation")] - public async Task Cannot_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) - { - // Arrange - var requestDocument = new ResourcePostRequestDocument - { - Data = new ResourceDataInPostRequest - { - Attributes = _fakers.PostAttributes.Generate(), - Relationships = new ResourceRelationshipsInPostRequest - { - ToOne = _fakers.NullableToOne.Generate(), - RequiredToOne = _fakers.NullableToOne.Generate(), - ToMany = _fakers.ToMany.Generate(), - RequiredToMany = _fakers.ToMany.Generate() - } - } - }; - - ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); - object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); - requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); - - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); - - using (apiClient.WithPartialAttributeSerialization(requestDocument)) - { - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should() - .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'."); - } - } - - [Theory] - [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), "requiredToOne", Skip = "Known limitation")] - [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany", Skip = "Known limitation")] - public async Task Cannot_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) - { - // Arrange - var requestDocument = new ResourcePostRequestDocument - { - Data = new ResourceDataInPostRequest - { - Attributes = _fakers.PostAttributes.Generate(), - Relationships = new ResourceRelationshipsInPostRequest - { - ToOne = _fakers.NullableToOne.Generate(), - RequiredToOne = _fakers.NullableToOne.Generate(), - ToMany = _fakers.ToMany.Generate(), - RequiredToMany = _fakers.ToMany.Generate() - } - } - }; - - ResourceRelationshipsInPostRequest emptyRelationshipsObject = new(); - object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName); - requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue); - - using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null); - var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient); - - // Act - Func> action = async () => - await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument)); - - // Assert - ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); - JsonSerializationException exception = assertion.Subject.Single(); - - exception.Message.Should() - .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'."); - } } diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs index 1bc729cbd9..817344c11c 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs @@ -1,6 +1,5 @@ using Bogus; using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode; -using TestBuildingBlocks; namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled; diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs index 2960441a6c..e3dbbd0d36 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs @@ -1,6 +1,5 @@ using Bogus; using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode; -using TestBuildingBlocks; namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled; diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/CreateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/CreateResourceTests.cs index 27e9c06b5c..0ca3a6fb82 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/CreateResourceTests.cs +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/CreateResourceTests.cs @@ -524,8 +524,6 @@ public async Task Can_exclude_relationship_without_partial_attribute_serializati [Theory] [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), "requiredNonNullableToOne")] - [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), "requiredNullableToOne", Skip = "Known limitation")] - [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany", Skip = "Known limitation")] public async Task Cannot_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) { // Arrange @@ -570,8 +568,6 @@ public async Task Cannot_exclude_relationship_with_partial_attribute_serializati [Theory] [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), "requiredNonNullableToOne")] - [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), "requiredNullableToOne", Skip = "Known limitation")] - [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany", Skip = "Known limitation")] public async Task Cannot_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName) { // Arrange diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs index 1c31f040b8..b0c9c260c7 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs @@ -1,6 +1,5 @@ using Bogus; using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled.GeneratedCode; -using TestBuildingBlocks; namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled; diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs index 8389b8bd2c..c032f0c073 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs @@ -1,6 +1,5 @@ using Bogus; using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled.GeneratedCode; -using TestBuildingBlocks; namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled; From 3e9708a28076fb2895c60a4ba3044f4745fd77e7 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 5 Jan 2023 21:13:31 +0100 Subject: [PATCH 24/26] Remove overlooked code duplication in OpenApiTests, revert reflection caching in object extension --- test/OpenApiClientTests/ObjectExtensions.cs | 28 +-- .../NullabilityTests.cs | 130 ++---------- .../RequiredTests.cs | 130 +++--------- .../NullabilityTests.cs | 130 ++---------- .../RequiredTests.cs | 133 +++--------- .../NullabilityTests.cs | 44 ++-- .../RequiredTests.cs | 40 ++-- .../NullabilityTests.cs | 198 +++--------------- .../RequiredTests.cs | 190 +++-------------- 9 files changed, 211 insertions(+), 812 deletions(-) diff --git a/test/OpenApiClientTests/ObjectExtensions.cs b/test/OpenApiClientTests/ObjectExtensions.cs index 7225702d5c..9bf15b6ac7 100644 --- a/test/OpenApiClientTests/ObjectExtensions.cs +++ b/test/OpenApiClientTests/ObjectExtensions.cs @@ -1,4 +1,3 @@ -using System.Collections.Concurrent; using System.Reflection; using JsonApiDotNetCore.OpenApi.Client; @@ -6,29 +5,14 @@ namespace OpenApiClientTests; internal static class ObjectExtensions { - private static readonly ConcurrentDictionary PropertyInfoCache = new(); - public static object? GetPropertyValue(this object source, string propertyName) { ArgumentGuard.NotNull(source); ArgumentGuard.NotNull(propertyName); - string cacheKey = EnsurePropertyInfoIsCached(source, propertyName); - - return PropertyInfoCache[cacheKey].GetValue(source); - } - - private static string EnsurePropertyInfoIsCached(object source, string propertyName) - { - string cacheKey = $"{source.GetType().FullName}-{propertyName}"; - - if (!PropertyInfoCache.ContainsKey(cacheKey)) - { - PropertyInfo propertyInfo = source.GetType().GetProperties().Single(attribute => attribute.Name == propertyName); - PropertyInfoCache[cacheKey] = propertyInfo; - } + PropertyInfo propertyInfo = source.GetType().GetProperties().Single(property => property.Name == propertyName); - return cacheKey; + return propertyInfo.GetValue(source); } public static void SetPropertyValue(this object source, string propertyName, object? value) @@ -36,9 +20,9 @@ public static void SetPropertyValue(this object source, string propertyName, obj ArgumentGuard.NotNull(source); ArgumentGuard.NotNull(propertyName); - string cacheKey = EnsurePropertyInfoIsCached(source, propertyName); + PropertyInfo propertyInfo = source.GetType().GetProperties().Single(property => property.Name == propertyName); - PropertyInfoCache[cacheKey].SetValue(source, value); + propertyInfo.SetValue(source, value); } public static object? GetDefaultValueForProperty(this object source, string propertyName) @@ -46,8 +30,8 @@ public static void SetPropertyValue(this object source, string propertyName, obj ArgumentGuard.NotNull(source); ArgumentGuard.NotNull(propertyName); - string cacheKey = EnsurePropertyInfoIsCached(source, propertyName); + PropertyInfo propertyInfo = source.GetType().GetProperties().Single(property => property.Name == propertyName); - return Activator.CreateInstance(PropertyInfoCache[cacheKey].PropertyType); + return Activator.CreateInstance(propertyInfo.PropertyType); } } diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs index d6a4d83647..40c6c5558a 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs @@ -20,8 +20,12 @@ public NullabilityTests( testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled"; } - [Fact] - public async Task Schema_property_for_reference_type_attribute_is_nullable() + [Theory] + [InlineData("referenceType")] + [InlineData("requiredReferenceType")] + [InlineData("nullableValueType")] + [InlineData("requiredNullableValueType")] + public async Task Schema_property_for_attribute_is_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -29,15 +33,17 @@ public async Task Schema_property_for_reference_type_attribute_is_nullable() // Assert document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("referenceType").With(schemaProperty => + schemaProperties.ShouldContainPath(jsonPropertyName).With(schemaProperty => { schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); }); }); } - [Fact] - public async Task Schema_property_for_required_reference_type_attribute_is_nullable() + [Theory] + [InlineData("valueType")] + [InlineData("requiredValueType")] + public async Task Schema_property_for_attribute_is_not_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -45,79 +51,17 @@ public async Task Schema_property_for_required_reference_type_attribute_is_nulla // Assert document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("requiredReferenceType").With(schemaProperty => - { - schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); - }); - }); - } - - [Fact] - public async Task Schema_property_for_value_type_attribute_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("valueType").With(schemaProperty => + schemaProperties.ShouldContainPath(jsonPropertyName).With(schemaProperty => { schemaProperty.ShouldNotContainPath("nullable"); }); }); } - [Fact] - public async Task Schema_property_for_required_value_type_attribute_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("requiredValueType").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_for_nullable_value_type_attribute_is_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("nullableValueType").With(schemaProperty => - { - schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); - }); - }); - } - - [Fact] - public async Task Schema_property_for_required_nullable_value_type_attribute_is_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("requiredNullableValueType").With(schemaProperty => - { - schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); - }); - }); - } - - [Fact] - public async Task Schema_property_for_to_one_relationship_is_nullable() + [Theory] + [InlineData("toOne")] + [InlineData("requiredToOne")] + public async Task Schema_property_for_relationship_is_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -125,47 +69,17 @@ public async Task Schema_property_for_to_one_relationship_is_nullable() // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("toOne.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => { document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); }); }); } - [Fact] - public async Task Schema_property_for_required_to_one_relationship_is_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("requiredToOne.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); - }); - }); - } - - [Fact] - public async Task Schema_property_for_to_many_relationship_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("toMany.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } - - [Fact] - public async Task Schema_property_for_required_to_many_relationship_is_not_nullable() + [Theory] + [InlineData("toMany")] + [InlineData("requiredToMany")] + public async Task Schema_property_for_relationship_is_not_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -173,7 +87,7 @@ public async Task Schema_property_for_required_to_many_relationship_is_not_nulla // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("requiredToMany.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => { document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); }); diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/RequiredTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/RequiredTests.cs index 218af9b286..5688462fb9 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/RequiredTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/RequiredTests.cs @@ -18,64 +18,11 @@ public RequiredTests( testContext.UseController(); } - [Fact] - public async Task Schema_property_for_reference_type_attribute_is_not_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.referenceType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("referenceType")); - }); - } - - [Fact] - public async Task Schema_property_for_required_reference_type_attribute_is_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.requiredReferenceType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredReferenceType")); - }); - } - - [Fact] - public async Task Schema_property_for_value_type_attribute_is_not_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.valueType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("valueType")); - }); - } - - [Fact] - public async Task Schema_property_for_required_value_type_attribute_is_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.requiredValueType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredValueType")); - }); - } - - [Fact] - public async Task Schema_property_for_nullable_value_type_attribute_is_not_required() + [Theory] + [InlineData("requiredReferenceType")] + [InlineData("requiredValueType")] + [InlineData("requiredNullableValueType")] + public async Task Schema_property_for_attribute_is_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -83,13 +30,16 @@ public async Task Schema_property_for_nullable_value_type_attribute_is_not_requi // Assert document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => { - attributesObjectSchema.ShouldContainPath("properties.nullableValueType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("nullableValueType")); + attributesObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement(jsonPropertyName)); }); } - [Fact] - public async Task Schema_property_for_required_nullable_value_type_attribute_is_required() + [Theory] + [InlineData("referenceType")] + [InlineData("valueType")] + [InlineData("nullableValueType")] + public async Task Schema_property_for_attribute_is_not_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -97,13 +47,15 @@ public async Task Schema_property_for_required_nullable_value_type_attribute_is_ // Assert document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => { - attributesObjectSchema.ShouldContainPath("properties.requiredNullableValueType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNullableValueType")); + attributesObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement(jsonPropertyName)); }); } - [Fact] - public async Task Schema_property_for_to_one_relationship_is_not_required() + [Theory] + [InlineData("requiredToOne")] + [InlineData("requiredToMany")] + public async Task Schema_property_for_relationship_is_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -111,41 +63,15 @@ public async Task Schema_property_for_to_one_relationship_is_not_required() // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => { - relationshipsObjectSchema.ShouldContainPath("properties.toOne"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("toOne")); + relationshipsObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement(jsonPropertyName)); }); } - [Fact] - public async Task Schema_property_for_required_to_one_relationship_is_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => - { - relationshipsObjectSchema.ShouldContainPath("properties.requiredToOne"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredToOne")); - }); - } - - [Fact] - public async Task Schema_property_for_to_many_relationship_is_not_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => - { - relationshipsObjectSchema.ShouldContainPath("properties.toMany"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("toMany")); - }); - } - - [Fact] - public async Task Schema_property_for_required_to_many_relationship_is_required() + [Theory] + [InlineData("toOne")] + [InlineData("toMany")] + public async Task Schema_property_for_relationship_is_not_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -153,13 +79,13 @@ public async Task Schema_property_for_required_to_many_relationship_is_required( // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => { - relationshipsObjectSchema.ShouldContainPath("properties.requiredToMany"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredToMany")); + relationshipsObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement(jsonPropertyName)); }); } [Fact] - public async Task No_schema_properties_for_attributes_are_required_when_updating_resource() + public async Task No_attribute_schema_properties_are_required_when_updating_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -169,7 +95,7 @@ public async Task No_schema_properties_for_attributes_are_required_when_updating } [Fact] - public async Task No_schema_properties_for_relationships_when_updating_resource() + public async Task No_relationship_schema_properties_are_required_when_updating_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs index 3e2f4d4b08..dcbcc572ab 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs @@ -18,8 +18,10 @@ public NullabilityTests(OpenApiTestContext { - schemaProperties.ShouldContainPath("referenceType").With(schemaProperty => + schemaProperties.ShouldContainPath(jsonPropertyName).With(schemaProperty => { schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); }); }); } - [Fact] - public async Task Schema_property_for_required_reference_type_attribute_is_not_nullable() + [Theory] + [InlineData("requiredReferenceType")] + [InlineData("valueType")] + [InlineData("requiredValueType")] + [InlineData("requiredNullableValueType")] + public async Task Schema_property_for_attribute_is_not_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -43,79 +49,16 @@ public async Task Schema_property_for_required_reference_type_attribute_is_not_n // Assert document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("requiredReferenceType").With(schemaProperty => + schemaProperties.ShouldContainPath(jsonPropertyName).With(schemaProperty => { schemaProperty.ShouldNotContainPath("nullable"); }); }); } - [Fact] - public async Task Schema_property_for_value_type_attribute_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("valueType").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_for_required_value_type_attribute_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("requiredValueType").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_for_nullable_value_type_attribute_is_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("nullableValueType").With(schemaProperty => - { - schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); - }); - }); - } - - [Fact] - public async Task Schema_property_for_required_nullable_value_type_attribute_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("requiredNullableValueType").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_for_to_one_relationship_is_nullable() + [Theory] + [InlineData("toOne")] + public async Task Schema_property_for_relationship_is_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -123,47 +66,18 @@ public async Task Schema_property_for_to_one_relationship_is_nullable() // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("toOne.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => { document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); }); }); } - [Fact] - public async Task Schema_property_for_required_to_one_relationship_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("requiredToOne.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } - - [Fact] - public async Task Schema_property_for_to_many_relationship_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("toMany.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } - - [Fact] - public async Task Schema_property_for_required_to_many_relationship_is_not_nullable() + [Theory] + [InlineData("requiredToOne")] + [InlineData("toMany")] + [InlineData("requiredToMany")] + public async Task Schema_property_for_relationship_is_not_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -171,7 +85,7 @@ public async Task Schema_property_for_required_to_many_relationship_is_not_nulla // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("requiredToMany.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => { document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); }); diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/RequiredTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/RequiredTests.cs index 3af1cdc0bc..cba6e2035b 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/RequiredTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/RequiredTests.cs @@ -16,8 +16,10 @@ public RequiredTests(OpenApiTestContext(); } - [Fact] - public async Task Schema_property_for_reference_type_attribute_is_not_required() + [Theory] + [InlineData("requiredReferenceType")] + [InlineData("requiredNullableValueType")] + public async Task Schema_property_for_attribute_is_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -25,13 +27,17 @@ public async Task Schema_property_for_reference_type_attribute_is_not_required() // Assert document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => { - attributesObjectSchema.ShouldContainPath("properties.referenceType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("referenceType")); + attributesObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement(jsonPropertyName)); }); } - [Fact] - public async Task Schema_property_for_required_reference_type_attribute_is_required() + [Theory] + [InlineData("referenceType")] + [InlineData("valueType")] + [InlineData("requiredValueType")] + [InlineData("nullableValueType")] + public async Task Schema_property_for_attribute_is_not_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -39,100 +45,14 @@ public async Task Schema_property_for_required_reference_type_attribute_is_requi // Assert document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => { - attributesObjectSchema.ShouldContainPath("properties.requiredReferenceType"); - - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredReferenceType")); + attributesObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement(jsonPropertyName)); }); } - [Fact] - public async Task Schema_property_for_value_type_attribute_is_not_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.valueType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("valueType")); - }); - } - - [Fact] - public async Task Schema_property_for_required_value_type_attribute_is_not_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.requiredValueType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("requiredValueType")); - }); - } - - [Fact] - public async Task Schema_property_for_nullable_value_type_attribute_is_not_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.nullableValueType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("nullableValueType")); - }); - } - - [Fact] - public async Task Schema_property_for_required_nullable_value_type_attribute_is_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.requiredNullableValueType"); - - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNullableValueType")); - }); - } - - [Fact] - public async Task Schema_property_for_to_one_relationship_is_not_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => - { - relationshipsObjectSchema.ShouldContainPath("properties.toOne"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("toOne")); - }); - } - - [Fact] - public async Task Schema_property_for_required_to_one_relationship_is_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => - { - relationshipsObjectSchema.ShouldContainPath("properties.requiredToOne"); - - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredToOne")); - }); - } - - [Fact] - public async Task Schema_property_for_to_many_relationship_is_not_required() + [Theory] + [InlineData("requiredToOne")] + public async Task Schema_property_for_relationship_is_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -140,13 +60,16 @@ public async Task Schema_property_for_to_many_relationship_is_not_required() // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => { - relationshipsObjectSchema.ShouldContainPath("properties.toMany"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("toMany")); + relationshipsObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement(jsonPropertyName)); }); } - [Fact] - public async Task Schema_property_for_required_to_many_relationship_is_not_required() + [Theory] + [InlineData("toOne")] + [InlineData("toMany")] + [InlineData("requiredToMany")] + public async Task Schema_property_for_relationship_is_not_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -154,13 +77,13 @@ public async Task Schema_property_for_required_to_many_relationship_is_not_requi // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => { - relationshipsObjectSchema.ShouldContainPath("properties.requiredToMany"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("requiredToMany")); + relationshipsObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement(jsonPropertyName)); }); } [Fact] - public async Task No_schema_properties_for_attributes_are_required_when_updating_resource() + public async Task No_attribute_schema_properties_are_required_when_updating_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -170,7 +93,7 @@ public async Task No_schema_properties_for_attributes_are_required_when_updating } [Fact] - public async Task No_schema_properties_for_relationships_when_updating_resource() + public async Task No_relationship_schema_properties_are_required_when_updating_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs index 520a9e0172..c31e4a9e42 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs @@ -21,11 +21,11 @@ public NullabilityTests( } [Theory] - [InlineData("nonNullableReferenceType")] - [InlineData("requiredNonNullableReferenceType")] - [InlineData("valueType")] - [InlineData("requiredValueType")] - public async Task Schema_property_that_describes_attribute_is_not_nullable(string jsonPropertyName) + [InlineData("nullableReferenceType")] + [InlineData("requiredNullableReferenceType")] + [InlineData("nullableValueType")] + [InlineData("requiredNullableValueType")] + public async Task Schema_property_for_attribute_is_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -35,17 +35,17 @@ public async Task Schema_property_that_describes_attribute_is_not_nullable(strin { schemaProperties.ShouldContainPath(jsonPropertyName).With(schemaProperty => { - schemaProperty.ShouldNotContainPath("nullable"); + schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); }); }); } [Theory] - [InlineData("nullableReferenceType")] - [InlineData("requiredNullableReferenceType")] - [InlineData("nullableValueType")] - [InlineData("requiredNullableValueType")] - public async Task Schema_property_that_describes_attribute_is_nullable(string jsonPropertyName) + [InlineData("nonNullableReferenceType")] + [InlineData("requiredNonNullableReferenceType")] + [InlineData("valueType")] + [InlineData("requiredValueType")] + public async Task Schema_property_for_attribute_is_not_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -55,17 +55,15 @@ public async Task Schema_property_that_describes_attribute_is_nullable(string js { schemaProperties.ShouldContainPath(jsonPropertyName).With(schemaProperty => { - schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + schemaProperty.ShouldNotContainPath("nullable"); }); }); } [Theory] - [InlineData("nonNullableToOne")] - [InlineData("requiredNonNullableToOne")] - [InlineData("toMany")] - [InlineData("requiredToMany")] - public async Task Schema_property_that_describes_relationship_is_not_nullable(string jsonPropertyName) + [InlineData("nullableToOne")] + [InlineData("requiredNullableToOne")] + public async Task Schema_property_for_relationship_is_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -75,15 +73,17 @@ public async Task Schema_property_that_describes_relationship_is_not_nullable(st { schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); }); }); } [Theory] - [InlineData("nullableToOne")] - [InlineData("requiredNullableToOne")] - public async Task Schema_property_that_describes_relationship_is_nullable(string jsonPropertyName) + [InlineData("nonNullableToOne")] + [InlineData("requiredNonNullableToOne")] + [InlineData("toMany")] + [InlineData("requiredToMany")] + public async Task Schema_property_for_relationship_is_not_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -93,7 +93,7 @@ public async Task Schema_property_that_describes_relationship_is_nullable(string { schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); }); }); } diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/RequiredTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/RequiredTests.cs index c622636ba6..0cca901d44 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/RequiredTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/RequiredTests.cs @@ -19,11 +19,11 @@ public RequiredTests( } [Theory] - [InlineData("nonNullableReferenceType")] - [InlineData("nullableReferenceType")] - [InlineData("valueType")] - [InlineData("nullableValueType")] - public async Task Schema_property_that_describes_attribute_in_create_resource_is_not_required(string jsonPropertyName) + [InlineData("requiredNonNullableReferenceType")] + [InlineData("requiredNullableReferenceType")] + [InlineData("requiredValueType")] + [InlineData("requiredNullableValueType")] + public async Task Schema_property_for_attribute_is_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -32,16 +32,16 @@ public async Task Schema_property_that_describes_attribute_in_create_resource_is document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => { attributesObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement(jsonPropertyName)); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement(jsonPropertyName)); }); } [Theory] - [InlineData("requiredNonNullableReferenceType")] - [InlineData("requiredNullableReferenceType")] - [InlineData("requiredValueType")] - [InlineData("requiredNullableValueType")] - public async Task Schema_property_that_describes_attribute_in_create_resource_is_required(string jsonPropertyName) + [InlineData("nonNullableReferenceType")] + [InlineData("nullableReferenceType")] + [InlineData("valueType")] + [InlineData("nullableValueType")] + public async Task Schema_property_for_attribute_is_not_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -50,7 +50,7 @@ public async Task Schema_property_that_describes_attribute_in_create_resource_is document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => { attributesObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement(jsonPropertyName)); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement(jsonPropertyName)); }); } @@ -58,7 +58,7 @@ public async Task Schema_property_that_describes_attribute_in_create_resource_is [InlineData("requiredNonNullableToOne")] [InlineData("requiredNullableToOne")] [InlineData("requiredToMany")] - public async Task Schema_property_that_describes_relationship_in_create_resource_is_required(string jsonPropertyName) + public async Task Schema_property_for_relationship_is_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -75,7 +75,7 @@ public async Task Schema_property_that_describes_relationship_in_create_resource [InlineData("nonNullableToOne")] [InlineData("nullableToOne")] [InlineData("toMany")] - public async Task Schema_property_that_describes_relationship_in_create_resource_is_not_required(string jsonPropertyName) + public async Task Schema_property_for_relationship_is_not_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -89,7 +89,17 @@ public async Task Schema_property_that_describes_relationship_in_create_resource } [Fact] - public async Task No_schema_properties_for_relationships_when_updating_resource() + public async Task No_attribute_schema_properties_are_required_when_updating_resource() + { + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.ShouldNotContainPath("components.schemas.resourceAttributesInPatchRequest.required"); + } + + [Fact] + public async Task No_relationship_schema_properties_are_required_when_updating_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs index 9a8cc9b69c..b374162562 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs @@ -18,8 +18,10 @@ public NullabilityTests(OpenApiTestContext { - schemaProperties.ShouldContainPath("nonNullableReferenceType").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_that_describes_required_non_nullable_reference_type_attribute_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("requiredNonNullableReferenceType").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_that_describes_nullable_reference_type_attribute_is_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("nullableReferenceType").With(schemaProperty => - { - schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); - }); - }); - } - - [Fact] - public async Task Schema_property_that_describes_required_nullable_reference_type_attribute_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("requiredNullableReferenceType").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_that_describes_value_type_attribute_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("valueType").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_that_describes_required_value_type_attribute_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("requiredValueType").With(schemaProperty => - { - schemaProperty.ShouldNotContainPath("nullable"); - }); - }); - } - - [Fact] - public async Task Schema_property_that_describes_nullable_value_type_attribute_is_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("nullableValueType").With(schemaProperty => + schemaProperties.ShouldContainPath(jsonPropertyName).With(schemaProperty => { schemaProperty.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); }); }); } - [Fact] - public async Task Schema_property_that_describes_required_nullable_value_type_attribute_is_not_nullable() + [Theory] + [InlineData("nonNullableReferenceType")] + [InlineData("requiredNonNullableReferenceType")] + [InlineData("requiredNullableReferenceType")] + [InlineData("valueType")] + [InlineData("requiredValueType")] + [InlineData("requiredNullableValueType")] + public async Task Schema_property_for_attribute_is_not_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -139,15 +51,16 @@ public async Task Schema_property_that_describes_required_nullable_value_type_at // Assert document.ShouldContainPath("components.schemas.resourceAttributesInResponse.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("requiredNullableValueType").With(schemaProperty => + schemaProperties.ShouldContainPath(jsonPropertyName).With(schemaProperty => { schemaProperty.ShouldNotContainPath("nullable"); }); }); } - [Fact] - public async Task Schema_property_that_describes_non_nullable_to_one_relationship_is_not_nullable() + [Theory] + [InlineData("nullableToOne")] + public async Task Schema_property_for_relationship_is_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -155,79 +68,20 @@ public async Task Schema_property_that_describes_non_nullable_to_one_relationshi // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("nonNullableToOne.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } - - [Fact] - public async Task Schema_property_that_describes_required_non_nullable_to_one_relationship_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("requiredNonNullableToOne.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } - - [Fact] - public async Task Schema_property_that_describes_nullable_to_one_relationship_is_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("nullableToOne.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => { document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); }); }); } - [Fact] - public async Task Schema_property_that_describes_required_nullable_to_one_relationship_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("requiredNullableToOne.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } - - [Fact] - public async Task Schema_property_that_describes_to_many_relationship_is_not_nullable() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => - { - schemaProperties.ShouldContainPath("toMany.$ref").WithSchemaReferenceId(schemaReferenceId => - { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); - }); - }); - } - - [Fact] - public async Task Schema_property_that_describes_required_to_many_relationship_is_not_nullable() + [Theory] + [InlineData("nonNullableToOne")] + [InlineData("requiredNonNullableToOne")] + [InlineData("requiredNullableToOne")] + [InlineData("toMany")] + [InlineData("requiredToMany")] + public async Task Schema_property_for_relationship_is_not_nullable(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -235,7 +89,7 @@ public async Task Schema_property_that_describes_required_to_many_relationship_i // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath("requiredToMany.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => { document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); }); diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/RequiredTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/RequiredTests.cs index 467d72684d..f0fbc13272 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/RequiredTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/RequiredTests.cs @@ -16,92 +16,12 @@ public RequiredTests(OpenApiTestContext(); } - [Fact] - public async Task Schema_property_that_describes_non_nullable_reference_type_attribute_is_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.nonNullableReferenceType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("nonNullableReferenceType")); - }); - } - - [Fact] - public async Task Schema_property_that_describes_required_non_nullable_reference_type_attribute_is_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.requiredNonNullableReferenceType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNonNullableReferenceType")); - }); - } - - [Fact] - public async Task Schema_property_that_describes_nullable_reference_type_attribute_is_not_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.nullableReferenceType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("nullableReferenceType")); - }); - } - - [Fact] - public async Task Schema_property_that_describes_required_nullable_reference_type_attribute_is_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.requiredNullableReferenceType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNullableReferenceType")); - }); - } - - [Fact] - public async Task Schema_property_that_describes_value_type_attribute_is_not_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.valueType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("valueType")); - }); - } - - [Fact] - public async Task Schema_property_that_describes_required_value_type_attribute_is_not_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => - { - attributesObjectSchema.ShouldContainPath("properties.requiredValueType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("requiredValueType")); - }); - } - - [Fact] - public async Task Schema_property_that_describes_nullable_value_type_attribute_is_not_required() + [Theory] + [InlineData("nonNullableReferenceType")] + [InlineData("requiredNonNullableReferenceType")] + [InlineData("requiredNullableReferenceType")] + [InlineData("requiredNullableValueType")] + public async Task Schema_property_for_attribute_is_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -109,13 +29,17 @@ public async Task Schema_property_that_describes_nullable_value_type_attribute_i // Assert document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => { - attributesObjectSchema.ShouldContainPath("properties.nullableValueType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("nullableValueType")); + attributesObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement(jsonPropertyName)); }); } - [Fact] - public async Task Schema_property_that_describes_required_nullable_value_type_attribute_is_required() + [Theory] + [InlineData("nullableReferenceType")] + [InlineData("valueType")] + [InlineData("requiredValueType")] + [InlineData("nullableValueType")] + public async Task Schema_property_for_attribute_is_not_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -123,13 +47,16 @@ public async Task Schema_property_that_describes_required_nullable_value_type_at // Assert document.ShouldContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => { - attributesObjectSchema.ShouldContainPath("properties.requiredNullableValueType"); - attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNullableValueType")); + attributesObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + attributesObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement(jsonPropertyName)); }); } - [Fact] - public async Task Schema_property_that_describes_non_nullable_to_one_relationship_is_required() + [Theory] + [InlineData("nonNullableToOne")] + [InlineData("requiredNonNullableToOne")] + [InlineData("requiredNullableToOne")] + public async Task Schema_property_for_relationship_is_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -137,69 +64,16 @@ public async Task Schema_property_that_describes_non_nullable_to_one_relationshi // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => { - relationshipsObjectSchema.ShouldContainPath("properties.nonNullableToOne"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("nonNullableToOne")); + relationshipsObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement(jsonPropertyName)); }); } - [Fact] - public async Task Schema_property_that_describes_required_non_nullable_to_one_relationship_is_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => - { - relationshipsObjectSchema.ShouldContainPath("properties.requiredNonNullableToOne"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNonNullableToOne")); - }); - } - - [Fact] - public async Task Schema_property_that_describes_nullable_to_one_relationship_is_not_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => - { - relationshipsObjectSchema.ShouldContainPath("properties.nullableToOne"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("nullableToOne")); - }); - } - - [Fact] - public async Task Schema_property_that_describes_required_nullable_to_one_relationship_is_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => - { - relationshipsObjectSchema.ShouldContainPath("properties.requiredNullableToOne"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithElement("requiredNullableToOne")); - }); - } - - [Fact] - public async Task Schema_property_that_describes_to_many_relationship_is_not_required() - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => - { - relationshipsObjectSchema.ShouldContainPath("properties.toMany"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("toMany")); - }); - } - - [Fact] - public async Task Schema_property_that_describes_required_to_many_relationship_is_not_required() + [Theory] + [InlineData("nullableToOne")] + [InlineData("toMany")] + [InlineData("requiredToMany")] + public async Task Schema_property_for_relationship_is_not_required_for_creating_resource(string jsonPropertyName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -207,13 +81,13 @@ public async Task Schema_property_that_describes_required_to_many_relationship_i // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => { - relationshipsObjectSchema.ShouldContainPath("properties.requiredToMany"); - relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement("requiredToMany")); + relationshipsObjectSchema.ShouldContainPath($"properties.{jsonPropertyName}"); + relationshipsObjectSchema.ShouldContainPath("required").With(propertySet => propertySet.ShouldBeArrayWithoutElement(jsonPropertyName)); }); } [Fact] - public async Task No_schema_properties_for_attributes_are_required_when_updating_resource() + public async Task No_attribute_schema_properties_are_required_when_updating_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -223,7 +97,7 @@ public async Task No_schema_properties_for_attributes_are_required_when_updating } [Fact] - public async Task No_schema_properties_for_relationships_when_updating_resource() + public async Task No_relationship_schema_properties_are_required_when_updating_resource() { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); From cd3ea05f11fa1a05ce21d4dd376c3ca0d99145ef Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Fri, 6 Jan 2023 16:23:01 +0100 Subject: [PATCH 25/26] Switch to built-in support for nullable reference schemas --- .../OpenApiSchemaExtensions.cs | 18 + .../ServiceCollectionExtensions.cs | 1 + .../JsonApiSchemaGenerator.cs | 26 +- .../NullableReferenceSchemaGenerator.cs | 104 --- .../NullableReferenceSchemaStrategy.cs | 7 - .../ResourceFieldObjectSchemaBuilder.cs | 64 +- .../ResourceObjectSchemaGenerator.cs | 10 +- .../ResourceTypeSchemaGenerator.cs | 20 +- .../LegacyClient/swagger.g.json | 879 ++++++++++++++---- .../CamelCase/swagger.g.json | 439 ++++++--- .../KebabCase/swagger.g.json | 439 ++++++--- .../PascalCase/swagger.g.json | 439 ++++++--- .../swagger.g.json | 391 +++++--- .../swagger.g.json | 451 ++++++--- .../swagger.g.json | 499 +++++++--- .../swagger.g.json | 499 +++++++--- .../LegacyOpenApiIntegration/swagger.json | 829 +++++++++++++---- .../CamelCase/CamelCaseTests.cs | 64 +- .../KebabCase/KebabCaseTests.cs | 72 +- .../PascalCase/PascalCaseTests.cs | 64 +- .../NullabilityTests.cs | 14 +- .../NullabilityTests.cs | 14 +- .../NullabilityTests.cs | 14 +- .../NullabilityTests.cs | 14 +- 24 files changed, 3908 insertions(+), 1463 deletions(-) create mode 100644 src/JsonApiDotNetCore.OpenApi/OpenApiSchemaExtensions.cs delete mode 100644 src/JsonApiDotNetCore.OpenApi/SwaggerComponents/NullableReferenceSchemaGenerator.cs delete mode 100644 src/JsonApiDotNetCore.OpenApi/SwaggerComponents/NullableReferenceSchemaStrategy.cs diff --git a/src/JsonApiDotNetCore.OpenApi/OpenApiSchemaExtensions.cs b/src/JsonApiDotNetCore.OpenApi/OpenApiSchemaExtensions.cs new file mode 100644 index 0000000000..e86c2c7423 --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/OpenApiSchemaExtensions.cs @@ -0,0 +1,18 @@ +using Microsoft.OpenApi.Models; + +namespace JsonApiDotNetCore.OpenApi; + +internal static class OpenApiSchemaExtensions +{ + public static OpenApiSchema UnwrapExtendedReferenceSchema(this OpenApiSchema source) + { + ArgumentGuard.NotNull(source); + + if (source.AllOf.Count != 1) + { + throw new InvalidOperationException($"Schema '{nameof(source)}' should not contain multiple entries in '{nameof(source.AllOf)}' "); + } + + return source.AllOf.Single(); + } +} diff --git a/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs index def25eb0dd..9a76adb7e5 100644 --- a/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs @@ -69,6 +69,7 @@ private static void AddSwaggerGenerator(IServiceScope scope, IServiceCollection SetOperationInfo(swaggerGenOptions, controllerResourceMapping, namingPolicy); SetSchemaIdSelector(swaggerGenOptions, resourceGraph, namingPolicy); swaggerGenOptions.DocumentFilter(); + swaggerGenOptions.UseAllOfToExtendReferenceSchemas(); setupSwaggerGenAction?.Invoke(swaggerGenOptions); }); diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs index 1e8efa8452..b2bf21db1b 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs @@ -36,7 +36,6 @@ internal sealed class JsonApiSchemaGenerator : ISchemaGenerator private readonly ISchemaGenerator _defaultSchemaGenerator; private readonly ResourceObjectSchemaGenerator _resourceObjectSchemaGenerator; - private readonly NullableReferenceSchemaGenerator _nullableReferenceSchemaGenerator; private readonly SchemaRepositoryAccessor _schemaRepositoryAccessor = new(); public JsonApiSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IResourceGraph resourceGraph, IJsonApiOptions options, @@ -47,7 +46,6 @@ public JsonApiSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IResourceG ArgumentGuard.NotNull(options); _defaultSchemaGenerator = defaultSchemaGenerator; - _nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(_schemaRepositoryAccessor, options.SerializerOptions.PropertyNamingPolicy); _resourceObjectSchemaGenerator = new ResourceObjectSchemaGenerator(defaultSchemaGenerator, resourceGraph, options, _schemaRepositoryAccessor, resourceFieldValidationMetadataProvider); @@ -63,7 +61,11 @@ public OpenApiSchema GenerateSchema(Type modelType, SchemaRepository schemaRepos if (schemaRepository.TryLookupByType(modelType, out OpenApiSchema jsonApiDocumentSchema)) { - return jsonApiDocumentSchema; + // For unknown reasons, Swashbuckle chooses to wrap root request bodies, but not response bodies. See + // https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/861#issuecomment-1373631712 + return memberInfo != null || parameterInfo != null + ? _defaultSchemaGenerator.GenerateSchema(modelType, schemaRepository, memberInfo, parameterInfo) + : jsonApiDocumentSchema; } if (IsJsonApiDocument(modelType)) @@ -74,6 +76,8 @@ public OpenApiSchema GenerateSchema(Type modelType, SchemaRepository schemaRepos { SetDataObjectSchemaToNullable(schema); } + + // Schema might depend on other schemas not handled by us, so should not return here. } return _defaultSchemaGenerator.GenerateSchema(modelType, schemaRepository, memberInfo, parameterInfo); @@ -98,7 +102,7 @@ private OpenApiSchema GenerateJsonApiDocumentSchema(Type documentType) OpenApiSchema referenceSchemaForDataObject = IsManyDataDocument(documentType) ? CreateArrayTypeDataSchema(referenceSchemaForResourceObject) - : referenceSchemaForResourceObject; + : CreateExtendedReferenceSchema(referenceSchemaForResourceObject); fullSchemaForDocument.Properties[JsonApiObjectPropertyName.Data] = referenceSchemaForDataObject; @@ -121,7 +125,8 @@ private void SetDataObjectSchemaToNullable(OpenApiSchema referenceSchemaForDocum { OpenApiSchema fullSchemaForDocument = _schemaRepositoryAccessor.Current.Schemas[referenceSchemaForDocument.Reference.Id]; OpenApiSchema referenceSchemaForData = fullSchemaForDocument.Properties[JsonApiObjectPropertyName.Data]; - fullSchemaForDocument.Properties[JsonApiObjectPropertyName.Data] = _nullableReferenceSchemaGenerator.GenerateSchema(referenceSchemaForData); + referenceSchemaForData.Nullable = true; + fullSchemaForDocument.Properties[JsonApiObjectPropertyName.Data] = referenceSchemaForData; } private static OpenApiSchema CreateArrayTypeDataSchema(OpenApiSchema referenceSchemaForResourceObject) @@ -132,4 +137,15 @@ private static OpenApiSchema CreateArrayTypeDataSchema(OpenApiSchema referenceSc Type = "array" }; } + + private static OpenApiSchema CreateExtendedReferenceSchema(OpenApiSchema referenceSchemaForResourceObject) + { + return new OpenApiSchema + { + AllOf = new List + { + referenceSchemaForResourceObject + } + }; + } } diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/NullableReferenceSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/NullableReferenceSchemaGenerator.cs deleted file mode 100644 index 4e09769e87..0000000000 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/NullableReferenceSchemaGenerator.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Text.Json; -using Microsoft.OpenApi.Models; - -namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; - -internal sealed class NullableReferenceSchemaGenerator -{ - private const string PascalCaseNullableSchemaReferenceId = "NullValue"; - - private readonly NullableReferenceSchemaStrategy _nullableReferenceStrategy = - Enum.Parse(NullableReferenceSchemaStrategy.Implicit.ToString()); - - private readonly ISchemaRepositoryAccessor _schemaRepositoryAccessor; - private readonly string _nullableSchemaReferenceId; - - private OpenApiSchema? _referenceSchemaForExplicitNullValue; - - public NullableReferenceSchemaGenerator(ISchemaRepositoryAccessor schemaRepositoryAccessor, JsonNamingPolicy? namingPolicy) - { - ArgumentGuard.NotNull(schemaRepositoryAccessor); - - _schemaRepositoryAccessor = schemaRepositoryAccessor; - - _nullableSchemaReferenceId = namingPolicy != null ? namingPolicy.ConvertName(PascalCaseNullableSchemaReferenceId) : PascalCaseNullableSchemaReferenceId; - } - - public OpenApiSchema GenerateSchema(OpenApiSchema referenceSchema) - { - ArgumentGuard.NotNull(referenceSchema); - - return new OpenApiSchema - { - OneOf = new List - { - referenceSchema, - _nullableReferenceStrategy == NullableReferenceSchemaStrategy.Explicit ? GetExplicitNullSchema() : GetImplicitNullSchema() - } - }; - } - - // This approach is supported in OAS starting from v3.1. See https://github.com/OAI/OpenAPI-Specification/issues/1368#issuecomment-580103688 - private static OpenApiSchema GetExplicitNullSchema() - { - return new OpenApiSchema - { - Type = "null" - }; - } - - // This approach is supported starting from OAS v3.0. See https://github.com/OAI/OpenAPI-Specification/issues/1368#issuecomment-487314681 - private OpenApiSchema GetImplicitNullSchema() - { - EnsureFullSchemaForNullValueExists(); - - return _referenceSchemaForExplicitNullValue ??= new OpenApiSchema - { - Reference = new OpenApiReference - { - Id = _nullableSchemaReferenceId, - Type = ReferenceType.Schema - } - }; - } - - private void EnsureFullSchemaForNullValueExists() - { - if (!_schemaRepositoryAccessor.Current.Schemas.ContainsKey(_nullableSchemaReferenceId)) - { - var fullSchemaForNullValue = new OpenApiSchema - { - Nullable = true, - Not = new OpenApiSchema - { - AnyOf = new List - { - new() - { - Type = "string" - }, - new() - { - Type = "number" - }, - new() - { - Type = "boolean" - }, - new() - { - Type = "object" - }, - new() - { - Type = "array" - } - }, - Items = new OpenApiSchema() - } - }; - - _schemaRepositoryAccessor.Current.AddDefinition(_nullableSchemaReferenceId, fullSchemaForNullValue); - } - } -} diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/NullableReferenceSchemaStrategy.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/NullableReferenceSchemaStrategy.cs deleted file mode 100644 index 282b4a282b..0000000000 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/NullableReferenceSchemaStrategy.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; - -internal enum NullableReferenceSchemaStrategy -{ - Implicit, - Explicit -} diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs index 678229864c..6959e17284 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs @@ -1,4 +1,4 @@ -using JsonApiDotNetCore.Configuration; +using System.Reflection; using JsonApiDotNetCore.OpenApi.JsonApiObjects; using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships; using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; @@ -28,20 +28,19 @@ internal sealed class ResourceFieldObjectSchemaBuilder private readonly SchemaGenerator _defaultSchemaGenerator; private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator; private readonly SchemaRepository _resourceSchemaRepository = new(); - private readonly NullableReferenceSchemaGenerator _nullableReferenceSchemaGenerator; private readonly IDictionary _schemasForResourceFields; private readonly ResourceFieldValidationMetadataProvider _resourceFieldValidationMetadataProvider; private readonly RelationshipTypeFactory _relationshipTypeFactory; + private readonly NullabilityInfoContext _nullabilityInfoContext = new(); public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISchemaRepositoryAccessor schemaRepositoryAccessor, - SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, IJsonApiOptions options, + SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider) { ArgumentGuard.NotNull(resourceTypeInfo); ArgumentGuard.NotNull(schemaRepositoryAccessor); ArgumentGuard.NotNull(defaultSchemaGenerator); ArgumentGuard.NotNull(resourceTypeSchemaGenerator); - ArgumentGuard.NotNull(options); ArgumentGuard.NotNull(resourceFieldValidationMetadataProvider); _resourceTypeInfo = resourceTypeInfo; @@ -50,7 +49,6 @@ public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISche _resourceTypeSchemaGenerator = resourceTypeSchemaGenerator; _resourceFieldValidationMetadataProvider = resourceFieldValidationMetadataProvider; - _nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(schemaRepositoryAccessor, options.SerializerOptions.PropertyNamingPolicy); _relationshipTypeFactory = new RelationshipTypeFactory(resourceFieldValidationMetadataProvider); _schemasForResourceFields = GetFieldSchemas(); } @@ -76,7 +74,15 @@ public void SetMembersOfAttributesObject(OpenApiSchema fullSchemaForAttributesOb if (matchingAttribute != null && matchingAttribute.Capabilities.HasFlag(requiredCapability)) { - AddAttributeSchemaToResourceObject(matchingAttribute, fullSchemaForAttributesObject, resourceFieldSchema); + bool isPrimitiveOpenApiType = resourceFieldSchema.AllOf.IsNullOrEmpty(); + + // Types like enum and and complex attributes are not primitive and handles as reference schemas. + if (!isPrimitiveOpenApiType) + { + EnsureAttributeSchemaIsExposed(resourceFieldSchema, matchingAttribute); + } + + fullSchemaForAttributesObject.Properties[matchingAttribute.PublicName] = resourceFieldSchema; resourceFieldSchema.Nullable = _resourceFieldValidationMetadataProvider.IsNullable(matchingAttribute); @@ -95,21 +101,33 @@ private static AttrCapabilities GetRequiredCapabilityForAttributes(Type resource resourceObjectOpenType == typeof(ResourceObjectInPatchRequest<>) ? AttrCapabilities.AllowChange : throw new UnreachableCodeException(); } - private void AddAttributeSchemaToResourceObject(AttrAttribute attribute, OpenApiSchema attributesObjectSchema, OpenApiSchema resourceAttributeSchema) + private void EnsureAttributeSchemaIsExposed(OpenApiSchema attributeReferenceSchema, AttrAttribute attribute) { - if (resourceAttributeSchema.Reference != null && !_schemaRepositoryAccessor.Current.TryLookupByType(attribute.Property.PropertyType, out _)) + Type nonNullableTypeInPropertyType = GetRepresentedTypeForAttributeSchema(attribute); + + if (_schemaRepositoryAccessor.Current.TryLookupByType(nonNullableTypeInPropertyType, out _)) { - ExposeSchema(resourceAttributeSchema.Reference, attribute.Property.PropertyType); + return; } - attributesObjectSchema.Properties.Add(attribute.PublicName, resourceAttributeSchema); + string schemaId = attributeReferenceSchema.UnwrapExtendedReferenceSchema().Reference.Id; + + OpenApiSchema fullSchema = _resourceSchemaRepository.Schemas[schemaId]; + _schemaRepositoryAccessor.Current.AddDefinition(schemaId, fullSchema); + _schemaRepositoryAccessor.Current.RegisterType(nonNullableTypeInPropertyType, schemaId); } - private void ExposeSchema(OpenApiReference openApiReference, Type typeRepresentedBySchema) + private Type GetRepresentedTypeForAttributeSchema(AttrAttribute attribute) { - OpenApiSchema fullSchema = _resourceSchemaRepository.Schemas[openApiReference.Id]; - _schemaRepositoryAccessor.Current.AddDefinition(openApiReference.Id, fullSchema); - _schemaRepositoryAccessor.Current.RegisterType(typeRepresentedBySchema, openApiReference.Id); + NullabilityInfo attributeNullabilityInfo = _nullabilityInfoContext.Create(attribute.Property); + + bool isNullable = attributeNullabilityInfo is { ReadState: NullabilityState.Nullable, WriteState: NullabilityState.Nullable }; + + Type nonNullableTypeInPropertyType = isNullable + ? Nullable.GetUnderlyingType(attribute.Property.PropertyType) ?? attribute.Property.PropertyType + : attribute.Property.PropertyType; + + return nonNullableTypeInPropertyType; } private bool IsFieldRequired(ResourceFieldAttribute field) @@ -164,9 +182,18 @@ private void AddRelationshipSchemaToResourceObject(RelationshipAttribute relatio { Type relationshipSchemaType = GetRelationshipSchemaType(relationship, _resourceTypeInfo.ResourceObjectOpenType); - OpenApiSchema relationshipSchema = GetReferenceSchemaForRelationship(relationshipSchemaType) ?? CreateRelationshipSchema(relationshipSchemaType); + OpenApiSchema referenceSchemaForRelationship = + GetReferenceSchemaForRelationship(relationshipSchemaType) ?? CreateRelationshipReferenceSchema(relationshipSchemaType); + + var extendedReferenceSchemaForRelationship = new OpenApiSchema + { + AllOf = new List + { + referenceSchemaForRelationship + } + }; - fullSchemaForRelationshipsObject.Properties.Add(relationship.PublicName, relationshipSchema); + fullSchemaForRelationshipsObject.Properties.Add(relationship.PublicName, extendedReferenceSchemaForRelationship); if (IsFieldRequired(relationship)) { @@ -187,7 +214,7 @@ private Type GetRelationshipSchemaType(RelationshipAttribute relationship, Type return referenceSchema; } - private OpenApiSchema CreateRelationshipSchema(Type relationshipSchemaType) + private OpenApiSchema CreateRelationshipReferenceSchema(Type relationshipSchemaType) { OpenApiSchema referenceSchema = _defaultSchemaGenerator.GenerateSchema(relationshipSchemaType, _schemaRepositoryAccessor.Current); @@ -195,8 +222,7 @@ private OpenApiSchema CreateRelationshipSchema(Type relationshipSchemaType) if (IsDataPropertyNullableInRelationshipSchemaType(relationshipSchemaType)) { - fullSchema.Properties[JsonApiObjectPropertyName.Data] = - _nullableReferenceSchemaGenerator.GenerateSchema(fullSchema.Properties[JsonApiObjectPropertyName.Data]); + fullSchema.Properties[JsonApiObjectPropertyName.Data].Nullable = true; } if (IsRelationshipInResponseType(relationshipSchemaType)) diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs index 8e061fff57..ca6cfafdab 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs @@ -31,7 +31,7 @@ public ResourceObjectSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IRe _allowClientGeneratedIds = options.AllowClientGeneratedIds; _resourceFieldObjectSchemaBuilderFactory = resourceTypeInfo => new ResourceFieldObjectSchemaBuilder(resourceTypeInfo, schemaRepositoryAccessor, - defaultSchemaGenerator, _resourceTypeSchemaGenerator, options, resourceFieldValidationMetadataProvider); + defaultSchemaGenerator, _resourceTypeSchemaGenerator, resourceFieldValidationMetadataProvider); } public OpenApiSchema GenerateSchema(Type resourceObjectType) @@ -92,7 +92,9 @@ private void SetResourceType(OpenApiSchema fullSchemaForResourceObject, Type res private void SetResourceAttributes(OpenApiSchema fullSchemaForResourceObject, ResourceFieldObjectSchemaBuilder builder) { - OpenApiSchema referenceSchemaForAttributesObject = fullSchemaForResourceObject.Properties[JsonApiObjectPropertyName.AttributesObject]; + OpenApiSchema referenceSchemaForAttributesObject = + fullSchemaForResourceObject.Properties[JsonApiObjectPropertyName.AttributesObject].UnwrapExtendedReferenceSchema(); + OpenApiSchema fullSchemaForAttributesObject = _schemaRepositoryAccessor.Current.Schemas[referenceSchemaForAttributesObject.Reference.Id]; builder.SetMembersOfAttributesObject(fullSchemaForAttributesObject); @@ -110,7 +112,9 @@ private void SetResourceAttributes(OpenApiSchema fullSchemaForResourceObject, Re private void SetResourceRelationships(OpenApiSchema fullSchemaForResourceObject, ResourceFieldObjectSchemaBuilder builder) { - OpenApiSchema referenceSchemaForRelationshipsObject = fullSchemaForResourceObject.Properties[JsonApiObjectPropertyName.RelationshipsObject]; + OpenApiSchema referenceSchemaForRelationshipsObject = + fullSchemaForResourceObject.Properties[JsonApiObjectPropertyName.RelationshipsObject].UnwrapExtendedReferenceSchema(); + OpenApiSchema fullSchemaForRelationshipsObject = _schemaRepositoryAccessor.Current.Schemas[referenceSchemaForRelationshipsObject.Reference.Id]; builder.SetMembersOfRelationshipsObject(fullSchemaForRelationshipsObject); diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeSchemaGenerator.cs index 592a8ed860..6db24901b2 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeSchemaGenerator.cs @@ -28,9 +28,9 @@ public OpenApiSchema Get(Type resourceClrType) { ArgumentGuard.NotNull(resourceClrType); - if (_resourceClrTypeSchemaCache.TryGetValue(resourceClrType, out OpenApiSchema? referenceSchema)) + if (_resourceClrTypeSchemaCache.TryGetValue(resourceClrType, out OpenApiSchema? extendedReferenceSchema)) { - return referenceSchema; + return extendedReferenceSchema; } ResourceType resourceType = _resourceGraph.GetResourceType(resourceClrType); @@ -46,7 +46,7 @@ public OpenApiSchema Get(Type resourceClrType) string schemaId = GetSchemaId(resourceType); - referenceSchema = new OpenApiSchema + var referenceSchema = new OpenApiSchema { Reference = new OpenApiReference { @@ -55,10 +55,18 @@ public OpenApiSchema Get(Type resourceClrType) } }; - _schemaRepositoryAccessor.Current.AddDefinition(referenceSchema.Reference.Id, fullSchema); - _resourceClrTypeSchemaCache.Add(resourceType.ClrType, referenceSchema); + extendedReferenceSchema = new OpenApiSchema + { + AllOf = new List + { + referenceSchema + } + }; + + _schemaRepositoryAccessor.Current.AddDefinition(schemaId, fullSchema); + _resourceClrTypeSchemaCache.Add(resourceType.ClrType, extendedReferenceSchema); - return referenceSchema; + return extendedReferenceSchema; } private string GetSchemaId(ResourceType resourceType) diff --git a/test/OpenApiClientTests/LegacyClient/swagger.g.json b/test/OpenApiClientTests/LegacyClient/swagger.g.json index 043f6394eb..f2c0237d1d 100644 --- a/test/OpenApiClientTests/LegacyClient/swagger.g.json +++ b/test/OpenApiClientTests/LegacyClient/swagger.g.json @@ -51,7 +51,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/airplane-post-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-post-request-document" + } + ] } } } @@ -149,7 +153,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/airplane-patch-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-patch-request-document" + } + ] } } } @@ -326,7 +334,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -356,7 +368,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -386,7 +402,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -444,7 +464,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/flight-attendant-post-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-post-request-document" + } + ] } } } @@ -542,7 +566,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/flight-attendant-patch-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-patch-request-document" + } + ] } } } @@ -719,7 +747,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -749,7 +781,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -779,7 +815,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -925,7 +965,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -955,7 +999,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -985,7 +1033,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -1043,7 +1095,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/flight-post-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/flight-post-request-document" + } + ] } } } @@ -1141,7 +1197,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/flight-patch-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/flight-patch-request-document" + } + ] } } } @@ -1318,7 +1378,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-request" + } + ] } } } @@ -1464,7 +1528,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + } + ] } } } @@ -1494,7 +1562,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + } + ] } } } @@ -1524,7 +1596,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + } + ] } } } @@ -1670,7 +1746,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-passenger-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-passenger-in-request" + } + ] } } } @@ -1700,7 +1780,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-passenger-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-passenger-in-request" + } + ] } } } @@ -1730,7 +1814,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-passenger-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-passenger-in-request" + } + ] } } } @@ -1876,7 +1964,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-one-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-one-flight-attendant-in-request" + } + ] } } } @@ -2007,7 +2099,11 @@ "nullable": true }, "kind": { - "$ref": "#/components/schemas/aircraft-kind" + "allOf": [ + { + "$ref": "#/components/schemas/aircraft-kind" + } + ] } }, "additionalProperties": false @@ -2027,13 +2123,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-collection-document" + } + ] } }, "additionalProperties": false @@ -2046,17 +2150,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/airplane-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/airplane-attributes-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-attributes-in-patch-request" + } + ] }, "relationships": { - "$ref": "#/components/schemas/airplane-relationships-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-relationships-in-patch-request" + } + ] } }, "additionalProperties": false @@ -2068,13 +2184,25 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/airplane-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-resource-type" + } + ] }, "attributes": { - "$ref": "#/components/schemas/airplane-attributes-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-attributes-in-post-request" + } + ] }, "relationships": { - "$ref": "#/components/schemas/airplane-relationships-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-relationships-in-post-request" + } + ] } }, "additionalProperties": false @@ -2088,24 +2216,40 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/airplane-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/airplane-attributes-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-attributes-in-response" + } + ] }, "relationships": { - "$ref": "#/components/schemas/airplane-relationships-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-relationships-in-response" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -2117,7 +2261,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/airplane-data-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-data-in-patch-request" + } + ] } }, "additionalProperties": false @@ -2129,7 +2277,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/airplane-data-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-data-in-post-request" + } + ] } }, "additionalProperties": false @@ -2142,17 +2294,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/airplane-data-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-data-in-response" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -2161,7 +2325,11 @@ "type": "object", "properties": { "flights": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } }, "additionalProperties": false @@ -2170,7 +2338,11 @@ "type": "object", "properties": { "flights": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } }, "additionalProperties": false @@ -2179,7 +2351,11 @@ "type": "object", "properties": { "flights": { - "$ref": "#/components/schemas/to-many-flight-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-response" + } + ] } }, "additionalProperties": false @@ -2284,13 +2460,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-collection-document" + } + ] } }, "additionalProperties": false @@ -2303,17 +2487,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-attendant-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/flight-attendant-attributes-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-attributes-in-patch-request" + } + ] }, "relationships": { - "$ref": "#/components/schemas/flight-attendant-relationships-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-relationships-in-patch-request" + } + ] } }, "additionalProperties": false @@ -2325,13 +2521,25 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-attendant-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-resource-type" + } + ] }, "attributes": { - "$ref": "#/components/schemas/flight-attendant-attributes-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-attributes-in-post-request" + } + ] }, "relationships": { - "$ref": "#/components/schemas/flight-attendant-relationships-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-relationships-in-post-request" + } + ] } }, "additionalProperties": false @@ -2345,24 +2553,40 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-attendant-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/flight-attendant-attributes-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-attributes-in-response" + } + ] }, "relationships": { - "$ref": "#/components/schemas/flight-attendant-relationships-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-relationships-in-response" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -2375,7 +2599,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-attendant-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-resource-type" + } + ] }, "id": { "minLength": 1, @@ -2399,13 +2627,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + } + ] } }, "additionalProperties": false @@ -2418,17 +2654,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-identifier" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-identifier" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-document" + } + ] } }, "additionalProperties": false @@ -2440,7 +2688,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-data-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-data-in-patch-request" + } + ] } }, "additionalProperties": false @@ -2452,7 +2704,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-data-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-data-in-post-request" + } + ] } }, "additionalProperties": false @@ -2465,17 +2721,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-data-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-data-in-response" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -2484,10 +2752,18 @@ "type": "object", "properties": { "scheduled-for-flights": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] }, "purser-on-flights": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } }, "additionalProperties": false @@ -2496,10 +2772,18 @@ "type": "object", "properties": { "scheduled-for-flights": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] }, "purser-on-flights": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } }, "additionalProperties": false @@ -2508,10 +2792,18 @@ "type": "object", "properties": { "scheduled-for-flights": { - "$ref": "#/components/schemas/to-many-flight-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-response" + } + ] }, "purser-on-flights": { - "$ref": "#/components/schemas/to-many-flight-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-response" + } + ] } }, "additionalProperties": false @@ -2530,17 +2822,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-data-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-data-in-response" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -2558,7 +2862,11 @@ "nullable": true }, "operated-by": { - "$ref": "#/components/schemas/airline" + "allOf": [ + { + "$ref": "#/components/schemas/airline" + } + ] } }, "additionalProperties": false @@ -2580,7 +2888,11 @@ "nullable": true }, "operated-by": { - "$ref": "#/components/schemas/airline" + "allOf": [ + { + "$ref": "#/components/schemas/airline" + } + ] }, "departs-at": { "type": "string", @@ -2616,13 +2928,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-collection-document" + } + ] } }, "additionalProperties": false @@ -2635,17 +2955,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/flight-attributes-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attributes-in-patch-request" + } + ] }, "relationships": { - "$ref": "#/components/schemas/flight-relationships-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-relationships-in-patch-request" + } + ] } }, "additionalProperties": false @@ -2657,10 +2989,18 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-resource-type" + } + ] }, "relationships": { - "$ref": "#/components/schemas/flight-relationships-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-relationships-in-post-request" + } + ] } }, "additionalProperties": false @@ -2674,24 +3014,40 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/flight-attributes-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attributes-in-response" + } + ] }, "relationships": { - "$ref": "#/components/schemas/flight-relationships-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-relationships-in-response" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -2704,7 +3060,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-resource-type" + } + ] }, "id": { "minLength": 1, @@ -2728,13 +3088,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + } + ] } }, "additionalProperties": false @@ -2746,7 +3114,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-data-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-data-in-patch-request" + } + ] } }, "additionalProperties": false @@ -2758,7 +3130,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-data-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-data-in-post-request" + } + ] } }, "additionalProperties": false @@ -2771,17 +3147,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-data-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-data-in-response" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -2790,16 +3178,32 @@ "type": "object", "properties": { "cabin-crew-members": { - "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + } + ] }, "purser": { - "$ref": "#/components/schemas/to-one-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-one-flight-attendant-in-request" + } + ] }, "backup-purser": { - "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-request" + } + ] }, "passengers": { - "$ref": "#/components/schemas/to-many-passenger-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-passenger-in-request" + } + ] } }, "additionalProperties": false @@ -2811,16 +3215,32 @@ "type": "object", "properties": { "cabin-crew-members": { - "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + } + ] }, "purser": { - "$ref": "#/components/schemas/to-one-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-one-flight-attendant-in-request" + } + ] }, "backup-purser": { - "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-request" + } + ] }, "passengers": { - "$ref": "#/components/schemas/to-many-passenger-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-passenger-in-request" + } + ] } }, "additionalProperties": false @@ -2832,16 +3252,32 @@ "type": "object", "properties": { "cabin-crew-members": { - "$ref": "#/components/schemas/to-many-flight-attendant-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-attendant-in-response" + } + ] }, "purser": { - "$ref": "#/components/schemas/to-one-flight-attendant-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-one-flight-attendant-in-response" + } + ] }, "backup-purser": { - "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-response" + } + ] }, "passengers": { - "$ref": "#/components/schemas/to-many-passenger-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-passenger-in-response" + } + ] } }, "additionalProperties": false @@ -2872,7 +3308,7 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -3010,29 +3446,6 @@ }, "additionalProperties": false }, - "null-value": { - "not": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "object" - }, - { - "type": "array" - } - ], - "items": { } - }, - "nullable": true - }, "nullable-flight-attendant-identifier-response-document": { "required": [ "data", @@ -3041,24 +3454,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/flight-attendant-identifier" - }, - { - "$ref": "#/components/schemas/null-value" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-document" + } + ] } }, "additionalProperties": false @@ -3071,24 +3490,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/flight-attendant-data-in-response" - }, - { - "$ref": "#/components/schemas/null-value" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -3100,14 +3525,12 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/flight-attendant-identifier" - }, - { - "$ref": "#/components/schemas/null-value" } - ] + ], + "nullable": true } }, "additionalProperties": false @@ -3119,21 +3542,23 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/flight-attendant-identifier" - }, - { - "$ref": "#/components/schemas/null-value" } - ] + ], + "nullable": true }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -3146,7 +3571,11 @@ "nullable": true }, "cabin-area": { - "$ref": "#/components/schemas/cabin-area" + "allOf": [ + { + "$ref": "#/components/schemas/cabin-area" + } + ] } }, "additionalProperties": false @@ -3166,13 +3595,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-collection-document" + } + ] } }, "additionalProperties": false @@ -3186,21 +3623,33 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/passenger-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/passenger-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/passenger-attributes-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/passenger-attributes-in-response" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -3213,7 +3662,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/passenger-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/passenger-resource-type" + } + ] }, "id": { "minLength": 1, @@ -3237,13 +3690,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + } + ] } }, "additionalProperties": false @@ -3282,11 +3743,15 @@ } }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -3319,11 +3784,15 @@ } }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -3356,11 +3825,15 @@ } }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -3372,7 +3845,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-identifier" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-identifier" + } + ] } }, "additionalProperties": false @@ -3384,18 +3861,26 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-identifier" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-identifier" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false } } } -} \ No newline at end of file +} diff --git a/test/OpenApiClientTests/NamingConventions/CamelCase/swagger.g.json b/test/OpenApiClientTests/NamingConventions/CamelCase/swagger.g.json index 5d2569050d..b356959a8d 100644 --- a/test/OpenApiClientTests/NamingConventions/CamelCase/swagger.g.json +++ b/test/OpenApiClientTests/NamingConventions/CamelCase/swagger.g.json @@ -51,7 +51,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/supermarketPostRequestDocument" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketPostRequestDocument" + } + ] } } } @@ -152,7 +156,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/supermarketPatchRequestDocument" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketPatchRequestDocument" + } + ] } } } @@ -335,7 +343,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableToOneStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneStaffMemberInRequest" + } + ] } } } @@ -486,7 +498,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyStaffMemberInRequest" + } + ] } } } @@ -517,7 +533,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyStaffMemberInRequest" + } + ] } } } @@ -548,7 +568,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyStaffMemberInRequest" + } + ] } } } @@ -699,7 +723,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toOneStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneStaffMemberInRequest" + } + ] } } } @@ -734,7 +762,7 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -872,29 +900,6 @@ }, "additionalProperties": false }, - "nullValue": { - "not": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "object" - }, - { - "type": "array" - } - ], - "items": { } - }, - "nullable": true - }, "nullableStaffMemberIdentifierResponseDocument": { "required": [ "data", @@ -903,24 +908,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/staffMemberIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + ] } }, "additionalProperties": false @@ -933,24 +944,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/staffMemberDataInResponse" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -962,14 +979,12 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/staffMemberIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true } }, "additionalProperties": false @@ -981,21 +996,23 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/staffMemberIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1031,13 +1048,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1051,21 +1076,33 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/staffMemberResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/staffMemberAttributesInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberAttributesInResponse" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1078,7 +1115,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/staffMemberResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberResourceType" + } + ] }, "id": { "minLength": 1, @@ -1102,13 +1143,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1121,17 +1170,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/staffMemberIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberIdentifier" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + ] } }, "additionalProperties": false @@ -1150,17 +1211,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/staffMemberDataInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberDataInResponse" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1172,7 +1245,11 @@ "type": "string" }, "kind": { - "$ref": "#/components/schemas/supermarketType" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketType" + } + ] } }, "additionalProperties": false @@ -1187,7 +1264,11 @@ "type": "string" }, "kind": { - "$ref": "#/components/schemas/supermarketType" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketType" + } + ] } }, "additionalProperties": false @@ -1202,7 +1283,11 @@ "type": "string" }, "kind": { - "$ref": "#/components/schemas/supermarketType" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketType" + } + ] } }, "additionalProperties": false @@ -1222,13 +1307,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1241,17 +1334,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/supermarketResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/supermarketAttributesInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketAttributesInPatchRequest" + } + ] }, "relationships": { - "$ref": "#/components/schemas/supermarketRelationshipsInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketRelationshipsInPatchRequest" + } + ] } }, "additionalProperties": false @@ -1263,13 +1368,25 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/supermarketResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketResourceType" + } + ] }, "attributes": { - "$ref": "#/components/schemas/supermarketAttributesInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketAttributesInPostRequest" + } + ] }, "relationships": { - "$ref": "#/components/schemas/supermarketRelationshipsInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketRelationshipsInPostRequest" + } + ] } }, "additionalProperties": false @@ -1283,24 +1400,40 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/supermarketResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/supermarketAttributesInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketAttributesInResponse" + } + ] }, "relationships": { - "$ref": "#/components/schemas/supermarketRelationshipsInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketRelationshipsInResponse" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1312,7 +1445,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/supermarketDataInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketDataInPatchRequest" + } + ] } }, "additionalProperties": false @@ -1324,7 +1461,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/supermarketDataInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketDataInPostRequest" + } + ] } }, "additionalProperties": false @@ -1337,17 +1478,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/supermarketDataInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/supermarketDataInResponse" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1356,13 +1509,25 @@ "type": "object", "properties": { "storeManager": { - "$ref": "#/components/schemas/toOneStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneStaffMemberInRequest" + } + ] }, "backupStoreManager": { - "$ref": "#/components/schemas/nullableToOneStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneStaffMemberInRequest" + } + ] }, "cashiers": { - "$ref": "#/components/schemas/toManyStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyStaffMemberInRequest" + } + ] } }, "additionalProperties": false @@ -1374,13 +1539,25 @@ "type": "object", "properties": { "storeManager": { - "$ref": "#/components/schemas/toOneStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneStaffMemberInRequest" + } + ] }, "backupStoreManager": { - "$ref": "#/components/schemas/nullableToOneStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneStaffMemberInRequest" + } + ] }, "cashiers": { - "$ref": "#/components/schemas/toManyStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyStaffMemberInRequest" + } + ] } }, "additionalProperties": false @@ -1392,13 +1569,25 @@ "type": "object", "properties": { "storeManager": { - "$ref": "#/components/schemas/toOneStaffMemberInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toOneStaffMemberInResponse" + } + ] }, "backupStoreManager": { - "$ref": "#/components/schemas/nullableToOneStaffMemberInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneStaffMemberInResponse" + } + ] }, "cashiers": { - "$ref": "#/components/schemas/toManyStaffMemberInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toManyStaffMemberInResponse" + } + ] } }, "additionalProperties": false @@ -1445,11 +1634,15 @@ } }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1461,7 +1654,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/staffMemberIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberIdentifier" + } + ] } }, "additionalProperties": false @@ -1473,18 +1670,26 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/staffMemberIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberIdentifier" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false } } } -} \ No newline at end of file +} diff --git a/test/OpenApiClientTests/NamingConventions/KebabCase/swagger.g.json b/test/OpenApiClientTests/NamingConventions/KebabCase/swagger.g.json index 9d7656c612..1e257a710f 100644 --- a/test/OpenApiClientTests/NamingConventions/KebabCase/swagger.g.json +++ b/test/OpenApiClientTests/NamingConventions/KebabCase/swagger.g.json @@ -51,7 +51,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/supermarket-post-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-post-request-document" + } + ] } } } @@ -152,7 +156,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/supermarket-patch-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-patch-request-document" + } + ] } } } @@ -335,7 +343,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullable-to-one-staff-member-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/nullable-to-one-staff-member-in-request" + } + ] } } } @@ -486,7 +498,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-staff-member-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-staff-member-in-request" + } + ] } } } @@ -517,7 +533,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-staff-member-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-staff-member-in-request" + } + ] } } } @@ -548,7 +568,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-staff-member-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-staff-member-in-request" + } + ] } } } @@ -699,7 +723,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-one-staff-member-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-one-staff-member-in-request" + } + ] } } } @@ -734,7 +762,7 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -872,29 +900,6 @@ }, "additionalProperties": false }, - "null-value": { - "not": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "object" - }, - { - "type": "array" - } - ], - "items": { } - }, - "nullable": true - }, "nullable-staff-member-identifier-response-document": { "required": [ "data", @@ -903,24 +908,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/staff-member-identifier" - }, - { - "$ref": "#/components/schemas/null-value" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-document" + } + ] } }, "additionalProperties": false @@ -933,24 +944,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/staff-member-data-in-response" - }, - { - "$ref": "#/components/schemas/null-value" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -962,14 +979,12 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/staff-member-identifier" - }, - { - "$ref": "#/components/schemas/null-value" } - ] + ], + "nullable": true } }, "additionalProperties": false @@ -981,21 +996,23 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/staff-member-identifier" - }, - { - "$ref": "#/components/schemas/null-value" } - ] + ], + "nullable": true }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1031,13 +1048,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-collection-document" + } + ] } }, "additionalProperties": false @@ -1051,21 +1076,33 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/staff-member-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/staff-member-attributes-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-attributes-in-response" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1078,7 +1115,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/staff-member-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-resource-type" + } + ] }, "id": { "minLength": 1, @@ -1102,13 +1143,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + } + ] } }, "additionalProperties": false @@ -1121,17 +1170,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/staff-member-identifier" + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-identifier" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-document" + } + ] } }, "additionalProperties": false @@ -1150,17 +1211,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/staff-member-data-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-data-in-response" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -1172,7 +1245,11 @@ "type": "string" }, "kind": { - "$ref": "#/components/schemas/supermarket-type" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-type" + } + ] } }, "additionalProperties": false @@ -1187,7 +1264,11 @@ "type": "string" }, "kind": { - "$ref": "#/components/schemas/supermarket-type" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-type" + } + ] } }, "additionalProperties": false @@ -1202,7 +1283,11 @@ "type": "string" }, "kind": { - "$ref": "#/components/schemas/supermarket-type" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-type" + } + ] } }, "additionalProperties": false @@ -1222,13 +1307,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-collection-document" + } + ] } }, "additionalProperties": false @@ -1241,17 +1334,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/supermarket-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/supermarket-attributes-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-attributes-in-patch-request" + } + ] }, "relationships": { - "$ref": "#/components/schemas/supermarket-relationships-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-relationships-in-patch-request" + } + ] } }, "additionalProperties": false @@ -1263,13 +1368,25 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/supermarket-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-resource-type" + } + ] }, "attributes": { - "$ref": "#/components/schemas/supermarket-attributes-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-attributes-in-post-request" + } + ] }, "relationships": { - "$ref": "#/components/schemas/supermarket-relationships-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-relationships-in-post-request" + } + ] } }, "additionalProperties": false @@ -1283,24 +1400,40 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/supermarket-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/supermarket-attributes-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-attributes-in-response" + } + ] }, "relationships": { - "$ref": "#/components/schemas/supermarket-relationships-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-relationships-in-response" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1312,7 +1445,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/supermarket-data-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-data-in-patch-request" + } + ] } }, "additionalProperties": false @@ -1324,7 +1461,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/supermarket-data-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-data-in-post-request" + } + ] } }, "additionalProperties": false @@ -1337,17 +1478,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/supermarket-data-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-data-in-response" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -1356,13 +1509,25 @@ "type": "object", "properties": { "store-manager": { - "$ref": "#/components/schemas/to-one-staff-member-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-one-staff-member-in-request" + } + ] }, "backup-store-manager": { - "$ref": "#/components/schemas/nullable-to-one-staff-member-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/nullable-to-one-staff-member-in-request" + } + ] }, "cashiers": { - "$ref": "#/components/schemas/to-many-staff-member-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-staff-member-in-request" + } + ] } }, "additionalProperties": false @@ -1374,13 +1539,25 @@ "type": "object", "properties": { "store-manager": { - "$ref": "#/components/schemas/to-one-staff-member-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-one-staff-member-in-request" + } + ] }, "backup-store-manager": { - "$ref": "#/components/schemas/nullable-to-one-staff-member-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/nullable-to-one-staff-member-in-request" + } + ] }, "cashiers": { - "$ref": "#/components/schemas/to-many-staff-member-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-staff-member-in-request" + } + ] } }, "additionalProperties": false @@ -1392,13 +1569,25 @@ "type": "object", "properties": { "store-manager": { - "$ref": "#/components/schemas/to-one-staff-member-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-one-staff-member-in-response" + } + ] }, "backup-store-manager": { - "$ref": "#/components/schemas/nullable-to-one-staff-member-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/nullable-to-one-staff-member-in-response" + } + ] }, "cashiers": { - "$ref": "#/components/schemas/to-many-staff-member-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-staff-member-in-response" + } + ] } }, "additionalProperties": false @@ -1445,11 +1634,15 @@ } }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1461,7 +1654,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/staff-member-identifier" + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-identifier" + } + ] } }, "additionalProperties": false @@ -1473,18 +1670,26 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/staff-member-identifier" + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-identifier" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false } } } -} \ No newline at end of file +} diff --git a/test/OpenApiClientTests/NamingConventions/PascalCase/swagger.g.json b/test/OpenApiClientTests/NamingConventions/PascalCase/swagger.g.json index ae79e8470b..f6de5bef79 100644 --- a/test/OpenApiClientTests/NamingConventions/PascalCase/swagger.g.json +++ b/test/OpenApiClientTests/NamingConventions/PascalCase/swagger.g.json @@ -51,7 +51,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/SupermarketPostRequestDocument" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketPostRequestDocument" + } + ] } } } @@ -152,7 +156,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/SupermarketPatchRequestDocument" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketPatchRequestDocument" + } + ] } } } @@ -335,7 +343,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/NullableToOneStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/NullableToOneStaffMemberInRequest" + } + ] } } } @@ -486,7 +498,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/ToManyStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/ToManyStaffMemberInRequest" + } + ] } } } @@ -517,7 +533,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/ToManyStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/ToManyStaffMemberInRequest" + } + ] } } } @@ -548,7 +568,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/ToManyStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/ToManyStaffMemberInRequest" + } + ] } } } @@ -699,7 +723,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/ToOneStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/ToOneStaffMemberInRequest" + } + ] } } } @@ -734,7 +762,7 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -872,29 +900,6 @@ }, "additionalProperties": false }, - "NullValue": { - "not": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "object" - }, - { - "type": "array" - } - ], - "items": { } - }, - "nullable": true - }, "NullableStaffMemberIdentifierResponseDocument": { "required": [ "data", @@ -903,24 +908,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/StaffMemberIdentifier" - }, - { - "$ref": "#/components/schemas/NullValue" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/JsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/JsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/LinksInResourceIdentifierDocument" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceIdentifierDocument" + } + ] } }, "additionalProperties": false @@ -933,24 +944,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/StaffMemberDataInResponse" - }, - { - "$ref": "#/components/schemas/NullValue" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/JsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/JsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/LinksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -962,14 +979,12 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/StaffMemberIdentifier" - }, - { - "$ref": "#/components/schemas/NullValue" } - ] + ], + "nullable": true } }, "additionalProperties": false @@ -981,21 +996,23 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/StaffMemberIdentifier" - }, - { - "$ref": "#/components/schemas/NullValue" } - ] + ], + "nullable": true }, "links": { - "$ref": "#/components/schemas/LinksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1031,13 +1048,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/JsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/JsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/LinksInResourceCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1051,21 +1076,33 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/StaffMemberResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/StaffMemberAttributesInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberAttributesInResponse" + } + ] }, "links": { - "$ref": "#/components/schemas/LinksInResourceObject" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1078,7 +1115,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/StaffMemberResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberResourceType" + } + ] }, "id": { "minLength": 1, @@ -1102,13 +1143,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/JsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/JsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/LinksInResourceIdentifierCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceIdentifierCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1121,17 +1170,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/StaffMemberIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberIdentifier" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/JsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/JsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/LinksInResourceIdentifierDocument" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceIdentifierDocument" + } + ] } }, "additionalProperties": false @@ -1150,17 +1211,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/StaffMemberDataInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberDataInResponse" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/JsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/JsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/LinksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1172,7 +1245,11 @@ "type": "string" }, "Kind": { - "$ref": "#/components/schemas/SupermarketType" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketType" + } + ] } }, "additionalProperties": false @@ -1187,7 +1264,11 @@ "type": "string" }, "Kind": { - "$ref": "#/components/schemas/SupermarketType" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketType" + } + ] } }, "additionalProperties": false @@ -1202,7 +1283,11 @@ "type": "string" }, "Kind": { - "$ref": "#/components/schemas/SupermarketType" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketType" + } + ] } }, "additionalProperties": false @@ -1222,13 +1307,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/JsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/JsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/LinksInResourceCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1241,17 +1334,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/SupermarketResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/SupermarketAttributesInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketAttributesInPatchRequest" + } + ] }, "relationships": { - "$ref": "#/components/schemas/SupermarketRelationshipsInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketRelationshipsInPatchRequest" + } + ] } }, "additionalProperties": false @@ -1263,13 +1368,25 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/SupermarketResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketResourceType" + } + ] }, "attributes": { - "$ref": "#/components/schemas/SupermarketAttributesInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketAttributesInPostRequest" + } + ] }, "relationships": { - "$ref": "#/components/schemas/SupermarketRelationshipsInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketRelationshipsInPostRequest" + } + ] } }, "additionalProperties": false @@ -1283,24 +1400,40 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/SupermarketResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/SupermarketAttributesInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketAttributesInResponse" + } + ] }, "relationships": { - "$ref": "#/components/schemas/SupermarketRelationshipsInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketRelationshipsInResponse" + } + ] }, "links": { - "$ref": "#/components/schemas/LinksInResourceObject" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1312,7 +1445,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/SupermarketDataInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketDataInPatchRequest" + } + ] } }, "additionalProperties": false @@ -1324,7 +1461,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/SupermarketDataInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketDataInPostRequest" + } + ] } }, "additionalProperties": false @@ -1337,17 +1478,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/SupermarketDataInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketDataInResponse" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/JsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/JsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/LinksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1356,13 +1509,25 @@ "type": "object", "properties": { "StoreManager": { - "$ref": "#/components/schemas/ToOneStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/ToOneStaffMemberInRequest" + } + ] }, "BackupStoreManager": { - "$ref": "#/components/schemas/NullableToOneStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/NullableToOneStaffMemberInRequest" + } + ] }, "Cashiers": { - "$ref": "#/components/schemas/ToManyStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/ToManyStaffMemberInRequest" + } + ] } }, "additionalProperties": false @@ -1374,13 +1539,25 @@ "type": "object", "properties": { "StoreManager": { - "$ref": "#/components/schemas/ToOneStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/ToOneStaffMemberInRequest" + } + ] }, "BackupStoreManager": { - "$ref": "#/components/schemas/NullableToOneStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/NullableToOneStaffMemberInRequest" + } + ] }, "Cashiers": { - "$ref": "#/components/schemas/ToManyStaffMemberInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/ToManyStaffMemberInRequest" + } + ] } }, "additionalProperties": false @@ -1392,13 +1569,25 @@ "type": "object", "properties": { "StoreManager": { - "$ref": "#/components/schemas/ToOneStaffMemberInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/ToOneStaffMemberInResponse" + } + ] }, "BackupStoreManager": { - "$ref": "#/components/schemas/NullableToOneStaffMemberInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/NullableToOneStaffMemberInResponse" + } + ] }, "Cashiers": { - "$ref": "#/components/schemas/ToManyStaffMemberInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/ToManyStaffMemberInResponse" + } + ] } }, "additionalProperties": false @@ -1445,11 +1634,15 @@ } }, "links": { - "$ref": "#/components/schemas/LinksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1461,7 +1654,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/StaffMemberIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberIdentifier" + } + ] } }, "additionalProperties": false @@ -1473,18 +1670,26 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/StaffMemberIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberIdentifier" + } + ] }, "links": { - "$ref": "#/components/schemas/LinksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/LinksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false } } } -} \ No newline at end of file +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/swagger.g.json index 572f1c7615..27d5a781d9 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/swagger.g.json +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/swagger.g.json @@ -51,7 +51,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/resourcePostRequestDocument" + "allOf": [ + { + "$ref": "#/components/schemas/resourcePostRequestDocument" + } + ] } } } @@ -152,7 +156,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/resourcePatchRequestDocument" + "allOf": [ + { + "$ref": "#/components/schemas/resourcePatchRequestDocument" + } + ] } } } @@ -335,7 +343,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -366,7 +378,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -397,7 +413,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -548,7 +568,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] } } } @@ -699,7 +723,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -730,7 +758,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -761,7 +793,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -912,7 +948,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] } } } @@ -942,13 +982,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + ] } }, "additionalProperties": false @@ -962,18 +1010,26 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/emptyResourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "links": { - "$ref": "#/components/schemas/linksInResourceObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -986,7 +1042,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/emptyResourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceResourceType" + } + ] }, "id": { "minLength": 1, @@ -1010,13 +1070,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1047,7 +1115,7 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1185,29 +1253,6 @@ }, "additionalProperties": false }, - "nullValue": { - "not": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "object" - }, - { - "type": "array" - } - ], - "items": { } - }, - "nullable": true - }, "nullableEmptyResourceIdentifierResponseDocument": { "required": [ "data", @@ -1216,24 +1261,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + ] } }, "additionalProperties": false @@ -1246,24 +1297,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceDataInResponse" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1275,14 +1332,12 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true } }, "additionalProperties": false @@ -1294,21 +1349,23 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1437,13 +1494,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1456,17 +1521,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/resourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/resourceResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/resourceAttributesInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInPatchRequest" + } + ] }, "relationships": { - "$ref": "#/components/schemas/resourceRelationshipsInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInPatchRequest" + } + ] } }, "additionalProperties": false @@ -1478,13 +1555,25 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/resourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/resourceResourceType" + } + ] }, "attributes": { - "$ref": "#/components/schemas/resourceAttributesInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInPostRequest" + } + ] }, "relationships": { - "$ref": "#/components/schemas/resourceRelationshipsInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInPostRequest" + } + ] } }, "additionalProperties": false @@ -1498,24 +1587,40 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/resourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/resourceResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/resourceAttributesInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInResponse" + } + ] }, "relationships": { - "$ref": "#/components/schemas/resourceRelationshipsInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInResponse" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1527,7 +1632,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/resourceDataInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceDataInPatchRequest" + } + ] } }, "additionalProperties": false @@ -1539,7 +1648,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/resourceDataInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceDataInPostRequest" + } + ] } }, "additionalProperties": false @@ -1552,17 +1665,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/resourceDataInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/resourceDataInResponse" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1571,16 +1696,32 @@ "type": "object", "properties": { "toOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] }, "requiredToOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] }, "toMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] }, "requiredToMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } }, "additionalProperties": false @@ -1593,16 +1734,32 @@ "type": "object", "properties": { "toOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] }, "requiredToOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] }, "toMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] }, "requiredToMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } }, "additionalProperties": false @@ -1615,16 +1772,32 @@ "type": "object", "properties": { "toOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + } + ] }, "requiredToOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + } + ] }, "toMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + } + ] }, "requiredToMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + } + ] } }, "additionalProperties": false @@ -1663,15 +1836,19 @@ } }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false } } } -} \ No newline at end of file +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/swagger.g.json index f366cc19e2..56e047eaee 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/swagger.g.json +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/swagger.g.json @@ -51,7 +51,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/resourcePostRequestDocument" + "allOf": [ + { + "$ref": "#/components/schemas/resourcePostRequestDocument" + } + ] } } } @@ -152,7 +156,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/resourcePatchRequestDocument" + "allOf": [ + { + "$ref": "#/components/schemas/resourcePatchRequestDocument" + } + ] } } } @@ -335,7 +343,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -366,7 +378,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -397,7 +413,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -548,7 +568,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] } } } @@ -699,7 +723,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -730,7 +758,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -761,7 +793,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -912,7 +948,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] } } } @@ -942,13 +982,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + ] } }, "additionalProperties": false @@ -962,18 +1010,26 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/emptyResourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "links": { - "$ref": "#/components/schemas/linksInResourceObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -986,7 +1042,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/emptyResourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceResourceType" + } + ] }, "id": { "minLength": 1, @@ -1010,13 +1070,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1029,17 +1097,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/emptyResourceIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + ] } }, "additionalProperties": false @@ -1058,17 +1138,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/emptyResourceDataInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceDataInResponse" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1093,7 +1185,7 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1231,29 +1323,6 @@ }, "additionalProperties": false }, - "nullValue": { - "not": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "object" - }, - { - "type": "array" - } - ], - "items": { } - }, - "nullable": true - }, "nullableEmptyResourceIdentifierResponseDocument": { "required": [ "data", @@ -1262,24 +1331,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + ] } }, "additionalProperties": false @@ -1292,24 +1367,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceDataInResponse" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1321,14 +1402,12 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true } }, "additionalProperties": false @@ -1340,21 +1419,23 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1475,13 +1556,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1494,17 +1583,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/resourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/resourceResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/resourceAttributesInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInPatchRequest" + } + ] }, "relationships": { - "$ref": "#/components/schemas/resourceRelationshipsInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInPatchRequest" + } + ] } }, "additionalProperties": false @@ -1516,13 +1617,25 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/resourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/resourceResourceType" + } + ] }, "attributes": { - "$ref": "#/components/schemas/resourceAttributesInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInPostRequest" + } + ] }, "relationships": { - "$ref": "#/components/schemas/resourceRelationshipsInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInPostRequest" + } + ] } }, "additionalProperties": false @@ -1536,24 +1649,40 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/resourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/resourceResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/resourceAttributesInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInResponse" + } + ] }, "relationships": { - "$ref": "#/components/schemas/resourceRelationshipsInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInResponse" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1565,7 +1694,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/resourceDataInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceDataInPatchRequest" + } + ] } }, "additionalProperties": false @@ -1577,7 +1710,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/resourceDataInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceDataInPostRequest" + } + ] } }, "additionalProperties": false @@ -1590,17 +1727,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/resourceDataInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/resourceDataInResponse" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1609,16 +1758,32 @@ "type": "object", "properties": { "toOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] }, "requiredToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] }, "toMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] }, "requiredToMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } }, "additionalProperties": false @@ -1630,16 +1795,32 @@ "type": "object", "properties": { "toOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] }, "requiredToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] }, "toMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] }, "requiredToMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } }, "additionalProperties": false @@ -1651,16 +1832,32 @@ "type": "object", "properties": { "toOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + } + ] }, "requiredToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + } + ] }, "toMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + } + ] }, "requiredToMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + } + ] } }, "additionalProperties": false @@ -1699,11 +1896,15 @@ } }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1715,7 +1916,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/emptyResourceIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + ] } }, "additionalProperties": false @@ -1727,18 +1932,26 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/emptyResourceIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false } } } -} \ No newline at end of file +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/swagger.g.json index 8619a357c8..71ebea9da7 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/swagger.g.json +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/swagger.g.json @@ -51,7 +51,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/resourcePostRequestDocument" + "allOf": [ + { + "$ref": "#/components/schemas/resourcePostRequestDocument" + } + ] } } } @@ -152,7 +156,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/resourcePatchRequestDocument" + "allOf": [ + { + "$ref": "#/components/schemas/resourcePatchRequestDocument" + } + ] } } } @@ -335,7 +343,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] } } } @@ -486,7 +498,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] } } } @@ -637,7 +653,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] } } } @@ -788,7 +808,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] } } } @@ -939,7 +963,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -970,7 +998,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -1001,7 +1033,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -1152,7 +1188,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -1183,7 +1223,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -1214,7 +1258,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -1244,13 +1292,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1264,18 +1320,26 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/emptyResourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "links": { - "$ref": "#/components/schemas/linksInResourceObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1288,7 +1352,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/emptyResourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceResourceType" + } + ] }, "id": { "minLength": 1, @@ -1312,13 +1380,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1331,17 +1407,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/emptyResourceIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + ] } }, "additionalProperties": false @@ -1360,17 +1448,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/emptyResourceDataInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceDataInResponse" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1395,7 +1495,7 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1533,29 +1633,6 @@ }, "additionalProperties": false }, - "nullValue": { - "not": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "object" - }, - { - "type": "array" - } - ], - "items": { } - }, - "nullable": true - }, "nullableEmptyResourceIdentifierResponseDocument": { "required": [ "data", @@ -1564,24 +1641,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + ] } }, "additionalProperties": false @@ -1594,24 +1677,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceDataInResponse" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1623,14 +1712,12 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true } }, "additionalProperties": false @@ -1642,21 +1729,23 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1808,13 +1897,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1827,17 +1924,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/resourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/resourceResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/resourceAttributesInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInPatchRequest" + } + ] }, "relationships": { - "$ref": "#/components/schemas/resourceRelationshipsInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInPatchRequest" + } + ] } }, "additionalProperties": false @@ -1849,13 +1958,25 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/resourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/resourceResourceType" + } + ] }, "attributes": { - "$ref": "#/components/schemas/resourceAttributesInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInPostRequest" + } + ] }, "relationships": { - "$ref": "#/components/schemas/resourceRelationshipsInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInPostRequest" + } + ] } }, "additionalProperties": false @@ -1869,24 +1990,40 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/resourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/resourceResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/resourceAttributesInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInResponse" + } + ] }, "relationships": { - "$ref": "#/components/schemas/resourceRelationshipsInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInResponse" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1898,7 +2035,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/resourceDataInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceDataInPatchRequest" + } + ] } }, "additionalProperties": false @@ -1910,7 +2051,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/resourceDataInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceDataInPostRequest" + } + ] } }, "additionalProperties": false @@ -1923,17 +2068,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/resourceDataInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/resourceDataInResponse" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1942,22 +2099,46 @@ "type": "object", "properties": { "nonNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] }, "requiredNonNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] }, "nullableToOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] }, "requiredNullableToOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] }, "toMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] }, "requiredToMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } }, "additionalProperties": false @@ -1971,22 +2152,46 @@ "type": "object", "properties": { "nonNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] }, "requiredNonNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] }, "nullableToOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] }, "requiredNullableToOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] }, "toMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] }, "requiredToMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } }, "additionalProperties": false @@ -2000,22 +2205,46 @@ "type": "object", "properties": { "nonNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + } + ] }, "requiredNonNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + } + ] }, "nullableToOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + } + ] }, "requiredNullableToOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + } + ] }, "toMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + } + ] }, "requiredToMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + } + ] } }, "additionalProperties": false @@ -2054,11 +2283,15 @@ } }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -2070,7 +2303,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/emptyResourceIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + ] } }, "additionalProperties": false @@ -2082,18 +2319,26 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/emptyResourceIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false } } } -} \ No newline at end of file +} diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/swagger.g.json index 897983660d..b9565e70db 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/swagger.g.json +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/swagger.g.json @@ -51,7 +51,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/resourcePostRequestDocument" + "allOf": [ + { + "$ref": "#/components/schemas/resourcePostRequestDocument" + } + ] } } } @@ -152,7 +156,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/resourcePatchRequestDocument" + "allOf": [ + { + "$ref": "#/components/schemas/resourcePatchRequestDocument" + } + ] } } } @@ -335,7 +343,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] } } } @@ -486,7 +498,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] } } } @@ -637,7 +653,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] } } } @@ -788,7 +808,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] } } } @@ -939,7 +963,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -970,7 +998,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -1001,7 +1033,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -1152,7 +1188,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -1183,7 +1223,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -1214,7 +1258,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } } } @@ -1244,13 +1292,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1264,18 +1320,26 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/emptyResourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "links": { - "$ref": "#/components/schemas/linksInResourceObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1288,7 +1352,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/emptyResourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceResourceType" + } + ] }, "id": { "minLength": 1, @@ -1312,13 +1380,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1331,17 +1407,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/emptyResourceIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + ] } }, "additionalProperties": false @@ -1360,17 +1448,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/emptyResourceDataInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceDataInResponse" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1395,7 +1495,7 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1533,29 +1633,6 @@ }, "additionalProperties": false }, - "nullValue": { - "not": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "object" - }, - { - "type": "array" - } - ], - "items": { } - }, - "nullable": true - }, "nullableEmptyResourceIdentifierResponseDocument": { "required": [ "data", @@ -1564,24 +1641,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" + } + ] } }, "additionalProperties": false @@ -1594,24 +1677,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceDataInResponse" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1623,14 +1712,12 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true } }, "additionalProperties": false @@ -1642,21 +1729,23 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/emptyResourceIdentifier" - }, - { - "$ref": "#/components/schemas/nullValue" } - ] + ], + "nullable": true }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1802,13 +1891,21 @@ }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceCollectionDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + ] } }, "additionalProperties": false @@ -1821,17 +1918,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/resourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/resourceResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/resourceAttributesInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInPatchRequest" + } + ] }, "relationships": { - "$ref": "#/components/schemas/resourceRelationshipsInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInPatchRequest" + } + ] } }, "additionalProperties": false @@ -1843,13 +1952,25 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/resourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/resourceResourceType" + } + ] }, "attributes": { - "$ref": "#/components/schemas/resourceAttributesInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInPostRequest" + } + ] }, "relationships": { - "$ref": "#/components/schemas/resourceRelationshipsInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInPostRequest" + } + ] } }, "additionalProperties": false @@ -1863,24 +1984,40 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/resourceResourceType" + "allOf": [ + { + "$ref": "#/components/schemas/resourceResourceType" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/resourceAttributesInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInResponse" + } + ] }, "relationships": { - "$ref": "#/components/schemas/resourceRelationshipsInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInResponse" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -1892,7 +2029,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/resourceDataInPatchRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceDataInPatchRequest" + } + ] } }, "additionalProperties": false @@ -1904,7 +2045,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/resourceDataInPostRequest" + "allOf": [ + { + "$ref": "#/components/schemas/resourceDataInPostRequest" + } + ] } }, "additionalProperties": false @@ -1917,17 +2062,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/resourceDataInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/resourceDataInResponse" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapiObject" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapiObject" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInResourceDocument" + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] } }, "additionalProperties": false @@ -1936,22 +2093,46 @@ "type": "object", "properties": { "nonNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] }, "requiredNonNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] }, "nullableToOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] }, "requiredNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] }, "toMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] }, "requiredToMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } }, "additionalProperties": false @@ -1965,22 +2146,46 @@ "type": "object", "properties": { "nonNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] }, "requiredNonNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] }, "nullableToOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest" + } + ] }, "requiredNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInRequest" + } + ] }, "toMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] }, "requiredToMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInRequest" + } + ] } }, "additionalProperties": false @@ -1994,22 +2199,46 @@ "type": "object", "properties": { "nonNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + } + ] }, "requiredNonNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + } + ] }, "nullableToOne": { - "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse" + } + ] }, "requiredNullableToOne": { - "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toOneEmptyResourceInResponse" + } + ] }, "toMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + } + ] }, "requiredToMany": { - "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + "allOf": [ + { + "$ref": "#/components/schemas/toManyEmptyResourceInResponse" + } + ] } }, "additionalProperties": false @@ -2048,11 +2277,15 @@ } }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false @@ -2064,7 +2297,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/emptyResourceIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + ] } }, "additionalProperties": false @@ -2076,18 +2313,26 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/emptyResourceIdentifier" + "allOf": [ + { + "$ref": "#/components/schemas/emptyResourceIdentifier" + } + ] }, "links": { - "$ref": "#/components/schemas/linksInRelationshipObject" + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationshipObject" + } + ] }, "meta": { "type": "object", - "additionalProperties": { } + "additionalProperties": {} } }, "additionalProperties": false } } } -} \ No newline at end of file +} diff --git a/test/OpenApiTests/LegacyOpenApiIntegration/swagger.json b/test/OpenApiTests/LegacyOpenApiIntegration/swagger.json index f8c1dcfcb1..f2c0237d1d 100644 --- a/test/OpenApiTests/LegacyOpenApiIntegration/swagger.json +++ b/test/OpenApiTests/LegacyOpenApiIntegration/swagger.json @@ -51,7 +51,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/airplane-post-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-post-request-document" + } + ] } } } @@ -149,7 +153,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/airplane-patch-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-patch-request-document" + } + ] } } } @@ -326,7 +334,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -356,7 +368,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -386,7 +402,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -444,7 +464,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/flight-attendant-post-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-post-request-document" + } + ] } } } @@ -542,7 +566,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/flight-attendant-patch-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-patch-request-document" + } + ] } } } @@ -719,7 +747,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -749,7 +781,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -779,7 +815,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -925,7 +965,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -955,7 +999,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -985,7 +1033,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } } } @@ -1043,7 +1095,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/flight-post-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/flight-post-request-document" + } + ] } } } @@ -1141,7 +1197,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/flight-patch-request-document" + "allOf": [ + { + "$ref": "#/components/schemas/flight-patch-request-document" + } + ] } } } @@ -1318,7 +1378,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-request" + } + ] } } } @@ -1464,7 +1528,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + } + ] } } } @@ -1494,7 +1562,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + } + ] } } } @@ -1524,7 +1596,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + } + ] } } } @@ -1670,7 +1746,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-passenger-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-passenger-in-request" + } + ] } } } @@ -1700,7 +1780,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-passenger-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-passenger-in-request" + } + ] } } } @@ -1730,7 +1814,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-many-passenger-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-passenger-in-request" + } + ] } } } @@ -1876,7 +1964,11 @@ "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/to-one-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-one-flight-attendant-in-request" + } + ] } } } @@ -2007,7 +2099,11 @@ "nullable": true }, "kind": { - "$ref": "#/components/schemas/aircraft-kind" + "allOf": [ + { + "$ref": "#/components/schemas/aircraft-kind" + } + ] } }, "additionalProperties": false @@ -2030,10 +2126,18 @@ "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-collection-document" + } + ] } }, "additionalProperties": false @@ -2046,17 +2150,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/airplane-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/airplane-attributes-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-attributes-in-patch-request" + } + ] }, "relationships": { - "$ref": "#/components/schemas/airplane-relationships-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-relationships-in-patch-request" + } + ] } }, "additionalProperties": false @@ -2068,13 +2184,25 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/airplane-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-resource-type" + } + ] }, "attributes": { - "$ref": "#/components/schemas/airplane-attributes-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-attributes-in-post-request" + } + ] }, "relationships": { - "$ref": "#/components/schemas/airplane-relationships-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-relationships-in-post-request" + } + ] } }, "additionalProperties": false @@ -2088,20 +2216,36 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/airplane-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/airplane-attributes-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-attributes-in-response" + } + ] }, "relationships": { - "$ref": "#/components/schemas/airplane-relationships-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-relationships-in-response" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-object" + } + ] }, "meta": { "type": "object", @@ -2117,7 +2261,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/airplane-data-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-data-in-patch-request" + } + ] } }, "additionalProperties": false @@ -2129,7 +2277,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/airplane-data-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-data-in-post-request" + } + ] } }, "additionalProperties": false @@ -2142,17 +2294,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/airplane-data-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/airplane-data-in-response" + } + ] }, "meta": { "type": "object", "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -2161,7 +2325,11 @@ "type": "object", "properties": { "flights": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } }, "additionalProperties": false @@ -2170,7 +2338,11 @@ "type": "object", "properties": { "flights": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } }, "additionalProperties": false @@ -2179,7 +2351,11 @@ "type": "object", "properties": { "flights": { - "$ref": "#/components/schemas/to-many-flight-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-response" + } + ] } }, "additionalProperties": false @@ -2287,10 +2463,18 @@ "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-collection-document" + } + ] } }, "additionalProperties": false @@ -2303,17 +2487,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-attendant-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/flight-attendant-attributes-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-attributes-in-patch-request" + } + ] }, "relationships": { - "$ref": "#/components/schemas/flight-attendant-relationships-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-relationships-in-patch-request" + } + ] } }, "additionalProperties": false @@ -2325,13 +2521,25 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-attendant-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-resource-type" + } + ] }, "attributes": { - "$ref": "#/components/schemas/flight-attendant-attributes-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-attributes-in-post-request" + } + ] }, "relationships": { - "$ref": "#/components/schemas/flight-attendant-relationships-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-relationships-in-post-request" + } + ] } }, "additionalProperties": false @@ -2345,20 +2553,36 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-attendant-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/flight-attendant-attributes-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-attributes-in-response" + } + ] }, "relationships": { - "$ref": "#/components/schemas/flight-attendant-relationships-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-relationships-in-response" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-object" + } + ] }, "meta": { "type": "object", @@ -2375,7 +2599,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-attendant-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-resource-type" + } + ] }, "id": { "minLength": 1, @@ -2402,10 +2630,18 @@ "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + } + ] } }, "additionalProperties": false @@ -2418,17 +2654,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-identifier" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-identifier" + } + ] }, "meta": { "type": "object", "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-document" + } + ] } }, "additionalProperties": false @@ -2440,7 +2688,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-data-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-data-in-patch-request" + } + ] } }, "additionalProperties": false @@ -2452,7 +2704,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-data-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-data-in-post-request" + } + ] } }, "additionalProperties": false @@ -2465,17 +2721,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-data-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-data-in-response" + } + ] }, "meta": { "type": "object", "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -2484,10 +2752,18 @@ "type": "object", "properties": { "scheduled-for-flights": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] }, "purser-on-flights": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } }, "additionalProperties": false @@ -2496,10 +2772,18 @@ "type": "object", "properties": { "scheduled-for-flights": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] }, "purser-on-flights": { - "$ref": "#/components/schemas/to-many-flight-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-request" + } + ] } }, "additionalProperties": false @@ -2508,10 +2792,18 @@ "type": "object", "properties": { "scheduled-for-flights": { - "$ref": "#/components/schemas/to-many-flight-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-response" + } + ] }, "purser-on-flights": { - "$ref": "#/components/schemas/to-many-flight-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-in-response" + } + ] } }, "additionalProperties": false @@ -2530,17 +2822,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-data-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-data-in-response" + } + ] }, "meta": { "type": "object", "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -2558,7 +2862,11 @@ "nullable": true }, "operated-by": { - "$ref": "#/components/schemas/airline" + "allOf": [ + { + "$ref": "#/components/schemas/airline" + } + ] } }, "additionalProperties": false @@ -2580,7 +2888,11 @@ "nullable": true }, "operated-by": { - "$ref": "#/components/schemas/airline" + "allOf": [ + { + "$ref": "#/components/schemas/airline" + } + ] }, "departs-at": { "type": "string", @@ -2619,10 +2931,18 @@ "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-collection-document" + } + ] } }, "additionalProperties": false @@ -2635,17 +2955,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/flight-attributes-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attributes-in-patch-request" + } + ] }, "relationships": { - "$ref": "#/components/schemas/flight-relationships-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-relationships-in-patch-request" + } + ] } }, "additionalProperties": false @@ -2657,10 +2989,18 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-resource-type" + } + ] }, "relationships": { - "$ref": "#/components/schemas/flight-relationships-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-relationships-in-post-request" + } + ] } }, "additionalProperties": false @@ -2674,20 +3014,36 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/flight-attributes-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attributes-in-response" + } + ] }, "relationships": { - "$ref": "#/components/schemas/flight-relationships-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-relationships-in-response" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-object" + } + ] }, "meta": { "type": "object", @@ -2704,7 +3060,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/flight-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/flight-resource-type" + } + ] }, "id": { "minLength": 1, @@ -2731,10 +3091,18 @@ "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + } + ] } }, "additionalProperties": false @@ -2746,7 +3114,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-data-in-patch-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-data-in-patch-request" + } + ] } }, "additionalProperties": false @@ -2758,7 +3130,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-data-in-post-request" + "allOf": [ + { + "$ref": "#/components/schemas/flight-data-in-post-request" + } + ] } }, "additionalProperties": false @@ -2771,17 +3147,29 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-data-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/flight-data-in-response" + } + ] }, "meta": { "type": "object", "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -2790,16 +3178,32 @@ "type": "object", "properties": { "cabin-crew-members": { - "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + } + ] }, "purser": { - "$ref": "#/components/schemas/to-one-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-one-flight-attendant-in-request" + } + ] }, "backup-purser": { - "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-request" + } + ] }, "passengers": { - "$ref": "#/components/schemas/to-many-passenger-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-passenger-in-request" + } + ] } }, "additionalProperties": false @@ -2811,16 +3215,32 @@ "type": "object", "properties": { "cabin-crew-members": { - "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-attendant-in-request" + } + ] }, "purser": { - "$ref": "#/components/schemas/to-one-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-one-flight-attendant-in-request" + } + ] }, "backup-purser": { - "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-request" + } + ] }, "passengers": { - "$ref": "#/components/schemas/to-many-passenger-in-request" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-passenger-in-request" + } + ] } }, "additionalProperties": false @@ -2832,16 +3252,32 @@ "type": "object", "properties": { "cabin-crew-members": { - "$ref": "#/components/schemas/to-many-flight-attendant-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-flight-attendant-in-response" + } + ] }, "purser": { - "$ref": "#/components/schemas/to-one-flight-attendant-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-one-flight-attendant-in-response" + } + ] }, "backup-purser": { - "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/nullable-to-one-flight-attendant-in-response" + } + ] }, "passengers": { - "$ref": "#/components/schemas/to-many-passenger-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/to-many-passenger-in-response" + } + ] } }, "additionalProperties": false @@ -3010,29 +3446,6 @@ }, "additionalProperties": false }, - "null-value": { - "not": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "object" - }, - { - "type": "array" - } - ], - "items": {} - }, - "nullable": true - }, "nullable-flight-attendant-identifier-response-document": { "required": [ "data", @@ -3041,24 +3454,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/flight-attendant-identifier" - }, - { - "$ref": "#/components/schemas/null-value" } - ] + ], + "nullable": true }, "meta": { "type": "object", "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-document" + } + ] } }, "additionalProperties": false @@ -3071,24 +3490,30 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/flight-attendant-data-in-response" - }, - { - "$ref": "#/components/schemas/null-value" } - ] + ], + "nullable": true }, "meta": { "type": "object", "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] } }, "additionalProperties": false @@ -3100,14 +3525,12 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/flight-attendant-identifier" - }, - { - "$ref": "#/components/schemas/null-value" } - ] + ], + "nullable": true } }, "additionalProperties": false @@ -3119,17 +3542,19 @@ "type": "object", "properties": { "data": { - "oneOf": [ + "allOf": [ { "$ref": "#/components/schemas/flight-attendant-identifier" - }, - { - "$ref": "#/components/schemas/null-value" } - ] + ], + "nullable": true }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", @@ -3146,7 +3571,11 @@ "nullable": true }, "cabin-area": { - "$ref": "#/components/schemas/cabin-area" + "allOf": [ + { + "$ref": "#/components/schemas/cabin-area" + } + ] } }, "additionalProperties": false @@ -3169,10 +3598,18 @@ "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-collection-document" + } + ] } }, "additionalProperties": false @@ -3186,17 +3623,29 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/passenger-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/passenger-resource-type" + } + ] }, "id": { "minLength": 1, "type": "string" }, "attributes": { - "$ref": "#/components/schemas/passenger-attributes-in-response" + "allOf": [ + { + "$ref": "#/components/schemas/passenger-attributes-in-response" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-object" + } + ] }, "meta": { "type": "object", @@ -3213,7 +3662,11 @@ "type": "object", "properties": { "type": { - "$ref": "#/components/schemas/passenger-resource-type" + "allOf": [ + { + "$ref": "#/components/schemas/passenger-resource-type" + } + ] }, "id": { "minLength": 1, @@ -3240,10 +3693,18 @@ "additionalProperties": {} }, "jsonapi": { - "$ref": "#/components/schemas/jsonapi-object" + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi-object" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + } + ] } }, "additionalProperties": false @@ -3282,7 +3743,11 @@ } }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", @@ -3319,7 +3784,11 @@ } }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", @@ -3356,7 +3825,11 @@ } }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", @@ -3372,7 +3845,11 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-identifier" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-identifier" + } + ] } }, "additionalProperties": false @@ -3384,10 +3861,18 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/flight-attendant-identifier" + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-identifier" + } + ] }, "links": { - "$ref": "#/components/schemas/links-in-relationship-object" + "allOf": [ + { + "$ref": "#/components/schemas/links-in-relationship-object" + } + ] }, "meta": { "type": "object", diff --git a/test/OpenApiTests/NamingConventions/CamelCase/CamelCaseTests.cs b/test/OpenApiTests/NamingConventions/CamelCase/CamelCaseTests.cs index e0bbd5bad3..33372b10d9 100644 --- a/test/OpenApiTests/NamingConventions/CamelCase/CamelCaseTests.cs +++ b/test/OpenApiTests/NamingConventions/CamelCase/CamelCaseTests.cs @@ -43,9 +43,9 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.ShouldContainPath("jsonapi.$ref").ShouldBeSchemaReferenceId("jsonapiObject"); + propertiesElement.ShouldContainPath("jsonapi.allOf[0].$ref").ShouldBeSchemaReferenceId("jsonapiObject"); - linksInResourceCollectionDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.$ref") + linksInResourceCollectionDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("linksInResourceCollectionDocument").SchemaReferenceId; resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.items.$ref").ShouldBeSchemaReferenceId("supermarketDataInResponse") @@ -69,16 +69,16 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.ShouldContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => { - linksInResourceObjectSchemaRefId = propertiesElement.ShouldContainPath("links.$ref").ShouldBeSchemaReferenceId("linksInResourceObject") + linksInResourceObjectSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref").ShouldBeSchemaReferenceId("linksInResourceObject") .SchemaReferenceId; - primaryResourceTypeSchemaRefId = propertiesElement.ShouldContainPath("type.$ref").ShouldBeSchemaReferenceId("supermarketResourceType") + primaryResourceTypeSchemaRefId = propertiesElement.ShouldContainPath("type.allOf[0].$ref").ShouldBeSchemaReferenceId("supermarketResourceType") .SchemaReferenceId; - resourceAttributesInResponseSchemaRefId = propertiesElement.ShouldContainPath("attributes.$ref") + resourceAttributesInResponseSchemaRefId = propertiesElement.ShouldContainPath("attributes.allOf[0].$ref") .ShouldBeSchemaReferenceId("supermarketAttributesInResponse").SchemaReferenceId; - resourceRelationshipInResponseSchemaRefId = propertiesElement.ShouldContainPath("relationships.$ref") + resourceRelationshipInResponseSchemaRefId = propertiesElement.ShouldContainPath("relationships.allOf[0].$ref") .ShouldBeSchemaReferenceId("supermarketRelationshipsInResponse").SchemaReferenceId; }); @@ -96,7 +96,7 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() { propertiesElement.Should().ContainProperty("nameOfCity"); propertiesElement.Should().ContainProperty("kind"); - propertiesElement.ShouldContainPath("kind.$ref").ShouldBeSchemaReferenceId("supermarketType"); + propertiesElement.ShouldContainPath("kind.allOf[0].$ref").ShouldBeSchemaReferenceId("supermarketType"); }); string? nullableToOneResourceResponseDataSchemaRefId = null; @@ -105,13 +105,13 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() { propertiesElement.Should().ContainProperty("storeManager"); - propertiesElement.ShouldContainPath("storeManager.$ref").ShouldBeSchemaReferenceId("toOneStaffMemberInResponse"); + propertiesElement.ShouldContainPath("storeManager.allOf[0].$ref").ShouldBeSchemaReferenceId("toOneStaffMemberInResponse"); - nullableToOneResourceResponseDataSchemaRefId = propertiesElement.ShouldContainPath("backupStoreManager.$ref") + nullableToOneResourceResponseDataSchemaRefId = propertiesElement.ShouldContainPath("backupStoreManager.allOf[0].$ref") .ShouldBeSchemaReferenceId("nullableToOneStaffMemberInResponse").SchemaReferenceId; propertiesElement.Should().ContainProperty("cashiers"); - propertiesElement.ShouldContainPath("cashiers.$ref").ShouldBeSchemaReferenceId("toManyStaffMemberInResponse"); + propertiesElement.ShouldContainPath("cashiers.allOf[0].$ref").ShouldBeSchemaReferenceId("toManyStaffMemberInResponse"); }); string? linksInRelationshipObjectSchemaRefId = null; @@ -119,13 +119,11 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.ShouldContainPath($"{nullableToOneResourceResponseDataSchemaRefId}.properties").With(propertiesElement => { - linksInRelationshipObjectSchemaRefId = propertiesElement.ShouldContainPath("links.$ref").ShouldBeSchemaReferenceId("linksInRelationshipObject") - .SchemaReferenceId; + linksInRelationshipObjectSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") + .ShouldBeSchemaReferenceId("linksInRelationshipObject").SchemaReferenceId; - relatedResourceIdentifierSchemaRefId = propertiesElement.ShouldContainPath("data.oneOf[0].$ref") + relatedResourceIdentifierSchemaRefId = propertiesElement.ShouldContainPath("data.allOf[0].$ref") .ShouldBeSchemaReferenceId("staffMemberIdentifier").SchemaReferenceId; - - propertiesElement.ShouldContainPath("data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); }); schemasElement.ShouldContainPath($"{linksInRelationshipObjectSchemaRefId}.properties").With(propertiesElement => @@ -138,7 +136,7 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.ShouldContainPath($"{relatedResourceIdentifierSchemaRefId}.properties").With(propertiesElement => { - relatedResourceTypeSchemaRefId = propertiesElement.ShouldContainPath("type.$ref").ShouldBeSchemaReferenceId("staffMemberResourceType") + relatedResourceTypeSchemaRefId = propertiesElement.ShouldContainPath("type.allOf[0].$ref").ShouldBeSchemaReferenceId("staffMemberResourceType") .SchemaReferenceId; }); @@ -172,8 +170,8 @@ public async Task Casing_convention_is_applied_to_GetSingle_endpoint() schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - linksInResourceDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.$ref").ShouldBeSchemaReferenceId("linksInResourceDocument") - .SchemaReferenceId; + linksInResourceDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") + .ShouldBeSchemaReferenceId("linksInResourceDocument").SchemaReferenceId; }); schemasElement.ShouldContainPath($"{linksInResourceDocumentSchemaRefId}.properties").With(propertiesElement => @@ -210,13 +208,13 @@ public async Task Casing_convention_is_applied_to_GetSecondary_endpoint_with_sin schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.$ref").ShouldBeSchemaReferenceId("staffMemberDataInResponse") + resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.allOf[0].$ref").ShouldBeSchemaReferenceId("staffMemberDataInResponse") .SchemaReferenceId; }); schemasElement.ShouldContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.ShouldContainPath("attributes.$ref").ShouldBeSchemaReferenceId("staffMemberAttributesInResponse"); + propertiesElement.ShouldContainPath("attributes.allOf[0].$ref").ShouldBeSchemaReferenceId("staffMemberAttributesInResponse"); }); }); } @@ -285,7 +283,7 @@ public async Task Casing_convention_is_applied_to_GetRelationship_endpoint_with_ schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - linksInResourceIdentifierDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.$ref") + linksInResourceIdentifierDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("linksInResourceIdentifierDocument").SchemaReferenceId; }); @@ -343,7 +341,7 @@ public async Task Casing_convention_is_applied_to_GetRelationship_endpoint_with_ schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - linksInResourceIdentifierCollectionDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.$ref") + linksInResourceIdentifierCollectionDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("linksInResourceIdentifierCollectionDocument").SchemaReferenceId; }); @@ -376,7 +374,7 @@ public async Task Casing_convention_is_applied_to_Post_endpoint() operationElement.ShouldBeString("postSupermarket"); }); - documentSchemaRefId = getElement.ShouldContainPath("requestBody.content['application/vnd.api+json'].schema.$ref") + documentSchemaRefId = getElement.ShouldContainPath("requestBody.content['application/vnd.api+json'].schema.allOf[0].$ref") .ShouldBeSchemaReferenceId("supermarketPostRequestDocument").SchemaReferenceId; }); @@ -386,7 +384,7 @@ public async Task Casing_convention_is_applied_to_Post_endpoint() schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.$ref").ShouldBeSchemaReferenceId("supermarketDataInPostRequest") + resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.allOf[0].$ref").ShouldBeSchemaReferenceId("supermarketDataInPostRequest") .SchemaReferenceId; }); @@ -394,22 +392,22 @@ public async Task Casing_convention_is_applied_to_Post_endpoint() schemasElement.ShouldContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.ShouldContainPath("attributes.$ref").ShouldBeSchemaReferenceId("supermarketAttributesInPostRequest"); + propertiesElement.ShouldContainPath("attributes.allOf[0].$ref").ShouldBeSchemaReferenceId("supermarketAttributesInPostRequest"); - resourceRelationshipInPostRequestSchemaRefId = propertiesElement.ShouldContainPath("relationships.$ref") + resourceRelationshipInPostRequestSchemaRefId = propertiesElement.ShouldContainPath("relationships.allOf[0].$ref") .ShouldBeSchemaReferenceId("supermarketRelationshipsInPostRequest").SchemaReferenceId; }); schemasElement.ShouldContainPath($"{resourceRelationshipInPostRequestSchemaRefId}.properties").With(propertiesElement => { propertiesElement.Should().ContainProperty("storeManager"); - propertiesElement.ShouldContainPath("storeManager.$ref").ShouldBeSchemaReferenceId("toOneStaffMemberInRequest"); + propertiesElement.ShouldContainPath("storeManager.allOf[0].$ref").ShouldBeSchemaReferenceId("toOneStaffMemberInRequest"); propertiesElement.Should().ContainProperty("backupStoreManager"); - propertiesElement.ShouldContainPath("backupStoreManager.$ref").ShouldBeSchemaReferenceId("nullableToOneStaffMemberInRequest"); + propertiesElement.ShouldContainPath("backupStoreManager.allOf[0].$ref").ShouldBeSchemaReferenceId("nullableToOneStaffMemberInRequest"); propertiesElement.Should().ContainProperty("cashiers"); - propertiesElement.ShouldContainPath("cashiers.$ref").ShouldBeSchemaReferenceId("toManyStaffMemberInRequest"); + propertiesElement.ShouldContainPath("cashiers.allOf[0].$ref").ShouldBeSchemaReferenceId("toManyStaffMemberInRequest"); }); }); } @@ -446,7 +444,7 @@ public async Task Casing_convention_is_applied_to_Patch_endpoint() operationElement.ShouldBeString("patchSupermarket"); }); - documentSchemaRefId = getElement.ShouldContainPath("requestBody.content['application/vnd.api+json'].schema.$ref") + documentSchemaRefId = getElement.ShouldContainPath("requestBody.content['application/vnd.api+json'].schema.allOf[0].$ref") .ShouldBeSchemaReferenceId("supermarketPatchRequestDocument").SchemaReferenceId; }); @@ -456,14 +454,14 @@ public async Task Casing_convention_is_applied_to_Patch_endpoint() schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.$ref").ShouldBeSchemaReferenceId("supermarketDataInPatchRequest") + resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.allOf[0].$ref").ShouldBeSchemaReferenceId("supermarketDataInPatchRequest") .SchemaReferenceId; }); schemasElement.ShouldContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.ShouldContainPath("attributes.$ref").ShouldBeSchemaReferenceId("supermarketAttributesInPatchRequest"); - propertiesElement.ShouldContainPath("relationships.$ref").ShouldBeSchemaReferenceId("supermarketRelationshipsInPatchRequest"); + propertiesElement.ShouldContainPath("attributes.allOf[0].$ref").ShouldBeSchemaReferenceId("supermarketAttributesInPatchRequest"); + propertiesElement.ShouldContainPath("relationships.allOf[0].$ref").ShouldBeSchemaReferenceId("supermarketRelationshipsInPatchRequest"); }); }); } diff --git a/test/OpenApiTests/NamingConventions/KebabCase/KebabCaseTests.cs b/test/OpenApiTests/NamingConventions/KebabCase/KebabCaseTests.cs index 0a532a6e3f..47af397845 100644 --- a/test/OpenApiTests/NamingConventions/KebabCase/KebabCaseTests.cs +++ b/test/OpenApiTests/NamingConventions/KebabCase/KebabCaseTests.cs @@ -43,9 +43,9 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.ShouldContainPath("jsonapi.$ref").ShouldBeSchemaReferenceId("jsonapi-object"); + propertiesElement.ShouldContainPath("jsonapi.allOf[0].$ref").ShouldBeSchemaReferenceId("jsonapi-object"); - linksInResourceCollectionDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.$ref") + linksInResourceCollectionDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("links-in-resource-collection-document").SchemaReferenceId; resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.items.$ref").ShouldBeSchemaReferenceId("supermarket-data-in-response") @@ -69,16 +69,16 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.ShouldContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => { - linksInResourceObjectSchemaRefId = propertiesElement.ShouldContainPath("links.$ref").ShouldBeSchemaReferenceId("links-in-resource-object") - .SchemaReferenceId; + linksInResourceObjectSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") + .ShouldBeSchemaReferenceId("links-in-resource-object").SchemaReferenceId; - primaryResourceTypeSchemaRefId = propertiesElement.ShouldContainPath("type.$ref").ShouldBeSchemaReferenceId("supermarket-resource-type") - .SchemaReferenceId; + primaryResourceTypeSchemaRefId = propertiesElement.ShouldContainPath("type.allOf[0].$ref") + .ShouldBeSchemaReferenceId("supermarket-resource-type").SchemaReferenceId; - resourceAttributesInResponseSchemaRefId = propertiesElement.ShouldContainPath("attributes.$ref") + resourceAttributesInResponseSchemaRefId = propertiesElement.ShouldContainPath("attributes.allOf[0].$ref") .ShouldBeSchemaReferenceId("supermarket-attributes-in-response").SchemaReferenceId; - resourceRelationshipInResponseSchemaRefId = propertiesElement.ShouldContainPath("relationships.$ref") + resourceRelationshipInResponseSchemaRefId = propertiesElement.ShouldContainPath("relationships.allOf[0].$ref") .ShouldBeSchemaReferenceId("supermarket-relationships-in-response").SchemaReferenceId; }); @@ -96,7 +96,7 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() { propertiesElement.Should().ContainProperty("name-of-city"); propertiesElement.Should().ContainProperty("kind"); - propertiesElement.ShouldContainPath("kind.$ref").ShouldBeSchemaReferenceId("supermarket-type"); + propertiesElement.ShouldContainPath("kind.allOf[0].$ref").ShouldBeSchemaReferenceId("supermarket-type"); }); string? nullableToOneResourceResponseDataSchemaRefId = null; @@ -105,13 +105,13 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() { propertiesElement.Should().ContainProperty("store-manager"); - propertiesElement.ShouldContainPath("store-manager.$ref").ShouldBeSchemaReferenceId("to-one-staff-member-in-response"); + propertiesElement.ShouldContainPath("store-manager.allOf[0].$ref").ShouldBeSchemaReferenceId("to-one-staff-member-in-response"); - nullableToOneResourceResponseDataSchemaRefId = propertiesElement.ShouldContainPath("backup-store-manager.$ref") + nullableToOneResourceResponseDataSchemaRefId = propertiesElement.ShouldContainPath("backup-store-manager.allOf[0].$ref") .ShouldBeSchemaReferenceId("nullable-to-one-staff-member-in-response").SchemaReferenceId; propertiesElement.Should().ContainProperty("cashiers"); - propertiesElement.ShouldContainPath("cashiers.$ref").ShouldBeSchemaReferenceId("to-many-staff-member-in-response"); + propertiesElement.ShouldContainPath("cashiers.allOf[0].$ref").ShouldBeSchemaReferenceId("to-many-staff-member-in-response"); }); string? linksInRelationshipObjectSchemaRefId = null; @@ -119,13 +119,11 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.ShouldContainPath($"{nullableToOneResourceResponseDataSchemaRefId}.properties").With(propertiesElement => { - linksInRelationshipObjectSchemaRefId = propertiesElement.ShouldContainPath("links.$ref") + linksInRelationshipObjectSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("links-in-relationship-object").SchemaReferenceId; - relatedResourceIdentifierSchemaRefId = propertiesElement.ShouldContainPath("data.oneOf[0].$ref") + relatedResourceIdentifierSchemaRefId = propertiesElement.ShouldContainPath("data.allOf[0].$ref") .ShouldBeSchemaReferenceId("staff-member-identifier").SchemaReferenceId; - - propertiesElement.ShouldContainPath("data.oneOf[1].$ref").ShouldBeSchemaReferenceId("null-value"); }); schemasElement.ShouldContainPath($"{linksInRelationshipObjectSchemaRefId}.properties").With(propertiesElement => @@ -138,8 +136,8 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.ShouldContainPath($"{relatedResourceIdentifierSchemaRefId}.properties").With(propertiesElement => { - relatedResourceTypeSchemaRefId = propertiesElement.ShouldContainPath("type.$ref").ShouldBeSchemaReferenceId("staff-member-resource-type") - .SchemaReferenceId; + relatedResourceTypeSchemaRefId = propertiesElement.ShouldContainPath("type.allOf[0].$ref") + .ShouldBeSchemaReferenceId("staff-member-resource-type").SchemaReferenceId; }); schemasElement.ShouldContainPath($"{relatedResourceTypeSchemaRefId}.enum[0]").ShouldBeSchemaReferenceId("staff-members"); @@ -172,8 +170,8 @@ public async Task Casing_convention_is_applied_to_GetSingle_endpoint() schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - linksInResourceDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.$ref").ShouldBeSchemaReferenceId("links-in-resource-document") - .SchemaReferenceId; + linksInResourceDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") + .ShouldBeSchemaReferenceId("links-in-resource-document").SchemaReferenceId; }); schemasElement.ShouldContainPath($"{linksInResourceDocumentSchemaRefId}.properties").With(propertiesElement => @@ -210,13 +208,13 @@ public async Task Casing_convention_is_applied_to_GetSecondary_endpoint_with_sin schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.$ref").ShouldBeSchemaReferenceId("staff-member-data-in-response") + resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.allOf[0].$ref").ShouldBeSchemaReferenceId("staff-member-data-in-response") .SchemaReferenceId; }); schemasElement.ShouldContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.ShouldContainPath("attributes.$ref").ShouldBeSchemaReferenceId("staff-member-attributes-in-response"); + propertiesElement.ShouldContainPath("attributes.allOf[0].$ref").ShouldBeSchemaReferenceId("staff-member-attributes-in-response"); }); }); } @@ -285,7 +283,7 @@ public async Task Casing_convention_is_applied_to_GetRelationship_endpoint_with_ schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - linksInResourceIdentifierDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.$ref") + linksInResourceIdentifierDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("links-in-resource-identifier-document").SchemaReferenceId; }); @@ -343,7 +341,7 @@ public async Task Casing_convention_is_applied_to_GetRelationship_endpoint_with_ schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - linksInResourceIdentifierCollectionDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.$ref") + linksInResourceIdentifierCollectionDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("links-in-resource-identifier-collection-document").SchemaReferenceId; }); @@ -376,7 +374,7 @@ public async Task Casing_convention_is_applied_to_Post_endpoint() operationElement.ShouldBeString("post-supermarket"); }); - documentSchemaRefId = getElement.ShouldContainPath("requestBody.content['application/vnd.api+json'].schema.$ref") + documentSchemaRefId = getElement.ShouldContainPath("requestBody.content['application/vnd.api+json'].schema.allOf[0].$ref") .ShouldBeSchemaReferenceId("supermarket-post-request-document").SchemaReferenceId; }); @@ -386,30 +384,30 @@ public async Task Casing_convention_is_applied_to_Post_endpoint() schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.$ref").ShouldBeSchemaReferenceId("supermarket-data-in-post-request") - .SchemaReferenceId; + resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.allOf[0].$ref") + .ShouldBeSchemaReferenceId("supermarket-data-in-post-request").SchemaReferenceId; }); string? resourceRelationshipInPostRequestSchemaRefId = null; schemasElement.ShouldContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.ShouldContainPath("attributes.$ref").ShouldBeSchemaReferenceId("supermarket-attributes-in-post-request"); + propertiesElement.ShouldContainPath("attributes.allOf[0].$ref").ShouldBeSchemaReferenceId("supermarket-attributes-in-post-request"); - resourceRelationshipInPostRequestSchemaRefId = propertiesElement.ShouldContainPath("relationships.$ref") + resourceRelationshipInPostRequestSchemaRefId = propertiesElement.ShouldContainPath("relationships.allOf[0].$ref") .ShouldBeSchemaReferenceId("supermarket-relationships-in-post-request").SchemaReferenceId; }); schemasElement.ShouldContainPath($"{resourceRelationshipInPostRequestSchemaRefId}.properties").With(propertiesElement => { propertiesElement.Should().ContainProperty("store-manager"); - propertiesElement.ShouldContainPath("store-manager.$ref").ShouldBeSchemaReferenceId("to-one-staff-member-in-request"); + propertiesElement.ShouldContainPath("store-manager.allOf[0].$ref").ShouldBeSchemaReferenceId("to-one-staff-member-in-request"); propertiesElement.Should().ContainProperty("backup-store-manager"); - propertiesElement.ShouldContainPath("backup-store-manager.$ref").ShouldBeSchemaReferenceId("nullable-to-one-staff-member-in-request"); + propertiesElement.ShouldContainPath("backup-store-manager.allOf[0].$ref").ShouldBeSchemaReferenceId("nullable-to-one-staff-member-in-request"); propertiesElement.Should().ContainProperty("cashiers"); - propertiesElement.ShouldContainPath("cashiers.$ref").ShouldBeSchemaReferenceId("to-many-staff-member-in-request"); + propertiesElement.ShouldContainPath("cashiers.allOf[0].$ref").ShouldBeSchemaReferenceId("to-many-staff-member-in-request"); }); }); } @@ -446,7 +444,7 @@ public async Task Casing_convention_is_applied_to_Patch_endpoint() operationElement.ShouldBeString("patch-supermarket"); }); - documentSchemaRefId = getElement.ShouldContainPath("requestBody.content['application/vnd.api+json'].schema.$ref") + documentSchemaRefId = getElement.ShouldContainPath("requestBody.content['application/vnd.api+json'].schema.allOf[0].$ref") .ShouldBeSchemaReferenceId("supermarket-patch-request-document").SchemaReferenceId; }); @@ -456,14 +454,14 @@ public async Task Casing_convention_is_applied_to_Patch_endpoint() schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.$ref").ShouldBeSchemaReferenceId("supermarket-data-in-patch-request") - .SchemaReferenceId; + resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.allOf[0].$ref") + .ShouldBeSchemaReferenceId("supermarket-data-in-patch-request").SchemaReferenceId; }); schemasElement.ShouldContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.ShouldContainPath("attributes.$ref").ShouldBeSchemaReferenceId("supermarket-attributes-in-patch-request"); - propertiesElement.ShouldContainPath("relationships.$ref").ShouldBeSchemaReferenceId("supermarket-relationships-in-patch-request"); + propertiesElement.ShouldContainPath("attributes.allOf[0].$ref").ShouldBeSchemaReferenceId("supermarket-attributes-in-patch-request"); + propertiesElement.ShouldContainPath("relationships.allOf[0].$ref").ShouldBeSchemaReferenceId("supermarket-relationships-in-patch-request"); }); }); } diff --git a/test/OpenApiTests/NamingConventions/PascalCase/PascalCaseTests.cs b/test/OpenApiTests/NamingConventions/PascalCase/PascalCaseTests.cs index 73868dcdad..c8cee4411b 100644 --- a/test/OpenApiTests/NamingConventions/PascalCase/PascalCaseTests.cs +++ b/test/OpenApiTests/NamingConventions/PascalCase/PascalCaseTests.cs @@ -44,9 +44,9 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.ShouldContainPath("jsonapi.$ref").ShouldBeSchemaReferenceId("JsonapiObject"); + propertiesElement.ShouldContainPath("jsonapi.allOf[0].$ref").ShouldBeSchemaReferenceId("JsonapiObject"); - linksInResourceCollectionDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.$ref") + linksInResourceCollectionDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("LinksInResourceCollectionDocument").SchemaReferenceId; resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.items.$ref").ShouldBeSchemaReferenceId("SupermarketDataInResponse") @@ -70,16 +70,16 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.ShouldContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => { - linksInResourceObjectSchemaRefId = propertiesElement.ShouldContainPath("links.$ref").ShouldBeSchemaReferenceId("LinksInResourceObject") + linksInResourceObjectSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref").ShouldBeSchemaReferenceId("LinksInResourceObject") .SchemaReferenceId; - primaryResourceTypeSchemaRefId = propertiesElement.ShouldContainPath("type.$ref").ShouldBeSchemaReferenceId("SupermarketResourceType") + primaryResourceTypeSchemaRefId = propertiesElement.ShouldContainPath("type.allOf[0].$ref").ShouldBeSchemaReferenceId("SupermarketResourceType") .SchemaReferenceId; - resourceAttributesInResponseSchemaRefId = propertiesElement.ShouldContainPath("attributes.$ref") + resourceAttributesInResponseSchemaRefId = propertiesElement.ShouldContainPath("attributes.allOf[0].$ref") .ShouldBeSchemaReferenceId("SupermarketAttributesInResponse").SchemaReferenceId; - resourceRelationshipInResponseSchemaRefId = propertiesElement.ShouldContainPath("relationships.$ref") + resourceRelationshipInResponseSchemaRefId = propertiesElement.ShouldContainPath("relationships.allOf[0].$ref") .ShouldBeSchemaReferenceId("SupermarketRelationshipsInResponse").SchemaReferenceId; }); @@ -97,7 +97,7 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() { propertiesElement.Should().ContainProperty("NameOfCity"); propertiesElement.Should().ContainProperty("Kind"); - propertiesElement.ShouldContainPath("Kind.$ref").ShouldBeSchemaReferenceId("SupermarketType"); + propertiesElement.ShouldContainPath("Kind.allOf[0].$ref").ShouldBeSchemaReferenceId("SupermarketType"); }); string? nullableToOneResourceResponseDataSchemaRefId = null; @@ -106,13 +106,13 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() { propertiesElement.Should().ContainProperty("StoreManager"); - propertiesElement.ShouldContainPath("StoreManager.$ref").ShouldBeSchemaReferenceId("ToOneStaffMemberInResponse"); + propertiesElement.ShouldContainPath("StoreManager.allOf[0].$ref").ShouldBeSchemaReferenceId("ToOneStaffMemberInResponse"); - nullableToOneResourceResponseDataSchemaRefId = propertiesElement.ShouldContainPath("BackupStoreManager.$ref") + nullableToOneResourceResponseDataSchemaRefId = propertiesElement.ShouldContainPath("BackupStoreManager.allOf[0].$ref") .ShouldBeSchemaReferenceId("NullableToOneStaffMemberInResponse").SchemaReferenceId; propertiesElement.Should().ContainProperty("Cashiers"); - propertiesElement.ShouldContainPath("Cashiers.$ref").ShouldBeSchemaReferenceId("ToManyStaffMemberInResponse"); + propertiesElement.ShouldContainPath("Cashiers.allOf[0].$ref").ShouldBeSchemaReferenceId("ToManyStaffMemberInResponse"); }); string? linksInRelationshipObjectSchemaRefId = null; @@ -120,13 +120,11 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.ShouldContainPath($"{nullableToOneResourceResponseDataSchemaRefId}.properties").With(propertiesElement => { - linksInRelationshipObjectSchemaRefId = propertiesElement.ShouldContainPath("links.$ref").ShouldBeSchemaReferenceId("LinksInRelationshipObject") - .SchemaReferenceId; + linksInRelationshipObjectSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") + .ShouldBeSchemaReferenceId("LinksInRelationshipObject").SchemaReferenceId; - relatedResourceIdentifierSchemaRefId = propertiesElement.ShouldContainPath("data.oneOf[0].$ref") + relatedResourceIdentifierSchemaRefId = propertiesElement.ShouldContainPath("data.allOf[0].$ref") .ShouldBeSchemaReferenceId("StaffMemberIdentifier").SchemaReferenceId; - - propertiesElement.ShouldContainPath("data.oneOf[1].$ref").ShouldBeSchemaReferenceId("NullValue"); }); schemasElement.ShouldContainPath($"{linksInRelationshipObjectSchemaRefId}.properties").With(propertiesElement => @@ -139,7 +137,7 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.ShouldContainPath($"{relatedResourceIdentifierSchemaRefId}.properties").With(propertiesElement => { - relatedResourceTypeSchemaRefId = propertiesElement.ShouldContainPath("type.$ref").ShouldBeSchemaReferenceId("StaffMemberResourceType") + relatedResourceTypeSchemaRefId = propertiesElement.ShouldContainPath("type.allOf[0].$ref").ShouldBeSchemaReferenceId("StaffMemberResourceType") .SchemaReferenceId; }); @@ -173,8 +171,8 @@ public async Task Casing_convention_is_applied_to_GetSingle_endpoint() schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - linksInResourceDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.$ref").ShouldBeSchemaReferenceId("LinksInResourceDocument") - .SchemaReferenceId; + linksInResourceDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") + .ShouldBeSchemaReferenceId("LinksInResourceDocument").SchemaReferenceId; }); schemasElement.ShouldContainPath($"{linksInResourceDocumentSchemaRefId}.properties").With(propertiesElement => @@ -211,13 +209,13 @@ public async Task Casing_convention_is_applied_to_GetSecondary_endpoint_with_sin schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.$ref").ShouldBeSchemaReferenceId("StaffMemberDataInResponse") + resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.allOf[0].$ref").ShouldBeSchemaReferenceId("StaffMemberDataInResponse") .SchemaReferenceId; }); schemasElement.ShouldContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.ShouldContainPath("attributes.$ref").ShouldBeSchemaReferenceId("StaffMemberAttributesInResponse"); + propertiesElement.ShouldContainPath("attributes.allOf[0].$ref").ShouldBeSchemaReferenceId("StaffMemberAttributesInResponse"); }); }); } @@ -286,7 +284,7 @@ public async Task Casing_convention_is_applied_to_GetRelationship_endpoint_with_ schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - linksInResourceIdentifierDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.$ref") + linksInResourceIdentifierDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("LinksInResourceIdentifierDocument").SchemaReferenceId; }); @@ -344,7 +342,7 @@ public async Task Casing_convention_is_applied_to_GetRelationship_endpoint_with_ schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - linksInResourceIdentifierCollectionDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.$ref") + linksInResourceIdentifierCollectionDocumentSchemaRefId = propertiesElement.ShouldContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("LinksInResourceIdentifierCollectionDocument").SchemaReferenceId; }); @@ -377,7 +375,7 @@ public async Task Casing_convention_is_applied_to_Post_endpoint() operationElement.ShouldBeString("PostSupermarket"); }); - documentSchemaRefId = getElement.ShouldContainPath("requestBody.content['application/vnd.api+json'].schema.$ref") + documentSchemaRefId = getElement.ShouldContainPath("requestBody.content['application/vnd.api+json'].schema.allOf[0].$ref") .ShouldBeSchemaReferenceId("SupermarketPostRequestDocument").SchemaReferenceId; }); @@ -387,7 +385,7 @@ public async Task Casing_convention_is_applied_to_Post_endpoint() schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.$ref").ShouldBeSchemaReferenceId("SupermarketDataInPostRequest") + resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.allOf[0].$ref").ShouldBeSchemaReferenceId("SupermarketDataInPostRequest") .SchemaReferenceId; }); @@ -395,22 +393,22 @@ public async Task Casing_convention_is_applied_to_Post_endpoint() schemasElement.ShouldContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.ShouldContainPath("attributes.$ref").ShouldBeSchemaReferenceId("SupermarketAttributesInPostRequest"); + propertiesElement.ShouldContainPath("attributes.allOf[0].$ref").ShouldBeSchemaReferenceId("SupermarketAttributesInPostRequest"); - resourceRelationshipInPostRequestSchemaRefId = propertiesElement.ShouldContainPath("relationships.$ref") + resourceRelationshipInPostRequestSchemaRefId = propertiesElement.ShouldContainPath("relationships.allOf[0].$ref") .ShouldBeSchemaReferenceId("SupermarketRelationshipsInPostRequest").SchemaReferenceId; }); schemasElement.ShouldContainPath($"{resourceRelationshipInPostRequestSchemaRefId}.properties").With(propertiesElement => { propertiesElement.Should().ContainProperty("StoreManager"); - propertiesElement.ShouldContainPath("StoreManager.$ref").ShouldBeSchemaReferenceId("ToOneStaffMemberInRequest"); + propertiesElement.ShouldContainPath("StoreManager.allOf[0].$ref").ShouldBeSchemaReferenceId("ToOneStaffMemberInRequest"); propertiesElement.Should().ContainProperty("BackupStoreManager"); - propertiesElement.ShouldContainPath("BackupStoreManager.$ref").ShouldBeSchemaReferenceId("NullableToOneStaffMemberInRequest"); + propertiesElement.ShouldContainPath("BackupStoreManager.allOf[0].$ref").ShouldBeSchemaReferenceId("NullableToOneStaffMemberInRequest"); propertiesElement.Should().ContainProperty("Cashiers"); - propertiesElement.ShouldContainPath("Cashiers.$ref").ShouldBeSchemaReferenceId("ToManyStaffMemberInRequest"); + propertiesElement.ShouldContainPath("Cashiers.allOf[0].$ref").ShouldBeSchemaReferenceId("ToManyStaffMemberInRequest"); }); }); } @@ -447,7 +445,7 @@ public async Task Casing_convention_is_applied_to_Patch_endpoint() operationElement.ShouldBeString("PatchSupermarket"); }); - documentSchemaRefId = getElement.ShouldContainPath("requestBody.content['application/vnd.api+json'].schema.$ref") + documentSchemaRefId = getElement.ShouldContainPath("requestBody.content['application/vnd.api+json'].schema.allOf[0].$ref") .ShouldBeSchemaReferenceId("SupermarketPatchRequestDocument").SchemaReferenceId; }); @@ -457,14 +455,14 @@ public async Task Casing_convention_is_applied_to_Patch_endpoint() schemasElement.ShouldContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.$ref").ShouldBeSchemaReferenceId("SupermarketDataInPatchRequest") + resourceDataSchemaRefId = propertiesElement.ShouldContainPath("data.allOf[0].$ref").ShouldBeSchemaReferenceId("SupermarketDataInPatchRequest") .SchemaReferenceId; }); schemasElement.ShouldContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.ShouldContainPath("attributes.$ref").ShouldBeSchemaReferenceId("SupermarketAttributesInPatchRequest"); - propertiesElement.ShouldContainPath("relationships.$ref").ShouldBeSchemaReferenceId("SupermarketRelationshipsInPatchRequest"); + propertiesElement.ShouldContainPath("attributes.allOf[0].$ref").ShouldBeSchemaReferenceId("SupermarketAttributesInPatchRequest"); + propertiesElement.ShouldContainPath("relationships.allOf[0].$ref").ShouldBeSchemaReferenceId("SupermarketRelationshipsInPatchRequest"); }); }); } diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs index 40c6c5558a..d0d51fb350 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs @@ -69,9 +69,12 @@ public async Task Schema_property_for_relationship_is_nullable(string jsonProper // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.allOf[0].$ref").WithSchemaReferenceId(schemaReferenceId => { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").With(relationshipDataSchema => + { + relationshipDataSchema.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); }); }); } @@ -87,9 +90,12 @@ public async Task Schema_property_for_relationship_is_not_nullable(string jsonPr // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.allOf[0].$ref").WithSchemaReferenceId(schemaReferenceId => { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").With(relationshipDataSchema => + { + relationshipDataSchema.ShouldNotContainPath("nullable"); + }); }); }); } diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs index dcbcc572ab..534a7ed8b9 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs @@ -66,9 +66,12 @@ public async Task Schema_property_for_relationship_is_nullable(string jsonProper // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.allOf[0].$ref").WithSchemaReferenceId(schemaReferenceId => { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").With(relationshipDataSchema => + { + relationshipDataSchema.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); }); }); } @@ -85,9 +88,12 @@ public async Task Schema_property_for_relationship_is_not_nullable(string jsonPr // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.allOf[0].$ref").WithSchemaReferenceId(schemaReferenceId => { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").With(relationshipDataSchema => + { + relationshipDataSchema.ShouldNotContainPath("nullable"); + }); }); }); } diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs index c31e4a9e42..23e58e54bd 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs @@ -71,9 +71,12 @@ public async Task Schema_property_for_relationship_is_nullable(string jsonProper // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.allOf[0].$ref").WithSchemaReferenceId(schemaReferenceId => { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").With(relationshipDataSchema => + { + relationshipDataSchema.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); }); }); } @@ -91,9 +94,12 @@ public async Task Schema_property_for_relationship_is_not_nullable(string jsonPr // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.allOf[0].$ref").WithSchemaReferenceId(schemaReferenceId => { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").With(relationshipDataSchema => + { + relationshipDataSchema.ShouldNotContainPath("nullable"); + }); }); }); } diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs index b374162562..a2fb76c38f 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs @@ -68,9 +68,12 @@ public async Task Schema_property_for_relationship_is_nullable(string jsonProper // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.allOf[0].$ref").WithSchemaReferenceId(schemaReferenceId => { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data.oneOf[1].$ref").ShouldBeSchemaReferenceId("nullValue"); + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").With(relationshipDataSchema => + { + relationshipDataSchema.ShouldContainPath("nullable").With(nullableProperty => nullableProperty.ValueKind.Should().Be(JsonValueKind.True)); + }); }); }); } @@ -89,9 +92,12 @@ public async Task Schema_property_for_relationship_is_not_nullable(string jsonPr // Assert document.ShouldContainPath("components.schemas.resourceRelationshipsInPostRequest.properties").With(schemaProperties => { - schemaProperties.ShouldContainPath($"{jsonPropertyName}.$ref").WithSchemaReferenceId(schemaReferenceId => + schemaProperties.ShouldContainPath($"{jsonPropertyName}.allOf[0].$ref").WithSchemaReferenceId(schemaReferenceId => { - document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").ShouldNotContainPath("oneOf[1].$ref"); + document.ShouldContainPath($"components.schemas.{schemaReferenceId}.properties.data").With(relationshipDataSchema => + { + relationshipDataSchema.ShouldNotContainPath("nullable"); + }); }); }); } From 08fb3056c8546843c97b13249f4eded2a65afdca Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Fri, 6 Jan 2023 16:46:24 +0100 Subject: [PATCH 26/26] temporarily enable CI for branch openapi-required-and-nullable-properties --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 433d391b16..bbd35ed160 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,6 +14,7 @@ environment: branches: only: + - openapi-required-and-nullable-properties # TODO: remove - master - openapi - develop