Skip to content

Support NRT and MSV in required and nullable #1185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 76 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
99dcfa8
Added tests for nullable and required properties in schema generation
maurei Sep 5, 2022
2604f84
Added handling of modelstate validation in setting required attributes
maurei Jul 5, 2022
f7194d9
Not all swagger documents need to be saved to disk; changes in OpenAp…
maurei Sep 5, 2022
449a079
Added OpenApi client tests for nullable and required properties
maurei Sep 6, 2022
c55a2cb
Use NullabilityInfoContext for nullability information
maurei Sep 6, 2022
31638b8
Merge branch 'merge-master-v503-into-openapi' into openapi-required-a…
bkoelman Sep 8, 2022
fc15912
Post-merge fixes
bkoelman Sep 8, 2022
7288b8a
Merge branch 'openapi' into openapi-required-and-nullable-properties
bkoelman Dec 15, 2022
d7c8b4b
Post-merge fixes
bkoelman Dec 15, 2022
d72e43e
Fixed: do not share NullabilityInfoContext, it is not thread-safe
bkoelman Dec 15, 2022
526d03f
Merge pull request #1229 from json-api-dotnet/merge-openapi-into-1185
bkoelman Dec 15, 2022
f8e640b
Review feedback
maurei Dec 19, 2022
7266bc6
remove redundant new lines in eof added by cleanupcode
maurei Dec 19, 2022
945a803
Improved naming in OpenApiTests/SchemaProperties
maurei Dec 19, 2022
f5bb7fc
Review feedback: NullabilityTests
maurei Dec 20, 2022
919a3f8
Improved JsonApiClient and testing
maurei Dec 20, 2022
87aea3e
Fix test: should not omit required field in test request body
maurei Dec 20, 2022
2ff06fc
Temp enable CI buid for current branch
maurei Dec 20, 2022
932367e
Rename test files: it no longer only concerns required attributes, bu…
maurei Dec 20, 2022
061f426
Changes and tests for support of nullable and required for relationships
maurei Dec 22, 2022
90970a8
- Rename placeholder model names and properties to examples consisent…
maurei Dec 24, 2022
affc187
Move into consistent folder structure, remove bad cleanupcode eof lin…
maurei Dec 24, 2022
daf25a2
Merge branch 'openapi' into openapi-required-and-nullable-properties
maurei Dec 24, 2022
d4f1d69
Merge pull request #1231 from json-api-dotnet/attributes-object-schem…
maurei Dec 27, 2022
76e098a
Organise tests such that they map directly to the tables in #1231 and…
maurei Dec 28, 2022
a2e7a96
Add two missing 'Act' comments
maurei Dec 28, 2022
1815dec
More elaborate testing
maurei Jan 3, 2023
63ba43d
Remove non-sensical testcases. Add caching in ObjectExtensions.
maurei Jan 4, 2023
3e9708a
Remove overlooked code duplication in OpenApiTests, revert reflection…
maurei Jan 5, 2023
8304c05
Make AutoFakers deterministic; generate positive IDs
bkoelman Jan 7, 2023
9672b14
Fix nameof
bkoelman Jan 7, 2023
345a689
Use On/Off naming, shorten type names by using Nrt+Msv
bkoelman Jan 7, 2023
22ad300
Renamed EmptyResource to Empty to further shorten FK names
bkoelman Jan 7, 2023
3069533
Fixed invalid EF Core mappings, resulting in logged warnings and inab…
bkoelman Jan 7, 2023
36ac8e7
Move misplaced Act comments
bkoelman Jan 7, 2023
a88bce8
Optimize and clarify ResourceFieldValidationMetadataProvider
bkoelman Jan 8, 2023
2652063
Rename method, provide error message
bkoelman Jan 8, 2023
f2ecb5f
Refactor JsonApiClient: simplified recursion by using two converters,…
bkoelman Jan 8, 2023
d337230
Add relationship nullability assertions in OpenAPI client tests
bkoelman Jan 8, 2023
52cc1f1
Cleanup JsonElementExtensions
bkoelman Jan 8, 2023
b30a680
Cleanup ObjectExtensions
bkoelman Jan 8, 2023
73c3232
Make base type abstract, remove redundant TranslateAsync calls, inlin…
bkoelman Jan 8, 2023
55e702c
Simplify usings
bkoelman Jan 9, 2023
28bb39b
Sync up test names
bkoelman Jan 9, 2023
ff4262e
Fix invalid tests
bkoelman Jan 9, 2023
821d09d
Fix assertion messages
bkoelman Jan 9, 2023
5b71d90
Sync up tests
bkoelman Jan 9, 2023
df9a1b4
Revert change to pass full options instead of just the naming policy
bkoelman Jan 9, 2023
9844e6a
Fix casing in test names
bkoelman Jan 10, 2023
6071f5b
Simplify Cannot_exclude_Id tests
bkoelman Jan 10, 2023
6881208
Rename base type to avoid OpenApiClientTests.OpenApiClientTests
bkoelman Jan 10, 2023
cc3815c
Adapt to existing naming convention
bkoelman Sep 30, 2023
4ef8214
Remove redundant assertions, fix formatting
bkoelman Sep 30, 2023
dcdf5a5
Correct test names
bkoelman Sep 30, 2023
2d7f8b9
Centralize code for property assignment in tests
bkoelman Oct 1, 2023
7364e94
Merge pull request #1244 from json-api-dotnet/openapi-nrt-msv-updates
bkoelman Oct 1, 2023
7c6f9eb
Merge branch 'openapi' into openapi-required-and-nullable-properties
bkoelman Oct 1, 2023
41d6d67
Apply Resharper hint: convert switch statement to expression
bkoelman Oct 1, 2023
bd31216
Simplify expressions
bkoelman Oct 1, 2023
6a87751
Simplify exception assertions
bkoelman Oct 1, 2023
5c85c3a
Use string interpolation
bkoelman Oct 1, 2023
b08ab9b
Corrections in openapi documentation
bkoelman Oct 1, 2023
f7786bb
Simplify code
bkoelman Oct 1, 2023
c3c4844
Remove redundant suppression
bkoelman Oct 1, 2023
66a2dc4
Merge branch 'master' into openapi-required-and-nullable-properties
bkoelman Oct 1, 2023
f46db8a
Combine OpenAPI client tests for create resource with null/default at…
bkoelman Oct 1, 2023
63bf071
Fixup OpenAPI example and docs
bkoelman Oct 1, 2023
ce8045d
Revert "Merge branch 'master' into openapi-required-and-nullable-prop…
bkoelman Oct 1, 2023
fac0471
Workaround for running OpenAPI tests on Windows
bkoelman Oct 2, 2023
dd667be
Merge branch 'openapi' into openapi-required-and-nullable-properties
bkoelman Oct 2, 2023
9194487
Address failing InspectCode
bkoelman Oct 2, 2023
85d608b
Remove redundant calls
bkoelman Oct 2, 2023
007ff30
Remove redundant tests
bkoelman Oct 2, 2023
817aeb3
Move types out of the wrong namespace
bkoelman Oct 2, 2023
fb9b12a
Merge branch 'openapi' into openapi-required-and-nullable-properties
bkoelman Oct 4, 2023
0dd49b0
Remove redundant suppressions in openapi after update to CSharpGuidel…
bkoelman Oct 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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 ####

Expand Down
36 changes: 0 additions & 36 deletions src/JsonApiDotNetCore.OpenApi/MemberInfoExtensions.cs

This file was deleted.

31 changes: 25 additions & 6 deletions src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using JsonApiDotNetCore.Resources.Annotations;
using Swashbuckle.AspNetCore.SwaggerGen;

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<RequiredAttribute>();

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -58,7 +65,7 @@ public OpenApiSchema GenerateSchema(Type type, SchemaRepository schemaRepository
{
OpenApiSchema schema = GenerateJsonApiDocumentSchema(type);

if (IsDataPropertyNullable(type))
if (IsDataPropertyNullableInDocument(type))
{
SetDataObjectSchemaToNullable(schema);
}
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -12,35 +12,46 @@ 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;
private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator;
private readonly IJsonApiOptions _options;
private readonly SchemaRepository _resourceSchemaRepository = new();
private readonly NullableReferenceSchemaGenerator _nullableReferenceSchemaGenerator;
private readonly IDictionary<string, OpenApiSchema> _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();
}

Expand Down Expand Up @@ -108,15 +119,14 @@ private bool IsFieldRequired(ResourceFieldAttribute field)
return false;
}

TypeCategory fieldTypeCategory = field.Property.GetTypeCategory();
bool hasRequiredAttribute = field.Property.HasAttribute<RequiredAttribute>();

return fieldTypeCategory switch
NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(field.Property);

return field.Property.PropertyType.IsValueType switch
{
TypeCategory.NonNullableReferenceType => true,
TypeCategory.ValueType => hasRequiredAttribute,
TypeCategory.NullableReferenceType or TypeCategory.NullableValueType => hasRequiredAttribute,
_ => throw new UnreachableCodeException()
true => hasRequiredAttribute,
false => _options.ValidateModelState ? nullabilityInfo.ReadState == NullabilityState.NotNull || hasRequiredAttribute : hasRequiredAttribute
};
}

Expand Down Expand Up @@ -194,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]);
Expand All @@ -212,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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 0 additions & 9 deletions src/JsonApiDotNetCore.OpenApi/TypeCategory.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#pragma warning disable AV1008 // Class should not be static

namespace OpenApiClientTests.LegacyClient;
namespace OpenApiClientTests;

internal static class ApiResponse
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Text;
using JsonApiDotNetCore.OpenApi.Client;

namespace OpenApiClientTests.LegacyClient;
namespace OpenApiClientTests;

/// <summary>
/// Enables to inject an outgoing response body and inspect the incoming request.
Expand Down
18 changes: 14 additions & 4 deletions test/OpenApiClientTests/OpenApiClientTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(AspNetVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NSwag.ApiDescription.Client" Version="13.10.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="5.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down Expand Up @@ -62,5 +58,19 @@
<CodeGenerator>NSwagCSharp</CodeGenerator>
<Options>/UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions</Options>
</OpenApiReference>
<OpenApiReference Include="SchemaProperties\NullableReferenceTypesEnabled\swagger.g.json">
<Namespace>OpenApiClientTests.SchemaProperties.NullableReferenceTypesEnabled.GeneratedCode</Namespace>
<ClassName>NullableReferenceTypesEnabledClient</ClassName>
<OutputPath>NullableReferenceTypesEnabledClient.cs</OutputPath>
<CodeGenerator>NSwagCSharp</CodeGenerator>
<Options>/UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true</Options>
</OpenApiReference>
<OpenApiReference Include="SchemaProperties\NullableReferenceTypesDisabled\swagger.g.json">
<Namespace>OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode</Namespace>
<ClassName>NullableReferenceTypesDisabledClient</ClassName>
<OutputPath>NullableReferenceTypesDisabledClient.cs</OutputPath>
<CodeGenerator>NSwagCSharp</CodeGenerator>
<Options>/UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false</Options>
</OpenApiReference>
</ItemGroup>
</Project>
30 changes: 30 additions & 0 deletions test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Reflection;
using FluentAssertions;
using FluentAssertions.Types;

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);

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;

NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(propertyInfo);

nullabilityInfo.ReadState.Should().Be(NullabilityState.NotNull, because, becauseArgs);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading