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
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..f6d9dbdb2d 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, writer);
+
+ serializer.ContractResolver = new JsonApiAttributeContractResolver(alwaysIncludedAttributes);
+ serializer.Serialize(writer, value);
+ }
+
+ private static void AssertRequiredPropertiesAreNotExcluded(object value, AttributesObjectInfo alwaysIncludedAttributes, JsonWriter jsonWriter)
+ {
+ PropertyInfo[] propertyInfos = value.GetType().GetProperties();
+
+ foreach (PropertyInfo attributesPropertyInfo in propertyInfos)
+ {
+ bool isExplicitlyIncluded = alwaysIncludedAttributes.IsAttributeMarkedForInclusion(attributesPropertyInfo.Name);
+
+ if (isExplicitlyIncluded)
{
- _isSerializing = false;
+ return;
}
+
+ AssertRequiredPropertyIsNotIgnored(value, attributesPropertyInfo, jsonWriter.Path);
+ }
+ }
+
+ private static void AssertRequiredPropertyIsNotIgnored(object value, PropertyInfo attribute, string path)
+ {
+ 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 '{path}'.");
}
}
+
+ 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/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs
index e4abf42549..7c9cb75a15 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
};
}
diff --git a/test/OpenApiClientTests/LegacyClient/ClientAttributeRegistrationLifeTimeTests.cs b/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs
similarity index 78%
rename from test/OpenApiClientTests/LegacyClient/ClientAttributeRegistrationLifeTimeTests.cs
rename to test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs
index 4582f9578d..55e882fedf 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);
@@ -320,11 +320,14 @@ public async Task Attribute_registration_is_unaffected_by_preceding_disposed_att
Data = new AirplaneDataInPostRequest
{
Type = AirplaneResourceType.Airplanes,
- Attributes = new AirplaneAttributesInPostRequest()
+ Attributes = new AirplaneAttributesInPostRequest
+ {
+ Name = "Jay Jay the Jet Plane"
+ }
}
};
- using (apiClient.RegisterAttributesForRequestDocument(requestDocument1,
+ using (apiClient.OmitDefaultValuesForAttributesInRequestDocument(requestDocument1,
airplane => airplane.AirtimeInHours))
{
_ = await ApiResponse.TranslateAsync(async () => await apiClient.PostAirplaneAsync(requestDocument1));
@@ -347,7 +350,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 +371,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 +401,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/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/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs
new file mode 100644
index 0000000000..1ee3ee0fc3
--- /dev/null
+++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequestTests.cs
@@ -0,0 +1,873 @@
+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/NullableReferenceTypesDisabled/RequiredAttributesTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs
deleted file mode 100644
index b97535f908..0000000000
--- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-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;
-
-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
index d71423c997..8357d61d05 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": {
@@ -288,13 +1207,244 @@
"format": "int32",
"nullable": true
},
- "hasProducedEggs": {
- "type": "boolean"
+ "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"
+ },
+ "links": {
+ "$ref": "#/components/schemas/linksInResourceDocument"
}
},
"additionalProperties": false
},
- "chickenCollectionResponseDocument": {
+ "henHouseCollectionResponseDocument": {
"required": [
"data",
"links"
@@ -304,7 +1454,7 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/chickenDataInResponse"
+ "$ref": "#/components/schemas/henHouseDataInResponse"
}
},
"meta": {
@@ -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,14 +1514,14 @@
"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"
@@ -383,31 +1533,31 @@
},
"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,7 +1565,7 @@
"type": "object",
"properties": {
"data": {
- "$ref": "#/components/schemas/chickenDataInResponse"
+ "$ref": "#/components/schemas/henHouseDataInResponse"
},
"meta": {
"type": "object",
@@ -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"
},
@@ -461,6 +1669,24 @@
},
"additionalProperties": false
},
+ "linksInRelationshipObject": {
+ "required": [
+ "related",
+ "self"
+ ],
+ "type": "object",
+ "properties": {
+ "self": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "related": {
+ "minLength": 1,
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
"linksInResourceCollectionDocument": {
"required": [
"first",
@@ -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/RequestTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs
new file mode 100644
index 0000000000..aef305b63d
--- /dev/null
+++ b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequestTests.cs
@@ -0,0 +1,780 @@
+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/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs b/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs
deleted file mode 100644
index 5f35ec7f29..0000000000
--- a/test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled/RequiredAttributesTests.cs
+++ /dev/null
@@ -1,94 +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 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
index 14669b5c84..1f2d189d46 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": {
@@ -317,7 +1538,238 @@
},
"additionalProperties": false
},
- "cowCollectionResponseDocument": {
+ "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
+ },
+ "cowStableCollectionResponseDocument": {
"required": [
"data",
"links"
@@ -327,7 +1779,7 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/cowDataInResponse"
+ "$ref": "#/components/schemas/cowStableDataInResponse"
}
},
"meta": {
@@ -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,14 +1839,14 @@
"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"
@@ -406,31 +1858,31 @@
},
"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,7 +1890,7 @@
"type": "object",
"properties": {
"data": {
- "$ref": "#/components/schemas/cowDataInResponse"
+ "$ref": "#/components/schemas/cowStableDataInResponse"
},
"meta": {
"type": "object",
@@ -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"
},
@@ -484,6 +2014,24 @@
},
"additionalProperties": false
},
+ "linksInRelationshipObject": {
+ "required": [
+ "related",
+ "self"
+ ],
+ "type": "object",
+ "properties": {
+ "self": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "related": {
+ "minLength": 1,
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
"linksInResourceCollectionDocument": {
"required": [
"first",
@@ -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/JsonElementExtensions.cs b/test/OpenApiTests/JsonElementExtensions.cs
index 6f1ffe1432..3649127917 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);
+ }
+
+ private 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
diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/HenHouse.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/HenHouse.cs
new file mode 100644
index 0000000000..979b029717
--- /dev/null
+++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/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;
+
+[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/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 a5485de6e5..dca51da02b 100644
--- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs
+++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullabilityTests.cs
@@ -15,13 +15,14 @@ public NullabilityTests(OpenApiTestContext();
+ testContext.UseController();
testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled";
}
[Theory]
[InlineData("name")]
[InlineData("timeAtCurrentFarmInDays")]
- public async Task Property_in_schema_for_resource_should_be_nullable(string propertyName)
+ public async Task Property_in_schema_for_attribute_of_resource_should_be_nullable(string propertyName)
{
// Act
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
@@ -41,7 +42,7 @@ public async Task Property_in_schema_for_resource_should_be_nullable(string prop
[InlineData("age")]
[InlineData("weight")]
[InlineData("hasProducedEggs")]
- public async Task Property_in_schema_for_resource_should_not_be_nullable(string propertyName)
+ public async Task Property_in_schema_for_attribute_of_should_not_be_nullable(string propertyName)
{
// Act
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
@@ -55,4 +56,40 @@ public async Task Property_in_schema_for_resource_should_not_be_nullable(string
});
});
}
+
+ [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/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs
index 16bdc07e15..a93f4bf779 100644
--- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs
+++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs
@@ -4,13 +4,33 @@
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/NullableReferenceTypesEnabled/CowStable.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/CowStable.cs
new file mode 100644
index 0000000000..b267423501
--- /dev/null
+++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/CowStable.cs
@@ -0,0 +1,32 @@
+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 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/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 3b8d0597d7..5652c61f33 100644
--- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs
+++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullabilityTests.cs
@@ -15,13 +15,14 @@ public NullabilityTests(OpenApiTestContext();
+ testContext.UseController();
testContext.SwaggerDocumentOutputPath = "test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesEnabled";
}
[Theory]
[InlineData("nameOfPreviousFarm")]
[InlineData("timeAtCurrentFarmInDays")]
- public async Task Property_in_schema_for_resource_should_be_nullable(string propertyName)
+ public async Task Property_in_schema_for_attribute_of_resource_should_be_nullable(string propertyName)
{
// Act
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
@@ -43,7 +44,7 @@ public async Task Property_in_schema_for_resource_should_be_nullable(string prop
[InlineData("age")]
[InlineData("weight")]
[InlineData("hasProducedMilk")]
- public async Task Property_in_schema_for_resource_should_not_be_nullable(string attributeName)
+ public async Task Property_in_schema_for_attribute_of_resource_should_not_be_nullable(string attributeName)
{
// Act
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
@@ -57,4 +58,42 @@ public async Task Property_in_schema_for_resource_should_not_be_nullable(string
});
});
}
+
+ [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 b7011e7d27..e83298f28d 100644
--- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs
+++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs
@@ -4,13 +4,39 @@
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);
+ }
}