diff --git a/src/JsonApiDotNetCore.OpenApi.Client/IJsonApiClient.cs b/src/JsonApiDotNetCore.OpenApi.Client/IJsonApiClient.cs
index 5cb98a7b9a..9c71e32380 100644
--- a/src/JsonApiDotNetCore.OpenApi.Client/IJsonApiClient.cs
+++ b/src/JsonApiDotNetCore.OpenApi.Client/IJsonApiClient.cs
@@ -5,22 +5,19 @@ namespace JsonApiDotNetCore.OpenApi.Client;
public interface IJsonApiClient
{
///
+ /// Ensures correct serialization of JSON:API attributes in the request body of a POST/PATCH request at a resource endpoint. Properties with default
+ /// values are omitted, unless explicitly included 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.
+ /// 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 its default value" vs "you didn't touch it, so it
+ /// contains its default value" when converting to JSON.
///
///
///
/// 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:
+ /// Optional. A list of lambda expressions that indicate which properties to always include in the JSON request body. For example:
/// video.Title, video => video.Summary
/// ]]>
@@ -33,7 +30,8 @@ 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.
+ /// using statement, so the registrations are cleaned up after executing the request. After disposal, the client can be reused without the
+ /// registrations added earlier.
///
IDisposable WithPartialAttributeSerialization(TRequestDocument requestDocument,
params Expression>[] alwaysIncludedAttributeSelectors)
diff --git a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs
index f119396747..1c5bdec559 100644
--- a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs
+++ b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs
@@ -1,26 +1,25 @@
using System.Linq.Expressions;
using System.Reflection;
-using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
-using SysNotNull = System.Diagnostics.CodeAnalysis.NotNullAttribute;
namespace JsonApiDotNetCore.OpenApi.Client;
///
-/// Base class to inherit auto-generated client from. Enables to mark fields to be explicitly included in a request body, even if they are null or
-/// default.
+/// Base class to inherit auto-generated OpenAPI clients from. Provides support for partial POST/PATCH in JSON:API requests.
///
-[PublicAPI]
public abstract class JsonApiClient : IJsonApiClient
{
- private readonly JsonApiJsonConverter _jsonApiJsonConverter = new();
+ private readonly DocumentJsonConverter _documentJsonConverter = new();
+ ///
+ /// Initial setup. Call this from the UpdateJsonSerializerSettings partial method in the auto-generated OpenAPI client.
+ ///
protected void SetSerializerSettingsForJsonApi(JsonSerializerSettings settings)
{
ArgumentGuard.NotNull(settings);
- settings.Converters.Add(_jsonApiJsonConverter);
+ settings.Converters.Add(_documentJsonConverter);
}
///
@@ -40,14 +39,15 @@ public IDisposable WithPartialAttributeSerialization article.Title'.");
+ throw new ArgumentException($"The expression '{selector}' should select a single property. For example: 'article => article.Title'.",
+ nameof(alwaysIncludedAttributeSelectors));
}
}
- _jsonApiJsonConverter.RegisterDocument(requestDocument, new AttributeNamesContainer(attributeNames, typeof(TAttributesObject)));
+ var alwaysIncludedAttributes = new AlwaysIncludedAttributes(attributeNames, typeof(TAttributesObject));
+ _documentJsonConverter.RegisterDocument(requestDocument, alwaysIncludedAttributes);
- return new RequestDocumentRegistrationScope(_jsonApiJsonConverter, requestDocument);
+ return new DocumentRegistrationScope(_documentJsonConverter, requestDocument);
}
private static Expression RemoveConvert(Expression expression)
@@ -67,40 +67,95 @@ private static Expression RemoveConvert(Expression expression)
}
}
- private sealed class JsonApiJsonConverter : JsonConverter
+ ///
+ /// Tracks a JSON:API attributes registration for a JSON:API document instance in the serializer. Disposing removes the registration, so the client can
+ /// be reused.
+ ///
+ private sealed class DocumentRegistrationScope : IDisposable
{
- private readonly Dictionary _alwaysIncludedAttributesByRequestDocument = new();
- private readonly Dictionary> _requestDocumentsByType = new();
- private SerializationScope? _serializationScope;
+ private readonly DocumentJsonConverter _documentJsonConverter;
+ private readonly object _document;
+
+ public DocumentRegistrationScope(DocumentJsonConverter documentJsonConverter, object document)
+ {
+ ArgumentGuard.NotNull(documentJsonConverter);
+ ArgumentGuard.NotNull(document);
+
+ _documentJsonConverter = documentJsonConverter;
+ _document = document;
+ }
+
+ public void Dispose()
+ {
+ _documentJsonConverter.UnRegisterDocument(_document);
+ }
+ }
+
+ ///
+ /// Represents the set of JSON:API attributes to always send to the server, even if they are uninitialized (contain default value).
+ ///
+ private sealed class AlwaysIncludedAttributes
+ {
+ private readonly ISet _propertyNames;
+ private readonly Type _attributesObjectType;
+
+ public AlwaysIncludedAttributes(ISet propertyNames, Type attributesObjectType)
+ {
+ ArgumentGuard.NotNull(propertyNames);
+ ArgumentGuard.NotNull(attributesObjectType);
+
+ _propertyNames = propertyNames;
+ _attributesObjectType = attributesObjectType;
+ }
+
+ public bool ContainsAttribute(string propertyName)
+ {
+ return _propertyNames.Contains(propertyName);
+ }
+
+ public bool IsAttributesObjectType(Type type)
+ {
+ return _attributesObjectType == type;
+ }
+ }
+
+ ///
+ /// A that acts on JSON:API documents.
+ ///
+ private sealed class DocumentJsonConverter : JsonConverter
+ {
+ private readonly Dictionary _alwaysIncludedAttributesByDocument = new();
+ private readonly Dictionary> _documentsByType = new();
+ private bool _isSerializing;
public override bool CanRead => false;
- public void RegisterDocument(object requestDocument, AttributeNamesContainer alwaysIncludedAttributes)
+ public void RegisterDocument(object document, AlwaysIncludedAttributes alwaysIncludedAttributes)
{
- _alwaysIncludedAttributesByRequestDocument[requestDocument] = alwaysIncludedAttributes;
+ _alwaysIncludedAttributesByDocument[document] = alwaysIncludedAttributes;
- Type requestDocumentType = requestDocument.GetType();
+ Type documentType = document.GetType();
- if (!_requestDocumentsByType.ContainsKey(requestDocumentType))
+ if (!_documentsByType.ContainsKey(documentType))
{
- _requestDocumentsByType[requestDocumentType] = new HashSet();
+ _documentsByType[documentType] = new HashSet();
}
- _requestDocumentsByType[requestDocumentType].Add(requestDocument);
+ _documentsByType[documentType].Add(document);
}
- public void RemoveRegistration(object requestDocument)
+ public void UnRegisterDocument(object document)
{
- if (_alwaysIncludedAttributesByRequestDocument.ContainsKey(requestDocument))
+ if (_alwaysIncludedAttributesByDocument.ContainsKey(document))
{
- _alwaysIncludedAttributesByRequestDocument.Remove(requestDocument);
+ _alwaysIncludedAttributesByDocument.Remove(document);
- Type requestDocumentType = requestDocument.GetType();
- _requestDocumentsByType[requestDocumentType].Remove(requestDocument);
+ Type documentType = document.GetType();
+ _documentsByType[documentType].Remove(document);
- if (!_requestDocumentsByType[requestDocumentType].Any())
+ if (!_documentsByType[documentType].Any())
{
- _requestDocumentsByType.Remove(requestDocumentType);
+ _documentsByType.Remove(documentType);
}
}
}
@@ -109,12 +164,13 @@ public override bool CanConvert(Type objectType)
{
ArgumentGuard.NotNull(objectType);
- if (_serializationScope == null)
+ if (_isSerializing)
{
- return _requestDocumentsByType.ContainsKey(objectType);
+ // Protect against infinite recursion.
+ return false;
}
- return _serializationScope.ShouldConvertAsAttributesObject(objectType);
+ return _documentsByType.ContainsKey(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
@@ -125,198 +181,147 @@ public override object ReadJson(JsonReader reader, Type objectType, object? exis
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
ArgumentGuard.NotNull(writer);
- ArgumentGuard.NotNull(value);
ArgumentGuard.NotNull(serializer);
- if (_serializationScope == null)
+ if (value != null)
{
- AssertObjectIsRequestDocument(value);
-
- SerializeRequestDocument(writer, value, serializer);
- }
- else
- {
- AttributeNamesContainer? attributesObjectInfo = _serializationScope.AttributesObjectInScope;
-
- AssertObjectMatchesSerializationScope(attributesObjectInfo, value);
+ if (_alwaysIncludedAttributesByDocument.TryGetValue(value, out AlwaysIncludedAttributes? alwaysIncludedAttributes))
+ {
+ var attributesJsonConverter = new AttributesJsonConverter(alwaysIncludedAttributes);
+ serializer.Converters.Add(attributesJsonConverter);
+ }
- SerializeAttributesObject(attributesObjectInfo, writer, value, serializer);
+ try
+ {
+ _isSerializing = true;
+ serializer.Serialize(writer, value);
+ }
+ finally
+ {
+ _isSerializing = false;
+ }
}
}
+ }
- private void AssertObjectIsRequestDocument(object value)
- {
- Type objectType = value.GetType();
+ ///
+ /// A that acts on JSON:API attribute objects.
+ ///
+ private sealed class AttributesJsonConverter : JsonConverter
+ {
+ private readonly AlwaysIncludedAttributes _alwaysIncludedAttributes;
+ private bool _isSerializing;
- if (!_requestDocumentsByType.ContainsKey(objectType))
- {
- throw new UnreachableCodeException();
- }
- }
+ public override bool CanRead => false;
- private void SerializeRequestDocument(JsonWriter writer, object value, JsonSerializer serializer)
+ public AttributesJsonConverter(AlwaysIncludedAttributes alwaysIncludedAttributes)
{
- _serializationScope = new SerializationScope();
-
- if (_alwaysIncludedAttributesByRequestDocument.TryGetValue(value, out AttributeNamesContainer? attributesObjectInfo))
- {
- _serializationScope.AttributesObjectInScope = attributesObjectInfo;
- }
+ ArgumentGuard.NotNull(alwaysIncludedAttributes);
- try
- {
- serializer.Serialize(writer, value);
- }
- finally
- {
- _serializationScope = null;
- }
+ _alwaysIncludedAttributes = alwaysIncludedAttributes;
}
- private static void AssertObjectMatchesSerializationScope([SysNotNull] AttributeNamesContainer? attributesObjectInfo, object value)
+ public override bool CanConvert(Type objectType)
{
- Type objectType = value.GetType();
+ ArgumentGuard.NotNull(objectType);
- if (attributesObjectInfo == null || !attributesObjectInfo.MatchesAttributesObjectType(objectType))
+ if (_isSerializing)
{
- throw new UnreachableCodeException();
+ // Protect against infinite recursion.
+ return false;
}
+
+ return _alwaysIncludedAttributes.IsAttributesObjectType(objectType);
}
- private static void SerializeAttributesObject(AttributeNamesContainer alwaysIncludedAttributes, JsonWriter writer, object value,
- JsonSerializer serializer)
+ public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
- AssertRequiredPropertiesAreNotExcluded(value, alwaysIncludedAttributes, writer);
-
- serializer.ContractResolver = new JsonApiAttributeContractResolver(alwaysIncludedAttributes);
- serializer.Serialize(writer, value);
+ throw new UnreachableCodeException();
}
- private static void AssertRequiredPropertiesAreNotExcluded(object value, AttributeNamesContainer alwaysIncludedAttributes, JsonWriter jsonWriter)
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
- PropertyInfo[] propertyInfos = value.GetType().GetProperties();
+ ArgumentGuard.NotNull(writer);
+ ArgumentGuard.NotNull(serializer);
- foreach (PropertyInfo attributesPropertyInfo in propertyInfos)
+ if (value != null)
{
- bool isExplicitlyIncluded = alwaysIncludedAttributes.ContainsAttribute(attributesPropertyInfo.Name);
-
- if (isExplicitlyIncluded)
+ if (_alwaysIncludedAttributes.IsAttributesObjectType(value.GetType()))
{
- return;
+ AssertRequiredAttributesHaveNonDefaultValues(value, writer.Path);
+
+ serializer.ContractResolver = new JsonApiAttributeContractResolver(_alwaysIncludedAttributes);
}
- AssertRequiredPropertyIsNotIgnored(value, attributesPropertyInfo, jsonWriter.Path);
+ try
+ {
+ _isSerializing = true;
+ serializer.Serialize(writer, value);
+ }
+ finally
+ {
+ _isSerializing = false;
+ }
}
}
- private static void AssertRequiredPropertyIsNotIgnored(object value, PropertyInfo attribute, string path)
+ private void AssertRequiredAttributesHaveNonDefaultValues(object attributesObject, string jsonPath)
{
- JsonPropertyAttribute jsonPropertyForAttribute = attribute.GetCustomAttributes().Single();
-
- if (jsonPropertyForAttribute.Required is not (Required.Always or Required.AllowNull))
- {
- return;
- }
-
- bool isPropertyIgnored = DefaultValueEqualsCurrentValue(attribute, value);
-
- if (isPropertyIgnored)
+ foreach (PropertyInfo propertyInfo in attributesObject.GetType().GetProperties())
{
- throw new InvalidOperationException($"The following property should not be omitted: {path}.{jsonPropertyForAttribute.PropertyName}.");
- }
- }
+ bool isExplicitlyIncluded = _alwaysIncludedAttributes.ContainsAttribute(propertyInfo.Name);
- private static bool DefaultValueEqualsCurrentValue(PropertyInfo propertyInfo, object instance)
- {
- object? currentValue = propertyInfo.GetValue(instance);
- object? defaultValue = GetDefaultValue(propertyInfo.PropertyType);
-
- if (defaultValue == null)
- {
- return currentValue == null;
+ if (!isExplicitlyIncluded)
+ {
+ AssertPropertyHasNonDefaultValueIfRequired(attributesObject, propertyInfo, jsonPath);
+ }
}
-
- return defaultValue.Equals(currentValue);
}
- private static object? GetDefaultValue(Type type)
+ private static void AssertPropertyHasNonDefaultValueIfRequired(object attributesObject, PropertyInfo propertyInfo, string jsonPath)
{
- return type.IsValueType ? Activator.CreateInstance(type) : null;
- }
- }
+ JsonPropertyAttribute jsonProperty = propertyInfo.GetCustomAttributes().Single();
- private sealed class SerializationScope
- {
- private bool _isFirstAttemptToConvertAttributes = true;
- public AttributeNamesContainer? AttributesObjectInScope { get; set; }
-
- public bool ShouldConvertAsAttributesObject(Type type)
- {
- if (!_isFirstAttemptToConvertAttributes || AttributesObjectInScope == null)
+ if (jsonProperty.Required is Required.Always or Required.AllowNull)
{
- return false;
- }
+ bool propertyHasDefaultValue = PropertyHasDefaultValue(propertyInfo, attributesObject);
- if (!AttributesObjectInScope.MatchesAttributesObjectType(type))
- {
- return false;
+ if (propertyHasDefaultValue)
+ {
+ throw new InvalidOperationException(
+ $"Required property '{propertyInfo.Name}' at JSON path '{jsonPath}.{jsonProperty.PropertyName}' is not set. If sending its default value is intended, include it explicitly.");
+ }
}
-
- _isFirstAttemptToConvertAttributes = false;
- return true;
- }
- }
-
- private sealed class AttributeNamesContainer
- {
- private readonly ISet _attributeNames;
- private readonly Type _attributesObjectType;
-
- public AttributeNamesContainer(ISet attributeNames, Type attributesObjectType)
- {
- ArgumentGuard.NotNull(attributeNames);
- ArgumentGuard.NotNull(attributesObjectType);
-
- _attributeNames = attributeNames;
- _attributesObjectType = attributesObjectType;
}
- public bool ContainsAttribute(string name)
+ private static bool PropertyHasDefaultValue(PropertyInfo propertyInfo, object instance)
{
- return _attributeNames.Contains(name);
- }
-
- public bool MatchesAttributesObjectType(Type type)
- {
- return _attributesObjectType == type;
- }
- }
-
- private sealed class RequestDocumentRegistrationScope : IDisposable
- {
- private readonly JsonApiJsonConverter _jsonApiJsonConverter;
- private readonly object _requestDocument;
-
- public RequestDocumentRegistrationScope(JsonApiJsonConverter jsonApiJsonConverter, object requestDocument)
- {
- ArgumentGuard.NotNull(jsonApiJsonConverter);
- ArgumentGuard.NotNull(requestDocument);
+ object? propertyValue = propertyInfo.GetValue(instance);
+ object? defaultValue = GetDefaultValue(propertyInfo.PropertyType);
- _jsonApiJsonConverter = jsonApiJsonConverter;
- _requestDocument = requestDocument;
+ return EqualityComparer.Default.Equals(propertyValue, defaultValue);
}
- public void Dispose()
+ private static object? GetDefaultValue(Type type)
{
- _jsonApiJsonConverter.RemoveRegistration(_requestDocument);
+ return type.IsValueType ? Activator.CreateInstance(type) : null;
}
}
+ ///
+ /// Corrects the and JSON annotations at runtime, which appear on the auto-generated
+ /// properties for JSON:API attributes. For example:
+ ///
+ ///
+ ///
+ ///
private sealed class JsonApiAttributeContractResolver : DefaultContractResolver
{
- private readonly AttributeNamesContainer _alwaysIncludedAttributes;
+ private readonly AlwaysIncludedAttributes _alwaysIncludedAttributes;
- public JsonApiAttributeContractResolver(AttributeNamesContainer alwaysIncludedAttributes)
+ public JsonApiAttributeContractResolver(AlwaysIncludedAttributes alwaysIncludedAttributes)
{
ArgumentGuard.NotNull(alwaysIncludedAttributes);
@@ -325,25 +330,23 @@ public JsonApiAttributeContractResolver(AttributeNamesContainer alwaysIncludedAt
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
- JsonProperty property = base.CreateProperty(member, memberSerialization);
-
- bool canOmitAttribute = property.Required != Required.Always;
+ JsonProperty jsonProperty = base.CreateProperty(member, memberSerialization);
- if (canOmitAttribute && _alwaysIncludedAttributes.MatchesAttributesObjectType(property.DeclaringType!))
+ if (_alwaysIncludedAttributes.IsAttributesObjectType(jsonProperty.DeclaringType!))
{
- if (_alwaysIncludedAttributes.ContainsAttribute(property.UnderlyingName!))
+ if (_alwaysIncludedAttributes.ContainsAttribute(jsonProperty.UnderlyingName!))
{
- property.NullValueHandling = NullValueHandling.Include;
- property.DefaultValueHandling = DefaultValueHandling.Include;
+ jsonProperty.NullValueHandling = NullValueHandling.Include;
+ jsonProperty.DefaultValueHandling = DefaultValueHandling.Include;
}
else
{
- property.NullValueHandling = NullValueHandling.Ignore;
- property.DefaultValueHandling = DefaultValueHandling.Ignore;
+ jsonProperty.NullValueHandling = NullValueHandling.Ignore;
+ jsonProperty.DefaultValueHandling = DefaultValueHandling.Ignore;
}
}
- return property;
+ return jsonProperty;
}
}
}
diff --git a/src/JsonApiDotNetCore.OpenApi/ResourceFieldValidationMetadataProvider.cs b/src/JsonApiDotNetCore.OpenApi/ResourceFieldValidationMetadataProvider.cs
index bfbe32bb31..e40ab57618 100644
--- a/src/JsonApiDotNetCore.OpenApi/ResourceFieldValidationMetadataProvider.cs
+++ b/src/JsonApiDotNetCore.OpenApi/ResourceFieldValidationMetadataProvider.cs
@@ -10,7 +10,7 @@ namespace JsonApiDotNetCore.OpenApi;
internal sealed class ResourceFieldValidationMetadataProvider
{
private readonly bool _validateModelState;
- private readonly NullabilityInfoContext _nullabilityContext;
+ private readonly NullabilityInfoContext _nullabilityContext = new();
private readonly IModelMetadataProvider _modelMetadataProvider;
public ResourceFieldValidationMetadataProvider(IJsonApiOptions options, IModelMetadataProvider modelMetadataProvider)
@@ -20,7 +20,6 @@ public ResourceFieldValidationMetadataProvider(IJsonApiOptions options, IModelMe
_validateModelState = options.ValidateModelState;
_modelMetadataProvider = modelMetadataProvider;
- _nullabilityContext = new NullabilityInfoContext();
}
public bool IsNullable(ResourceFieldAttribute field)
@@ -33,18 +32,13 @@ public bool IsNullable(ResourceFieldAttribute field)
}
bool hasRequiredAttribute = field.Property.HasAttribute();
- NullabilityInfo nullabilityInfo = _nullabilityContext.Create(field.Property);
-
- if (field is HasManyAttribute)
- {
- return false;
- }
- if (hasRequiredAttribute && _validateModelState && nullabilityInfo.ReadState != NullabilityState.NotNull)
+ if (_validateModelState && hasRequiredAttribute)
{
return false;
}
+ NullabilityInfo nullabilityInfo = _nullabilityContext.Create(field.Property);
return nullabilityInfo.ReadState != NullabilityState.NotNull;
}
@@ -64,11 +58,12 @@ public bool IsRequired(ResourceFieldAttribute field)
return false;
}
- bool isNotNull = HasNullabilityStateNotNull(field);
- bool isRequiredValueType = field.Property.PropertyType.IsValueType && hasRequiredAttribute && isNotNull;
+ NullabilityInfo nullabilityInfo = _nullabilityContext.Create(field.Property);
+ bool isRequiredValueType = field.Property.PropertyType.IsValueType && hasRequiredAttribute && nullabilityInfo.ReadState == NullabilityState.NotNull;
if (isRequiredValueType)
{
+ // Special case: ASP.NET ModelState Validation effectively ignores value types with [Required].
return false;
}
@@ -77,16 +72,9 @@ public bool IsRequired(ResourceFieldAttribute field)
private bool IsModelStateValidationRequired(ResourceFieldAttribute field)
{
- ModelMetadata resourceFieldModelMetadata = _modelMetadataProvider.GetMetadataForProperties(field.Type.ClrType)
- .Single(modelMetadata => modelMetadata.PropertyName! == field.Property.Name);
+ ModelMetadata modelMetadata = _modelMetadataProvider.GetMetadataForProperty(field.Type.ClrType, field.Property.Name);
- return resourceFieldModelMetadata.ValidatorMetadata.Any(validatorMetadata => validatorMetadata is RequiredAttribute);
- }
-
- private bool HasNullabilityStateNotNull(ResourceFieldAttribute field)
- {
- NullabilityInfo resourceFieldNullabilityInfo = _nullabilityContext.Create(field.Property);
- bool hasNullabilityStateNotNull = resourceFieldNullabilityInfo is { ReadState: NullabilityState.NotNull, WriteState: NullabilityState.NotNull };
- return hasNullabilityStateNotNull;
+ // Non-nullable reference types are implicitly required, unless SuppressImplicitRequiredAttributeForNonNullableReferenceTypes is set.
+ return modelMetadata.ValidatorMetadata.Any(validatorMetadata => validatorMetadata is RequiredAttribute);
}
}
diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs
index 678229864c..9b31d2c31f 100644
--- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs
+++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs
@@ -1,4 +1,4 @@
-using JsonApiDotNetCore.Configuration;
+using System.Text.Json;
using JsonApiDotNetCore.OpenApi.JsonApiObjects;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
@@ -34,14 +34,13 @@ internal sealed class ResourceFieldObjectSchemaBuilder
private readonly RelationshipTypeFactory _relationshipTypeFactory;
public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISchemaRepositoryAccessor schemaRepositoryAccessor,
- SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, IJsonApiOptions options,
+ SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, JsonNamingPolicy? namingPolicy,
ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider)
{
ArgumentGuard.NotNull(resourceTypeInfo);
ArgumentGuard.NotNull(schemaRepositoryAccessor);
ArgumentGuard.NotNull(defaultSchemaGenerator);
ArgumentGuard.NotNull(resourceTypeSchemaGenerator);
- ArgumentGuard.NotNull(options);
ArgumentGuard.NotNull(resourceFieldValidationMetadataProvider);
_resourceTypeInfo = resourceTypeInfo;
@@ -50,7 +49,7 @@ public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISche
_resourceTypeSchemaGenerator = resourceTypeSchemaGenerator;
_resourceFieldValidationMetadataProvider = resourceFieldValidationMetadataProvider;
- _nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(schemaRepositoryAccessor, options.SerializerOptions.PropertyNamingPolicy);
+ _nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(schemaRepositoryAccessor, namingPolicy);
_relationshipTypeFactory = new RelationshipTypeFactory(resourceFieldValidationMetadataProvider);
_schemasForResourceFields = GetFieldSchemas();
}
diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs
index 8e061fff57..ffa3729dcf 100644
--- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs
+++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs
@@ -31,7 +31,7 @@ public ResourceObjectSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IRe
_allowClientGeneratedIds = options.AllowClientGeneratedIds;
_resourceFieldObjectSchemaBuilderFactory = resourceTypeInfo => new ResourceFieldObjectSchemaBuilder(resourceTypeInfo, schemaRepositoryAccessor,
- defaultSchemaGenerator, _resourceTypeSchemaGenerator, options, resourceFieldValidationMetadataProvider);
+ defaultSchemaGenerator, _resourceTypeSchemaGenerator, options.SerializerOptions.PropertyNamingPolicy, resourceFieldValidationMetadataProvider);
}
public OpenApiSchema GenerateSchema(Type resourceObjectType)
diff --git a/test/OpenApiClientTests/BaseOpenApiClientTests.cs b/test/OpenApiClientTests/BaseOpenApiClientTests.cs
new file mode 100644
index 0000000000..a8ba84de9a
--- /dev/null
+++ b/test/OpenApiClientTests/BaseOpenApiClientTests.cs
@@ -0,0 +1,86 @@
+using System.Linq.Expressions;
+using System.Reflection;
+using JsonApiDotNetCore.OpenApi.Client;
+
+namespace OpenApiClientTests;
+
+public abstract class BaseOpenApiClientTests
+{
+ private const string AttributesObjectParameterName = "attributesObject";
+
+ protected static Expression> CreateAttributeSelectorFor(string propertyName)
+ where TAttributesObject : class
+ {
+ Type attributesObjectType = typeof(TAttributesObject);
+
+ ParameterExpression parameter = Expression.Parameter(attributesObjectType, AttributesObjectParameterName);
+ MemberExpression property = Expression.Property(parameter, propertyName);
+ UnaryExpression toObjectConversion = Expression.Convert(property, typeof(object));
+
+ return Expression.Lambda>(toObjectConversion, parameter);
+ }
+
+ ///
+ /// Sets the property on the specified source to its default value (null for string, 0 for int, false for bool, etc).
+ ///
+ protected static void SetPropertyToDefaultValue(T source, string propertyName)
+ where T : class
+ {
+ ArgumentGuard.NotNull(source);
+ ArgumentGuard.NotNull(propertyName);
+
+ PropertyInfo property = GetExistingProperty(typeof(T), propertyName);
+
+ object? defaultValue = property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) : null;
+ property.SetValue(source, defaultValue);
+ }
+
+ ///
+ /// Sets the property on the specified source to its initial value, when the type was constructed. This takes the presence of a type initializer into
+ /// account.
+ ///
+ protected static void SetPropertyToInitialValue(T source, string propertyName)
+ where T : class, new()
+ {
+ ArgumentGuard.NotNull(source);
+ ArgumentGuard.NotNull(propertyName);
+
+ var emptyRelationshipsObject = new T();
+ object? defaultValue = emptyRelationshipsObject.GetPropertyValue(propertyName);
+
+ source.SetPropertyValue(propertyName, defaultValue);
+ }
+
+ ///
+ /// Sets the 'Data' property of the specified relationship to null .
+ ///
+ protected static void SetDataPropertyToNull(T source, string relationshipPropertyName)
+ where T : class
+ {
+ ArgumentGuard.NotNull(source);
+ ArgumentGuard.NotNull(relationshipPropertyName);
+
+ PropertyInfo relationshipProperty = GetExistingProperty(typeof(T), relationshipPropertyName);
+ object? relationshipValue = relationshipProperty.GetValue(source);
+
+ if (relationshipValue == null)
+ {
+ throw new InvalidOperationException($"Property '{typeof(T).Name}.{relationshipPropertyName}' is null.");
+ }
+
+ PropertyInfo dataProperty = GetExistingProperty(relationshipProperty.PropertyType, "Data");
+ dataProperty.SetValue(relationshipValue, null);
+ }
+
+ private static PropertyInfo GetExistingProperty(Type type, string propertyName)
+ {
+ PropertyInfo? property = type.GetProperty(propertyName);
+
+ if (property == null)
+ {
+ throw new InvalidOperationException($"Type '{type.Name}' does not contain a property named '{propertyName}'.");
+ }
+
+ return property;
+ }
+}
diff --git a/test/OpenApiClientTests/FakeHttpClientWrapper.cs b/test/OpenApiClientTests/FakeHttpClientWrapper.cs
index 8226a4e496..0d3e868384 100644
--- a/test/OpenApiClientTests/FakeHttpClientWrapper.cs
+++ b/test/OpenApiClientTests/FakeHttpClientWrapper.cs
@@ -23,11 +23,11 @@ private FakeHttpClientWrapper(HttpClient httpClient, FakeHttpMessageHandler hand
_handler = handler;
}
- public JsonElement ParseRequestBody()
+ public JsonElement GetRequestBodyAsJson()
{
if (RequestBody == null)
{
- throw new InvalidOperationException();
+ throw new InvalidOperationException("No body was provided with the request.");
}
using JsonDocument jsonDocument = JsonDocument.Parse(RequestBody);
diff --git a/test/OpenApiClientTests/FakerFactory.cs b/test/OpenApiClientTests/FakerFactory.cs
index c28adbaa60..db4fe3a406 100644
--- a/test/OpenApiClientTests/FakerFactory.cs
+++ b/test/OpenApiClientTests/FakerFactory.cs
@@ -1,5 +1,7 @@
using System.Reflection;
using AutoBogus;
+using JetBrains.Annotations;
+using TestBuildingBlocks;
namespace OpenApiClientTests;
@@ -14,18 +16,27 @@ private FakerFactory()
public AutoFaker Create()
where TTarget : class
{
- return new AutoFaker();
+ return GetDeterministicFaker();
+ }
+
+ private static AutoFaker GetDeterministicFaker()
+ where TTarget : class
+ {
+ var autoFaker = new AutoFaker();
+ autoFaker.UseSeed(FakerContainer.GetFakerSeed());
+ return autoFaker;
}
public AutoFaker CreateForObjectWithResourceId()
where TTarget : class
{
- return new AutoFaker().Configure(builder => builder.WithOverride(new ResourceStringIdOverride()));
+ return GetDeterministicFaker().Configure(builder => builder.WithOverride(new ResourceStringIdOverride()));
}
private sealed class ResourceStringIdOverride : AutoGeneratorOverride
{
- private readonly IAutoFaker _idFaker = AutoFaker.Create();
+ // AutoFaker has a class constraint, while TId has not, so we need to wrap it.
+ private readonly AutoFaker> _idContainerFaker = GetDeterministicFaker>();
public override bool CanOverride(AutoGenerateContext context)
{
@@ -35,7 +46,36 @@ public override bool CanOverride(AutoGenerateContext context)
public override void Generate(AutoGenerateOverrideContext context)
{
- ((dynamic)context.Instance).Id = _idFaker.Generate()!.ToString()!;
+ object idValue = _idContainerFaker.Generate().Value!;
+ idValue = ToPositiveValue(idValue);
+
+ ((dynamic)context.Instance).Id = idValue.ToString()!;
+ }
+
+ private static object ToPositiveValue(object idValue)
+ {
+ if (idValue is short shortValue)
+ {
+ return Math.Abs(shortValue);
+ }
+
+ if (idValue is int intValue)
+ {
+ return Math.Abs(intValue);
+ }
+
+ if (idValue is long longValue)
+ {
+ return Math.Abs(longValue);
+ }
+
+ return idValue;
+ }
+
+ [UsedImplicitly(ImplicitUseTargetFlags.Members)]
+ private sealed class ObjectContainer
+ {
+ public TValue? Value { get; set; }
}
}
}
diff --git a/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs b/test/OpenApiClientTests/LegacyClient/PartialAttributeSerializationLifetimeTests.cs
similarity index 93%
rename from test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs
rename to test/OpenApiClientTests/LegacyClient/PartialAttributeSerializationLifetimeTests.cs
index b6855c06d2..be71dfa1e3 100644
--- a/test/OpenApiClientTests/LegacyClient/RequestDocumentRegistrationLifetimeTests.cs
+++ b/test/OpenApiClientTests/LegacyClient/PartialAttributeSerializationLifetimeTests.cs
@@ -6,10 +6,10 @@
namespace OpenApiClientTests.LegacyClient;
-public sealed class RequestDocumentRegistrationLifetimeTests
+public sealed class PartialAttributeSerializationLifetimeTests
{
[Fact]
- public async Task Disposed_request_document_registration_does_not_affect_request()
+ public async Task Disposed_registration_does_not_affect_request()
{
// Arrange
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
@@ -51,7 +51,7 @@ public async Task Disposed_request_document_registration_does_not_affect_request
}
[Fact]
- public async Task Request_document_registration_can_be_used_for_multiple_requests()
+ public async Task Registration_can_be_used_for_multiple_requests()
{
// Arrange
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
@@ -98,7 +98,7 @@ public async Task Request_document_registration_can_be_used_for_multiple_request
}
[Fact]
- public async Task Request_is_unaffected_by_request_document_registration_of_different_request_document_of_same_type()
+ public async Task Request_is_unaffected_by_registration_for_different_document_of_same_type()
{
// Arrange
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
@@ -153,7 +153,7 @@ public async Task Request_is_unaffected_by_request_document_registration_of_diff
}
[Fact]
- public async Task Attribute_values_can_be_changed_after_request_document_registration()
+ public async Task Attribute_values_can_be_changed_after_registration()
{
// Arrange
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
@@ -196,7 +196,7 @@ public async Task Attribute_values_can_be_changed_after_request_document_registr
}
[Fact]
- public async Task Request_document_registration_is_unaffected_by_successive_registration_of_request_document_of_different_type()
+ public async Task Registration_is_unaffected_by_successive_registration_for_document_of_different_type()
{
// Arrange
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
@@ -247,7 +247,7 @@ public async Task Request_document_registration_is_unaffected_by_successive_regi
}
[Fact]
- public async Task Request_document_registration_is_unaffected_by_preceding_disposed_registration_of_different_request_document_of_same_type()
+ public async Task Registration_is_unaffected_by_preceding_disposed_registration_for_different_document_of_same_type()
{
// Arrange
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
@@ -309,7 +309,7 @@ public async Task Request_document_registration_is_unaffected_by_preceding_dispo
}
[Fact]
- public async Task Request_document_registration_is_unaffected_by_preceding_disposed_registration_of_request_document_of_different_type()
+ public async Task Registration_is_unaffected_by_preceding_disposed_registration_for_document_of_different_type()
{
// Arrange
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
@@ -371,7 +371,7 @@ public async Task Request_document_registration_is_unaffected_by_preceding_dispo
}
[Fact]
- public async Task Request_document_registration_is_unaffected_by_preceding_registration_of_different_request_document_of_same_type()
+ public async Task Registration_is_unaffected_by_preceding_registration_for_different_document_of_same_type()
{
// Arrange
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
diff --git a/test/OpenApiClientTests/ObjectExtensions.cs b/test/OpenApiClientTests/ObjectExtensions.cs
index 9bf15b6ac7..3f2633f5ff 100644
--- a/test/OpenApiClientTests/ObjectExtensions.cs
+++ b/test/OpenApiClientTests/ObjectExtensions.cs
@@ -10,8 +10,7 @@ internal static class ObjectExtensions
ArgumentGuard.NotNull(source);
ArgumentGuard.NotNull(propertyName);
- PropertyInfo propertyInfo = source.GetType().GetProperties().Single(property => property.Name == propertyName);
-
+ PropertyInfo propertyInfo = GetExistingProperty(source.GetType(), propertyName);
return propertyInfo.GetValue(source);
}
@@ -20,18 +19,19 @@ public static void SetPropertyValue(this object source, string propertyName, obj
ArgumentGuard.NotNull(source);
ArgumentGuard.NotNull(propertyName);
- PropertyInfo propertyInfo = source.GetType().GetProperties().Single(property => property.Name == propertyName);
-
+ PropertyInfo propertyInfo = GetExistingProperty(source.GetType(), propertyName);
propertyInfo.SetValue(source, value);
}
- public static object? GetDefaultValueForProperty(this object source, string propertyName)
+ private static PropertyInfo GetExistingProperty(Type type, string propertyName)
{
- ArgumentGuard.NotNull(source);
- ArgumentGuard.NotNull(propertyName);
+ PropertyInfo? propertyInfo = type.GetProperty(propertyName);
- PropertyInfo propertyInfo = source.GetType().GetProperties().Single(property => property.Name == propertyName);
+ if (propertyInfo == null)
+ {
+ throw new InvalidOperationException($"Type '{type}' does not contain a property named '{propertyName}'.");
+ }
- return Activator.CreateInstance(propertyInfo.PropertyType);
+ return propertyInfo;
}
}
diff --git a/test/OpenApiClientTests/OpenApiClientTests.cs b/test/OpenApiClientTests/OpenApiClientTests.cs
deleted file mode 100644
index bb0b5d61a1..0000000000
--- a/test/OpenApiClientTests/OpenApiClientTests.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Linq.Expressions;
-
-namespace OpenApiClientTests;
-
-public class OpenApiClientTests
-{
- private const string AttributesObjectParameterName = "attributesObject";
-
- protected static Expression> CreateIncludedAttributeSelector(string propertyName)
- where TAttributesObject : class
- {
- Type attributesObjectType = typeof(TAttributesObject);
-
- ParameterExpression parameter = Expression.Parameter(attributesObjectType, AttributesObjectParameterName);
- MemberExpression property = Expression.Property(parameter, propertyName);
- UnaryExpression toObjectConversion = Expression.Convert(property, typeof(object));
-
- return Expression.Lambda>(toObjectConversion, parameter);
- }
-}
diff --git a/test/OpenApiClientTests/OpenApiClientTests.csproj b/test/OpenApiClientTests/OpenApiClientTests.csproj
index c5e7f0acdd..0052d618eb 100644
--- a/test/OpenApiClientTests/OpenApiClientTests.csproj
+++ b/test/OpenApiClientTests/OpenApiClientTests.csproj
@@ -53,34 +53,33 @@
NSwagCSharp
/UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions
-
+
NrtOffMsvOffClient
NrtOffMsvOffClient.cs
NSwagCSharp
- OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode
+ OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOff.GeneratedCode
/UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false
-
+
NrtOffMsvOnClient
NrtOffMsvOnClient.cs
NSwagCSharp
- OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode
+ OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOn.GeneratedCode
/UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false
-
+
NrtOnMsvOffClient
NrtOnMsvOffClient.cs
NSwagCSharp
- OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled.GeneratedCode
+ OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOn.ModelStateValidationOff.GeneratedCode
/UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true
-
+
NrtOnMsvOnClient
NrtOnMsvOnClient.cs
NSwagCSharp
- OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled.GeneratedCode
+ OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOn.ModelStateValidationOn.GeneratedCode
/UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true
-
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs
deleted file mode 100644
index 8079c4896c..0000000000
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/NullabilityTests.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Reflection;
-using FluentAssertions;
-using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode;
-using Xunit;
-
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled;
-
-public sealed class NullabilityTests
-{
- [Theory]
- [InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), NullabilityState.Unknown)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), NullabilityState.Unknown)]
- [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), NullabilityState.Nullable)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), NullabilityState.Nullable)]
- public void Nullability_of_generated_property_is_as_expected(string propertyName, NullabilityState expectedState)
- {
- PropertyInfo[] properties = typeof(ResourceAttributesInPostRequest).GetProperties();
- PropertyInfo property = properties.Single(property => property.Name == propertyName);
- property.Should().HaveNullabilityState(expectedState);
- }
-}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs
deleted file mode 100644
index 53252ee439..0000000000
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/NullabilityTests.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Reflection;
-using FluentAssertions;
-using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode;
-using Xunit;
-
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled;
-
-public sealed class NullabilityTests
-{
- [Theory]
- [InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), NullabilityState.Unknown)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), NullabilityState.Unknown)]
- [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), NullabilityState.Nullable)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), NullabilityState.NotNull)]
- public void Nullability_of_generated_property_is_as_expected(string propertyName, NullabilityState expectedState)
- {
- PropertyInfo[] properties = typeof(ResourceAttributesInPostRequest).GetProperties();
- PropertyInfo property = properties.Single(property => property.Name == propertyName);
- property.Should().HaveNullabilityState(expectedState);
- }
-}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs
deleted file mode 100644
index dc2bc84ccd..0000000000
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/NullabilityTests.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System.Reflection;
-using FluentAssertions;
-using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled.GeneratedCode;
-using Xunit;
-
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled;
-
-public sealed class NullabilityTests
-{
- [Theory]
- [InlineData(nameof(ResourceAttributesInPostRequest.NonNullableReferenceType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNonNullableReferenceType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.NullableReferenceType), NullabilityState.Nullable)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableReferenceType), NullabilityState.Nullable)]
- [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), NullabilityState.Nullable)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), NullabilityState.Nullable)]
- public void Nullability_of_generated_property_is_as_expected(string propertyName, NullabilityState expectedState)
- {
- PropertyInfo[] properties = typeof(ResourceAttributesInPostRequest).GetProperties();
- PropertyInfo property = properties.Single(property => property.Name == propertyName);
- property.Should().HaveNullabilityState(expectedState);
- }
-}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs
deleted file mode 100644
index e3c1a5ce31..0000000000
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/NullabilityTests.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System.Reflection;
-using FluentAssertions;
-using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled.GeneratedCode;
-using Xunit;
-
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled;
-
-public sealed class NullabilityTests
-{
- [Theory]
- [InlineData(nameof(ResourceAttributesInPostRequest.NonNullableReferenceType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNonNullableReferenceType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.NullableReferenceType), NullabilityState.Nullable)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableReferenceType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), NullabilityState.NotNull)]
- [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), NullabilityState.Nullable)]
- [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), NullabilityState.NotNull)]
- public void Nullability_of_generated_property_is_as_expected(string propertyName, NullabilityState expectedState)
- {
- PropertyInfo[] properties = typeof(ResourceAttributesInPostRequest).GetProperties();
- PropertyInfo property = properties.Single(property => property.Name == propertyName);
- property.Should().HaveNullabilityState(expectedState);
- }
-}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/CreateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/CreateResourceTests.cs
similarity index 56%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/CreateResourceTests.cs
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/CreateResourceTests.cs
index 5dfa347b05..ef5ed004e0 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/CreateResourceTests.cs
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/CreateResourceTests.cs
@@ -4,15 +4,15 @@
using FluentAssertions;
using FluentAssertions.Specialized;
using Newtonsoft.Json;
-using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode;
+using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOff.GeneratedCode;
using TestBuildingBlocks;
using Xunit;
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled;
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOff;
-public sealed class CreateResourceTests : OpenApiClientTests
+public sealed class CreateResourceTests : BaseOpenApiClientTests
{
- private readonly ResourceFieldValidationFakers _fakers = new();
+ private readonly NrtOffMsvOffFakers _fakers = new();
[Theory]
[InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), "referenceType")]
@@ -37,33 +37,32 @@ public async Task Can_clear_attribute(string attributePropertyName, string jsonP
}
};
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, null);
+ SetPropertyToDefaultValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
Expression> includeAttributeSelector =
- CreateIncludedAttributeSelector(attributePropertyName);
+ CreateAttributeSelectorFor(attributePropertyName);
- using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.attributes").With(attributesObject =>
+ document.Should().ContainPath("data.attributes").With(attributesObject =>
{
- attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null));
+ attributesObject.Should().ContainPath(jsonPropertyName).With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null));
});
}
[Theory]
[InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")]
- public async Task Can_set_default_value_to_attribute(string attributePropertyName, string jsonPropertyName)
+ public async Task Can_set_attribute_to_default_value(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -81,27 +80,25 @@ public async Task Can_set_default_value_to_attribute(string attributePropertyNam
}
};
- object? defaultValue = requestDocument.Data.Attributes.GetDefaultValueForProperty(attributePropertyName);
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue);
-
- Expression> includeAttributeSelector =
- CreateIncludedAttributeSelector(attributePropertyName);
+ SetPropertyToDefaultValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ Expression> includeAttributeSelector =
+ CreateAttributeSelectorFor(attributePropertyName);
+
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.attributes").With(attributesObject =>
+ document.Should().ContainPath("data.attributes").With(attributesObject =>
{
- attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ShouldBeInteger(0));
+ attributesObject.Should().ContainPath(jsonPropertyName).With(attribute => attribute.Should().Be(0));
});
}
@@ -109,7 +106,7 @@ public async Task Can_set_default_value_to_attribute(string attributePropertyNam
[InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), "referenceType")]
[InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")]
[InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), "nullableValueType")]
- public async Task Can_exclude_attribute(string attributePropertyName, string jsonPropertyName)
+ public async Task Can_omit_attribute(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -127,25 +124,22 @@ public async Task Can_exclude_attribute(string attributePropertyName, string jso
}
};
- ResourceAttributesInPostRequest emptyAttributesObject = new();
- object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName);
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.attributes").With(attributesObject =>
+ document.Should().ContainPath("data.attributes").With(attributesObject =>
{
- attributesObject.ShouldNotContainPath(jsonPropertyName);
+ attributesObject.Should().NotContainPath(jsonPropertyName);
});
}
@@ -153,7 +147,7 @@ public async Task Can_exclude_attribute(string attributePropertyName, string jso
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), "requiredReferenceType")]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), "requiredNullableValueType")]
- public async Task Cannot_exclude_attribute(string attributePropertyName, string jsonPropertyName)
+ public async Task Cannot_omit_attribute(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -171,31 +165,28 @@ public async Task Cannot_exclude_attribute(string attributePropertyName, string
}
};
- ResourceAttributesInPostRequest emptyAttributesObject = new();
- object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName);
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
-
- // Assert
- ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
- InvalidOperationException exception = assertion.Subject.Single();
- exception.Message.Should().Be($"The following property should not be omitted: data.attributes.{jsonPropertyName}.");
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
+
+ // Assert
+ ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
+ InvalidOperationException exception = assertion.Subject.Single();
+
+ exception.Message.Should().Be(
+ $"Required property '{attributePropertyName}' at JSON path 'data.attributes.{jsonPropertyName}' is not set. If sending its default value is intended, include it explicitly.");
}
[Theory]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), nameof(ResourceRelationshipsInPostRequest.ToOne.Data), "toOne")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), nameof(ResourceRelationshipsInPostRequest.RequiredToOne.Data), "requiredToOne")]
- public async Task Can_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName,
- string jsonPropertyName)
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), "toOne")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), "requiredToOne")]
+ public async Task Can_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -213,32 +204,29 @@ public async Task Can_clear_relationship_with_partial_attribute_serialization(st
}
};
- object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName);
- relationshipObject!.SetPropertyValue(dataPropertyName, null);
+ SetDataPropertyToNull(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
// Act
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject =>
+ document.Should().ContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject =>
{
relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null);
});
}
[Theory]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), nameof(ResourceRelationshipsInPostRequest.ToOne.Data), "toOne")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), nameof(ResourceRelationshipsInPostRequest.RequiredToOne.Data), "requiredToOne")]
- public async Task Can_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName,
- string jsonPropertyName)
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), "toOne")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), "requiredToOne")]
+ public async Task Can_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -256,8 +244,7 @@ public async Task Can_clear_relationship_without_partial_attribute_serialization
}
};
- object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName);
- relationshipObject!.SetPropertyValue(dataPropertyName, null);
+ SetDataPropertyToNull(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
@@ -266,19 +253,18 @@ public async Task Can_clear_relationship_without_partial_attribute_serialization
await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject =>
+ document.Should().ContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject =>
{
relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null);
});
}
[Theory]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")]
- public async Task Cannot_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName,
- string jsonPropertyName)
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany")]
+ public async Task Cannot_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -296,32 +282,28 @@ public async Task Cannot_clear_relationship_with_partial_attribute_serialization
}
};
- object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName);
- relationshipObject!.SetPropertyValue(dataPropertyName, null);
+ SetDataPropertyToNull(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
- // Assert
- ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
- JsonSerializationException exception = assertion.Subject.Single();
+ // Assert
+ ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
+ JsonSerializationException exception = assertion.Subject.Single();
- exception.Message.Should()
- .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'.");
- }
+ exception.Message.Should().Be(
+ $"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'.");
}
[Theory]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")]
- public async Task Cannot_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName,
- string jsonPropertyName)
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany")]
+ public async Task Cannot_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -339,28 +321,26 @@ public async Task Cannot_clear_relationship_without_partial_attribute_serializat
}
};
- object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName);
- relationshipObject!.SetPropertyValue(dataPropertyName, null);
+ SetDataPropertyToNull(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
// Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
// Assert
ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
JsonSerializationException exception = assertion.Subject.Single();
- exception.Message.Should()
- .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'.");
+ exception.Message.Should().Be(
+ $"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'.");
}
[Theory]
[InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), "toOne")]
[InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")]
- public async Task Can_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
+ public async Task Can_omit_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -378,32 +358,29 @@ public async Task Can_exclude_relationship_with_partial_attribute_serialization(
}
};
- ResourceRelationshipsInPostRequest emptyRelationshipsObject = new();
- object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName);
- requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
// Act
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.relationships").With(relationshipsObject =>
+ document.Should().ContainPath("data.relationships").With(relationshipsObject =>
{
- relationshipsObject.ShouldNotContainPath(jsonPropertyName);
+ relationshipsObject.Should().NotContainPath(jsonPropertyName);
});
}
[Theory]
[InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), "toOne")]
[InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")]
- public async Task Can_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
+ public async Task Can_omit_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -421,25 +398,20 @@ public async Task Can_exclude_relationship_without_partial_attribute_serializati
}
};
- ResourceRelationshipsInPostRequest emptyRelationshipsObject = new();
- object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName);
- requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.relationships").With(relationshipsObject =>
+ document.Should().ContainPath("data.relationships").With(relationshipsObject =>
{
- relationshipsObject.ShouldNotContainPath(jsonPropertyName);
+ relationshipsObject.Should().NotContainPath(jsonPropertyName);
});
}
}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/GeneratedCode/NrtOffMsvOffClient.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/GeneratedCode/NrtOffMsvOffClient.cs
similarity index 85%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/GeneratedCode/NrtOffMsvOffClient.cs
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/GeneratedCode/NrtOffMsvOffClient.cs
index 4bea9db090..f3ee9a9c53 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/GeneratedCode/NrtOffMsvOffClient.cs
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/GeneratedCode/NrtOffMsvOffClient.cs
@@ -1,7 +1,7 @@
using JsonApiDotNetCore.OpenApi.Client;
using Newtonsoft.Json;
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode;
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOff.GeneratedCode;
internal partial class NrtOffMsvOffClient : JsonApiClient
{
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/NrtOffMsvOffFakers.cs
similarity index 57%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/NrtOffMsvOffFakers.cs
index 817344c11c..adb0e97833 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/ResourceFieldValidationFakers.cs
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/NrtOffMsvOffFakers.cs
@@ -1,9 +1,9 @@
using Bogus;
-using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode;
+using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOff.GeneratedCode;
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled;
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOff;
-internal sealed class ResourceFieldValidationFakers
+internal sealed class NrtOffMsvOffFakers
{
private readonly Lazy> _lazyPostAttributesFaker = new(() =>
FakerFactory.Instance.Create());
@@ -11,14 +11,14 @@ internal sealed class ResourceFieldValidationFakers
private readonly Lazy> _lazyPatchAttributesFaker = new(() =>
FakerFactory.Instance.Create());
- private readonly Lazy> _lazyNullableToOneFaker = new(() =>
- FakerFactory.Instance.CreateForObjectWithResourceId());
+ private readonly Lazy> _lazyNullableToOneFaker = new(() =>
+ FakerFactory.Instance.CreateForObjectWithResourceId());
- private readonly Lazy> _lazyToManyFaker = new(() =>
- FakerFactory.Instance.CreateForObjectWithResourceId());
+ private readonly Lazy> _lazyToManyFaker = new(() =>
+ FakerFactory.Instance.CreateForObjectWithResourceId());
public Faker PostAttributes => _lazyPostAttributesFaker.Value;
public Faker PatchAttributes => _lazyPatchAttributesFaker.Value;
- public Faker NullableToOne => _lazyNullableToOneFaker.Value;
- public Faker ToMany => _lazyToManyFaker.Value;
+ public Faker NullableToOne => _lazyNullableToOneFaker.Value;
+ public Faker ToMany => _lazyToManyFaker.Value;
}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/NullabilityTests.cs
new file mode 100644
index 0000000000..d15ba31e2b
--- /dev/null
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/NullabilityTests.cs
@@ -0,0 +1,45 @@
+using System.Reflection;
+using FluentAssertions;
+using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOff.GeneratedCode;
+using TestBuildingBlocks;
+using Xunit;
+
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOff;
+
+public sealed class NullabilityTests
+{
+ [Theory]
+ [InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), NullabilityState.Unknown)]
+ [InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), NullabilityState.Unknown)]
+ [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), NullabilityState.NotNull)]
+ [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), NullabilityState.NotNull)]
+ [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), NullabilityState.Nullable)]
+ [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), NullabilityState.Nullable)]
+ public void Nullability_of_generated_attribute_property_is_as_expected(string propertyName, NullabilityState expectedState)
+ {
+ // Act
+ PropertyInfo? property = typeof(ResourceAttributesInPostRequest).GetProperty(propertyName);
+
+ // Assert
+ property.ShouldNotBeNull();
+ property.Should().HaveNullabilityState(expectedState);
+ }
+
+ [Theory]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), NullabilityState.Unknown)]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), NullabilityState.Unknown)]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), NullabilityState.Unknown)]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), NullabilityState.Unknown)]
+ public void Nullability_of_generated_relationship_property_is_as_expected(string propertyName, NullabilityState expectedState)
+ {
+ // Act
+ PropertyInfo? relationshipProperty = typeof(ResourceRelationshipsInPostRequest).GetProperty(propertyName);
+
+ // Assert
+ relationshipProperty.ShouldNotBeNull();
+
+ PropertyInfo? dataProperty = relationshipProperty.PropertyType.GetProperty("Data");
+ dataProperty.ShouldNotBeNull();
+ dataProperty.Should().HaveNullabilityState(expectedState);
+ }
+}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/UpdateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/UpdateResourceTests.cs
similarity index 62%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/UpdateResourceTests.cs
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/UpdateResourceTests.cs
index 68e71e97b9..0429245150 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/UpdateResourceTests.cs
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/UpdateResourceTests.cs
@@ -3,25 +3,24 @@
using FluentAssertions;
using FluentAssertions.Specialized;
using Newtonsoft.Json;
-using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled.GeneratedCode;
+using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOff.GeneratedCode;
using TestBuildingBlocks;
using Xunit;
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationDisabled;
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOff;
-public sealed class UpdateResourceTests
+public sealed class UpdateResourceTests : BaseOpenApiClientTests
{
- private readonly ResourceFieldValidationFakers _fakers = new();
+ private readonly NrtOffMsvOffFakers _fakers = new();
[Fact]
- public async Task Cannot_exclude_id()
+ public async Task Cannot_omit_Id()
{
// Arrange
var requestDocument = new ResourcePatchRequestDocument
{
Data = new ResourceDataInPatchRequest
{
- Id = "1",
Attributes = _fakers.PatchAttributes.Generate(),
Relationships = new ResourceRelationshipsInPatchRequest
{
@@ -33,17 +32,13 @@ public async Task Cannot_exclude_id()
}
};
- ResourceDataInPatchRequest emptyDataObject = new();
- requestDocument.Data.Id = emptyDataObject.Id;
-
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
// Act
- Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(999, requestDocument));
+ Func action = async () => await apiClient.PatchResourceAsync(Unknown.TypedId.Int32, requestDocument);
// Assert
- await action.Should().ThrowAsync();
ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
JsonSerializationException exception = assertion.Subject.Single();
@@ -57,7 +52,7 @@ public async Task Cannot_exclude_id()
[InlineData(nameof(ResourceAttributesInPatchRequest.RequiredValueType), "requiredValueType")]
[InlineData(nameof(ResourceAttributesInPatchRequest.NullableValueType), "nullableValueType")]
[InlineData(nameof(ResourceAttributesInPatchRequest.RequiredNullableValueType), "requiredNullableValueType")]
- public async Task Can_exclude_attribute(string attributePropertyName, string jsonPropertyName)
+ public async Task Can_omit_attribute(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePatchRequestDocument
@@ -76,25 +71,22 @@ public async Task Can_exclude_attribute(string attributePropertyName, string jso
}
};
- ResourceAttributesInPatchRequest emptyAttributesObject = new();
- object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName);
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument));
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.attributes").With(attributesObject =>
+ document.Should().ContainPath("data.attributes").With(attributesObject =>
{
- attributesObject.ShouldNotContainPath(jsonPropertyName);
+ attributesObject.Should().NotContainPath(jsonPropertyName);
});
}
@@ -103,7 +95,7 @@ public async Task Can_exclude_attribute(string attributePropertyName, string jso
[InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredToOne), "requiredToOne")]
[InlineData(nameof(ResourceRelationshipsInPatchRequest.ToMany), "toMany")]
[InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredToMany), "requiredToMany")]
- public async Task Can_exclude_relationship(string relationshipPropertyName, string jsonPropertyName)
+ public async Task Can_omit_relationship(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePatchRequestDocument
@@ -122,25 +114,22 @@ public async Task Can_exclude_relationship(string relationshipPropertyName, stri
}
};
- ResourceRelationshipsInPatchRequest emptyRelationshipsObject = new();
- object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName);
- requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument));
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.relationships").With(relationshipsObject =>
+ document.Should().ContainPath("data.relationships").With(relationshipsObject =>
{
- relationshipsObject.ShouldNotContainPath(jsonPropertyName);
+ relationshipsObject.Should().NotContainPath(jsonPropertyName);
});
}
}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/swagger.g.json
similarity index 87%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/swagger.g.json
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/swagger.g.json
index 572f1c7615..c0b2808012 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationDisabled/swagger.g.json
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/swagger.g.json
@@ -5,10 +5,10 @@
"version": "1.0"
},
"paths": {
- "/Resource": {
+ "/resources": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceCollection",
"responses": {
@@ -26,7 +26,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceCollection",
"responses": {
@@ -44,7 +44,7 @@
},
"post": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "postResource",
"requestBody": {
@@ -73,10 +73,10 @@
}
}
},
- "/Resource/{id}": {
+ "/resources/{id}": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResource",
"parameters": [
@@ -105,7 +105,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResource",
"parameters": [
@@ -134,7 +134,7 @@
},
"patch": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "patchResource",
"parameters": [
@@ -175,7 +175,7 @@
},
"delete": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "deleteResource",
"parameters": [
@@ -196,10 +196,10 @@
}
}
},
- "/Resource/{id}/requiredToMany": {
+ "/resources/{id}/requiredToMany": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceRequiredToMany",
"parameters": [
@@ -219,7 +219,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyCollectionResponseDocument"
}
}
}
@@ -228,7 +228,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceRequiredToMany",
"parameters": [
@@ -248,7 +248,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyCollectionResponseDocument"
}
}
}
@@ -256,10 +256,10 @@
}
}
},
- "/Resource/{id}/relationships/requiredToMany": {
+ "/resources/{id}/relationships/requiredToMany": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceRequiredToManyRelationship",
"parameters": [
@@ -279,7 +279,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyIdentifierCollectionResponseDocument"
}
}
}
@@ -288,7 +288,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceRequiredToManyRelationship",
"parameters": [
@@ -308,7 +308,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyIdentifierCollectionResponseDocument"
}
}
}
@@ -317,7 +317,7 @@
},
"post": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "postResourceRequiredToManyRelationship",
"parameters": [
@@ -335,7 +335,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
}
}
@@ -348,7 +348,7 @@
},
"patch": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "patchResourceRequiredToManyRelationship",
"parameters": [
@@ -366,7 +366,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
}
}
@@ -379,7 +379,7 @@
},
"delete": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "deleteResourceRequiredToManyRelationship",
"parameters": [
@@ -397,7 +397,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
}
}
@@ -409,10 +409,10 @@
}
}
},
- "/Resource/{id}/requiredToOne": {
+ "/resources/{id}/requiredToOne": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceRequiredToOne",
"parameters": [
@@ -432,7 +432,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument"
+ "$ref": "#/components/schemas/nullableEmptySecondaryResponseDocument"
}
}
}
@@ -441,7 +441,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceRequiredToOne",
"parameters": [
@@ -461,7 +461,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument"
+ "$ref": "#/components/schemas/nullableEmptySecondaryResponseDocument"
}
}
}
@@ -469,10 +469,10 @@
}
}
},
- "/Resource/{id}/relationships/requiredToOne": {
+ "/resources/{id}/relationships/requiredToOne": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceRequiredToOneRelationship",
"parameters": [
@@ -492,7 +492,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument"
+ "$ref": "#/components/schemas/nullableEmptyIdentifierResponseDocument"
}
}
}
@@ -501,7 +501,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceRequiredToOneRelationship",
"parameters": [
@@ -521,7 +521,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument"
+ "$ref": "#/components/schemas/nullableEmptyIdentifierResponseDocument"
}
}
}
@@ -530,7 +530,7 @@
},
"patch": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "patchResourceRequiredToOneRelationship",
"parameters": [
@@ -548,7 +548,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest"
+ "$ref": "#/components/schemas/nullableToOneEmptyInRequest"
}
}
}
@@ -560,10 +560,10 @@
}
}
},
- "/Resource/{id}/toMany": {
+ "/resources/{id}/toMany": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceToMany",
"parameters": [
@@ -583,7 +583,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyCollectionResponseDocument"
}
}
}
@@ -592,7 +592,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceToMany",
"parameters": [
@@ -612,7 +612,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyCollectionResponseDocument"
}
}
}
@@ -620,10 +620,10 @@
}
}
},
- "/Resource/{id}/relationships/toMany": {
+ "/resources/{id}/relationships/toMany": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceToManyRelationship",
"parameters": [
@@ -643,7 +643,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyIdentifierCollectionResponseDocument"
}
}
}
@@ -652,7 +652,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceToManyRelationship",
"parameters": [
@@ -672,7 +672,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyIdentifierCollectionResponseDocument"
}
}
}
@@ -681,7 +681,7 @@
},
"post": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "postResourceToManyRelationship",
"parameters": [
@@ -699,7 +699,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
}
}
@@ -712,7 +712,7 @@
},
"patch": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "patchResourceToManyRelationship",
"parameters": [
@@ -730,7 +730,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
}
}
@@ -743,7 +743,7 @@
},
"delete": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "deleteResourceToManyRelationship",
"parameters": [
@@ -761,7 +761,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
}
}
@@ -773,10 +773,10 @@
}
}
},
- "/Resource/{id}/toOne": {
+ "/resources/{id}/toOne": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceToOne",
"parameters": [
@@ -796,7 +796,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument"
+ "$ref": "#/components/schemas/nullableEmptySecondaryResponseDocument"
}
}
}
@@ -805,7 +805,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceToOne",
"parameters": [
@@ -825,7 +825,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument"
+ "$ref": "#/components/schemas/nullableEmptySecondaryResponseDocument"
}
}
}
@@ -833,10 +833,10 @@
}
}
},
- "/Resource/{id}/relationships/toOne": {
+ "/resources/{id}/relationships/toOne": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceToOneRelationship",
"parameters": [
@@ -856,7 +856,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument"
+ "$ref": "#/components/schemas/nullableEmptyIdentifierResponseDocument"
}
}
}
@@ -865,7 +865,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceToOneRelationship",
"parameters": [
@@ -885,7 +885,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument"
+ "$ref": "#/components/schemas/nullableEmptyIdentifierResponseDocument"
}
}
}
@@ -894,7 +894,7 @@
},
"patch": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "patchResourceToOneRelationship",
"parameters": [
@@ -912,7 +912,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest"
+ "$ref": "#/components/schemas/nullableToOneEmptyInRequest"
}
}
}
@@ -927,7 +927,7 @@
},
"components": {
"schemas": {
- "emptyResourceCollectionResponseDocument": {
+ "emptyCollectionResponseDocument": {
"required": [
"data",
"links"
@@ -937,7 +937,7 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/emptyResourceDataInResponse"
+ "$ref": "#/components/schemas/emptyDataInResponse"
}
},
"meta": {
@@ -953,7 +953,7 @@
},
"additionalProperties": false
},
- "emptyResourceDataInResponse": {
+ "emptyDataInResponse": {
"required": [
"id",
"links",
@@ -962,7 +962,7 @@
"type": "object",
"properties": {
"type": {
- "$ref": "#/components/schemas/emptyResourceResourceType"
+ "$ref": "#/components/schemas/emptyResourceType"
},
"id": {
"minLength": 1,
@@ -978,7 +978,7 @@
},
"additionalProperties": false
},
- "emptyResourceIdentifier": {
+ "emptyIdentifier": {
"required": [
"id",
"type"
@@ -986,7 +986,7 @@
"type": "object",
"properties": {
"type": {
- "$ref": "#/components/schemas/emptyResourceResourceType"
+ "$ref": "#/components/schemas/emptyResourceType"
},
"id": {
"minLength": 1,
@@ -995,7 +995,7 @@
},
"additionalProperties": false
},
- "emptyResourceIdentifierCollectionResponseDocument": {
+ "emptyIdentifierCollectionResponseDocument": {
"required": [
"data",
"links"
@@ -1005,7 +1005,7 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
}
},
"meta": {
@@ -1021,9 +1021,9 @@
},
"additionalProperties": false
},
- "emptyResourceResourceType": {
+ "emptyResourceType": {
"enum": [
- "emptyResources"
+ "empties"
],
"type": "string"
},
@@ -1208,7 +1208,7 @@
},
"nullable": true
},
- "nullableEmptyResourceIdentifierResponseDocument": {
+ "nullableEmptyIdentifierResponseDocument": {
"required": [
"data",
"links"
@@ -1218,7 +1218,7 @@
"data": {
"oneOf": [
{
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
},
{
"$ref": "#/components/schemas/nullValue"
@@ -1238,7 +1238,7 @@
},
"additionalProperties": false
},
- "nullableEmptyResourceSecondaryResponseDocument": {
+ "nullableEmptySecondaryResponseDocument": {
"required": [
"data",
"links"
@@ -1248,7 +1248,7 @@
"data": {
"oneOf": [
{
- "$ref": "#/components/schemas/emptyResourceDataInResponse"
+ "$ref": "#/components/schemas/emptyDataInResponse"
},
{
"$ref": "#/components/schemas/nullValue"
@@ -1268,7 +1268,7 @@
},
"additionalProperties": false
},
- "nullableToOneEmptyResourceInRequest": {
+ "nullableToOneEmptyInRequest": {
"required": [
"data"
],
@@ -1277,7 +1277,7 @@
"data": {
"oneOf": [
{
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
},
{
"$ref": "#/components/schemas/nullValue"
@@ -1287,7 +1287,7 @@
},
"additionalProperties": false
},
- "nullableToOneEmptyResourceInResponse": {
+ "nullableToOneEmptyInResponse": {
"required": [
"links"
],
@@ -1296,7 +1296,7 @@
"data": {
"oneOf": [
{
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
},
{
"$ref": "#/components/schemas/nullValue"
@@ -1571,16 +1571,16 @@
"type": "object",
"properties": {
"toOne": {
- "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest"
+ "$ref": "#/components/schemas/nullableToOneEmptyInRequest"
},
"requiredToOne": {
- "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest"
+ "$ref": "#/components/schemas/nullableToOneEmptyInRequest"
},
"toMany": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
},
"requiredToMany": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
},
"additionalProperties": false
@@ -1593,16 +1593,16 @@
"type": "object",
"properties": {
"toOne": {
- "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest"
+ "$ref": "#/components/schemas/nullableToOneEmptyInRequest"
},
"requiredToOne": {
- "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest"
+ "$ref": "#/components/schemas/nullableToOneEmptyInRequest"
},
"toMany": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
},
"requiredToMany": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
},
"additionalProperties": false
@@ -1615,27 +1615,27 @@
"type": "object",
"properties": {
"toOne": {
- "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse"
+ "$ref": "#/components/schemas/nullableToOneEmptyInResponse"
},
"requiredToOne": {
- "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse"
+ "$ref": "#/components/schemas/nullableToOneEmptyInResponse"
},
"toMany": {
- "$ref": "#/components/schemas/toManyEmptyResourceInResponse"
+ "$ref": "#/components/schemas/toManyEmptyInResponse"
},
"requiredToMany": {
- "$ref": "#/components/schemas/toManyEmptyResourceInResponse"
+ "$ref": "#/components/schemas/toManyEmptyInResponse"
}
},
"additionalProperties": false
},
"resourceResourceType": {
"enum": [
- "Resource"
+ "resources"
],
"type": "string"
},
- "toManyEmptyResourceInRequest": {
+ "toManyEmptyInRequest": {
"required": [
"data"
],
@@ -1644,13 +1644,13 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
}
}
},
"additionalProperties": false
},
- "toManyEmptyResourceInResponse": {
+ "toManyEmptyInResponse": {
"required": [
"links"
],
@@ -1659,7 +1659,7 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
}
},
"links": {
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/CreateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/CreateResourceTests.cs
similarity index 55%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/CreateResourceTests.cs
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/CreateResourceTests.cs
index f3a5130486..806eb82d4a 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/CreateResourceTests.cs
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/CreateResourceTests.cs
@@ -4,15 +4,15 @@
using FluentAssertions;
using FluentAssertions.Specialized;
using Newtonsoft.Json;
-using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode;
+using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOn.GeneratedCode;
using TestBuildingBlocks;
using Xunit;
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled;
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOn;
-public sealed class CreateResourceTests : OpenApiClientTests
+public sealed class CreateResourceTests : BaseOpenApiClientTests
{
- private readonly ResourceFieldValidationFakers _fakers = new();
+ private readonly NrtOffMsvOnFakers _fakers = new();
[Theory]
[InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), "referenceType")]
@@ -35,26 +35,25 @@ public async Task Can_clear_attribute(string attributePropertyName, string jsonP
}
};
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, null);
+ SetPropertyToDefaultValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
Expression> includeAttributeSelector =
- CreateIncludedAttributeSelector(attributePropertyName);
+ CreateAttributeSelectorFor(attributePropertyName);
- using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.attributes").With(attributesObject =>
+ document.Should().ContainPath("data.attributes").With(attributesObject =>
{
- attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null));
+ attributesObject.Should().ContainPath(jsonPropertyName).With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null));
});
}
@@ -62,7 +61,7 @@ public async Task Can_clear_attribute(string attributePropertyName, string jsonP
[InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), "requiredNullableValueType")]
- public async Task Can_set_default_value_to_attribute(string attributePropertyName, string jsonPropertyName)
+ public async Task Can_set_attribute_to_default_value(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -80,33 +79,31 @@ public async Task Can_set_default_value_to_attribute(string attributePropertyNam
}
};
- object? defaultValue = requestDocument.Data.Attributes.GetDefaultValueForProperty(attributePropertyName);
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue);
-
- Expression> includeAttributeSelector =
- CreateIncludedAttributeSelector(attributePropertyName);
+ SetPropertyToDefaultValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ Expression> includeAttributeSelector =
+ CreateAttributeSelectorFor(attributePropertyName);
+
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.attributes").With(attributesObject =>
+ document.Should().ContainPath("data.attributes").With(attributesObject =>
{
- attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ShouldBeInteger(0));
+ attributesObject.Should().ContainPath(jsonPropertyName).With(attribute => attribute.Should().Be(0));
});
}
[Theory]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), "requiredReferenceType")]
- public async Task Cannot_clear_attribute(string clrPropertyName, string jsonPropertyName)
+ public async Task Cannot_clear_attribute(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -124,26 +121,24 @@ public async Task Cannot_clear_attribute(string clrPropertyName, string jsonProp
}
};
- requestDocument.Data.Attributes.SetPropertyValue(clrPropertyName, null);
+ SetPropertyToDefaultValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
Expression> includeAttributeSelector =
- CreateIncludedAttributeSelector(clrPropertyName);
+ CreateAttributeSelectorFor(attributePropertyName);
- using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector))
- {
- // Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector);
- // Assert
- ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
- JsonSerializationException exception = assertion.Subject.Single();
+ // Act
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
- exception.Message.Should().Contain($"Cannot write a null value for property '{jsonPropertyName}'.");
- }
+ // Assert
+ ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
+ JsonSerializationException exception = assertion.Subject.Single();
+
+ exception.Message.Should().Be($"Cannot write a null value for property '{jsonPropertyName}'. Property requires a value. Path 'data.attributes'.");
}
[Theory]
@@ -151,7 +146,7 @@ public async Task Cannot_clear_attribute(string clrPropertyName, string jsonProp
[InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")]
[InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), "nullableValueType")]
- public async Task Can_exclude_attribute(string attributePropertyName, string jsonPropertyName)
+ public async Task Can_omit_attribute(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -169,32 +164,29 @@ public async Task Can_exclude_attribute(string attributePropertyName, string jso
}
};
- ResourceAttributesInPostRequest emptyAttributesObject = new();
- object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName);
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.attributes").With(attributesObject =>
+ document.Should().ContainPath("data.attributes").With(attributesObject =>
{
- attributesObject.ShouldNotContainPath(jsonPropertyName);
+ attributesObject.Should().NotContainPath(jsonPropertyName);
});
}
[Theory]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), "requiredReferenceType")]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), "requiredNullableValueType")]
- public async Task Cannot_exclude_attribute(string attributePropertyName, string jsonPropertyName)
+ public async Task Cannot_omit_attribute(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -212,30 +204,27 @@ public async Task Cannot_exclude_attribute(string attributePropertyName, string
}
};
- ResourceAttributesInPostRequest emptyAttributesObject = new();
- object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName);
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
-
- // Assert
- ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
- InvalidOperationException exception = assertion.Subject.Single();
- exception.Message.Should().Be($"The following property should not be omitted: data.attributes.{jsonPropertyName}.");
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
+
+ // Assert
+ ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
+ InvalidOperationException exception = assertion.Subject.Single();
+
+ exception.Message.Should().Be(
+ $"Required property '{attributePropertyName}' at JSON path 'data.attributes.{jsonPropertyName}' is not set. If sending its default value is intended, include it explicitly.");
}
[Theory]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), nameof(ResourceRelationshipsInPostRequest.ToOne.Data), "toOne")]
- public async Task Can_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName,
- string jsonPropertyName)
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), "toOne")]
+ public async Task Can_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -253,31 +242,28 @@ public async Task Can_clear_relationship_with_partial_attribute_serialization(st
}
};
- object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName);
- relationshipObject!.SetPropertyValue(dataPropertyName, null);
+ SetDataPropertyToNull(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
// Act
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject =>
+ document.Should().ContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject =>
{
relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null);
});
}
[Theory]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), nameof(ResourceRelationshipsInPostRequest.ToOne.Data), "toOne")]
- public async Task Can_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName,
- string jsonPropertyName)
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), "toOne")]
+ public async Task Can_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -295,8 +281,7 @@ public async Task Can_clear_relationship_without_partial_attribute_serialization
}
};
- object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName);
- relationshipObject!.SetPropertyValue(dataPropertyName, null);
+ SetDataPropertyToNull(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
@@ -305,20 +290,19 @@ public async Task Can_clear_relationship_without_partial_attribute_serialization
await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject =>
+ document.Should().ContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject =>
{
relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null);
});
}
[Theory]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), nameof(ResourceRelationshipsInPostRequest.RequiredToOne.Data), "requiredToOne")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")]
- public async Task Cannot_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName,
- string jsonPropertyName)
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), "requiredToOne")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany")]
+ public async Task Cannot_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -336,33 +320,29 @@ public async Task Cannot_clear_relationship_with_partial_attribute_serialization
}
};
- object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName);
- relationshipObject!.SetPropertyValue(dataPropertyName, null);
+ SetDataPropertyToNull(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
- // Assert
- ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
- JsonSerializationException exception = assertion.Subject.Single();
+ // Act
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
- exception.Message.Should()
- .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'.");
- }
+ // Assert
+ ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
+ JsonSerializationException exception = assertion.Subject.Single();
+
+ exception.Message.Should().Be(
+ $"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'.");
}
[Theory]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), nameof(ResourceRelationshipsInPostRequest.RequiredToOne.Data), "requiredToOne")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")]
- public async Task Cannot_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName,
- string jsonPropertyName)
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), "requiredToOne")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany")]
+ public async Task Cannot_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -380,29 +360,27 @@ public async Task Cannot_clear_relationship_without_partial_attribute_serializat
}
};
- object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName);
- relationshipObject!.SetPropertyValue(dataPropertyName, null);
+ SetDataPropertyToNull(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
// Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
// Assert
ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
JsonSerializationException exception = assertion.Subject.Single();
- exception.Message.Should()
- .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'.");
+ exception.Message.Should().Be(
+ $"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'.");
}
[Theory]
[InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), "toOne")]
[InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")]
[InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany")]
- public async Task Can_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
+ public async Task Can_omit_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -420,25 +398,22 @@ public async Task Can_exclude_relationship_with_partial_attribute_serialization(
}
};
- ResourceRelationshipsInPostRequest emptyRelationshipsObject = new();
- object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName);
- requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
// Act
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.relationships").With(relationshipsObject =>
+ document.Should().ContainPath("data.relationships").With(relationshipsObject =>
{
- relationshipsObject.ShouldNotContainPath(jsonPropertyName);
+ relationshipsObject.Should().NotContainPath(jsonPropertyName);
});
}
@@ -446,7 +421,7 @@ public async Task Can_exclude_relationship_with_partial_attribute_serialization(
[InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), "toOne")]
[InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")]
[InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany")]
- public async Task Can_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
+ public async Task Can_omit_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -464,31 +439,26 @@ public async Task Can_exclude_relationship_without_partial_attribute_serializati
}
};
- ResourceRelationshipsInPostRequest emptyRelationshipsObject = new();
- object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName);
- requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.relationships").With(relationshipsObject =>
+ document.Should().ContainPath("data.relationships").With(relationshipsObject =>
{
- relationshipsObject.ShouldNotContainPath(jsonPropertyName);
+ relationshipsObject.Should().NotContainPath(jsonPropertyName);
});
}
[Theory]
[InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), "requiredToOne")]
- public async Task Cannot_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
+ public async Task Cannot_omit_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -506,31 +476,27 @@ public async Task Cannot_exclude_relationship_with_partial_attribute_serializati
}
};
- ResourceRelationshipsInPostRequest emptyRelationshipsObject = new();
- object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName);
- requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
- // Assert
- ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
- JsonSerializationException exception = assertion.Subject.Single();
+ // Assert
+ ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
+ JsonSerializationException exception = assertion.Subject.Single();
- exception.Message.Should()
- .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'.");
- }
+ exception.Message.Should().Be(
+ $"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'.");
}
[Theory]
[InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), "requiredToOne")]
- public async Task Cannot_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
+ public async Task Cannot_omit_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -548,22 +514,19 @@ public async Task Cannot_exclude_relationship_without_partial_attribute_serializ
}
};
- ResourceRelationshipsInPostRequest emptyRelationshipsObject = new();
- object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName);
- requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
// Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
// Assert
ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
JsonSerializationException exception = assertion.Subject.Single();
- exception.Message.Should()
- .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'.");
+ exception.Message.Should().Be(
+ $"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'.");
}
}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/GeneratedCode/NrtOffMsvOnClient.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/GeneratedCode/NrtOffMsvOnClient.cs
similarity index 85%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/GeneratedCode/NrtOffMsvOnClient.cs
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/GeneratedCode/NrtOffMsvOnClient.cs
index 94bb5efc37..a722c5d49b 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/GeneratedCode/NrtOffMsvOnClient.cs
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/GeneratedCode/NrtOffMsvOnClient.cs
@@ -1,7 +1,7 @@
using JsonApiDotNetCore.OpenApi.Client;
using Newtonsoft.Json;
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode;
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOn.GeneratedCode;
internal partial class NrtOffMsvOnClient : JsonApiClient
{
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/NrtOffMsvOnFakers.cs
similarity index 52%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/NrtOffMsvOnFakers.cs
index c032f0c073..14d23cb247 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/NrtOffMsvOnFakers.cs
@@ -1,9 +1,9 @@
using Bogus;
-using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled.GeneratedCode;
+using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOn.GeneratedCode;
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationEnabled;
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOn;
-internal sealed class ResourceFieldValidationFakers
+internal sealed class NrtOffMsvOnFakers
{
private readonly Lazy> _lazyPostAttributesFaker = new(() =>
FakerFactory.Instance.Create());
@@ -11,18 +11,18 @@ internal sealed class ResourceFieldValidationFakers
private readonly Lazy> _lazyPatchAttributesFaker = new(() =>
FakerFactory.Instance.Create());
- private readonly Lazy> _lazyToOneFaker = new(() =>
- FakerFactory.Instance.CreateForObjectWithResourceId());
+ private readonly Lazy> _lazyToOneFaker = new(() =>
+ FakerFactory.Instance.CreateForObjectWithResourceId());
- private readonly Lazy> _lazyNullableToOneFaker = new(() =>
- FakerFactory.Instance.CreateForObjectWithResourceId());
+ private readonly Lazy> _lazyNullableToOneFaker = new(() =>
+ FakerFactory.Instance.CreateForObjectWithResourceId());
- private readonly Lazy> _lazyToManyFaker = new(() =>
- FakerFactory.Instance.CreateForObjectWithResourceId());
+ private readonly Lazy> _lazyToManyFaker = new(() =>
+ FakerFactory.Instance.CreateForObjectWithResourceId());
public Faker PostAttributes => _lazyPostAttributesFaker.Value;
public Faker PatchAttributes => _lazyPatchAttributesFaker.Value;
- public Faker ToOne => _lazyToOneFaker.Value;
- public Faker NullableToOne => _lazyNullableToOneFaker.Value;
- public Faker ToMany => _lazyToManyFaker.Value;
+ public Faker ToOne => _lazyToOneFaker.Value;
+ public Faker NullableToOne => _lazyNullableToOneFaker.Value;
+ public Faker ToMany => _lazyToManyFaker.Value;
}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/NullabilityTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/NullabilityTests.cs
new file mode 100644
index 0000000000..11e082fdb9
--- /dev/null
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/NullabilityTests.cs
@@ -0,0 +1,45 @@
+using System.Reflection;
+using FluentAssertions;
+using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOn.GeneratedCode;
+using TestBuildingBlocks;
+using Xunit;
+
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOn;
+
+public sealed class NullabilityTests
+{
+ [Theory]
+ [InlineData(nameof(ResourceAttributesInPostRequest.ReferenceType), NullabilityState.Unknown)]
+ [InlineData(nameof(ResourceAttributesInPostRequest.RequiredReferenceType), NullabilityState.Unknown)]
+ [InlineData(nameof(ResourceAttributesInPostRequest.ValueType), NullabilityState.NotNull)]
+ [InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), NullabilityState.NotNull)]
+ [InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), NullabilityState.Nullable)]
+ [InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), NullabilityState.NotNull)]
+ public void Nullability_of_generated_attribute_property_is_as_expected(string propertyName, NullabilityState expectedState)
+ {
+ // Act
+ PropertyInfo? property = typeof(ResourceAttributesInPostRequest).GetProperty(propertyName);
+
+ // Assert
+ property.ShouldNotBeNull();
+ property.Should().HaveNullabilityState(expectedState);
+ }
+
+ [Theory]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToOne), NullabilityState.Unknown)]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToOne), NullabilityState.Unknown)]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), NullabilityState.Unknown)]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), NullabilityState.Unknown)]
+ public void Nullability_of_generated_relationship_property_is_as_expected(string propertyName, NullabilityState expectedState)
+ {
+ // Act
+ PropertyInfo? relationshipProperty = typeof(ResourceRelationshipsInPostRequest).GetProperty(propertyName);
+
+ // Assert
+ relationshipProperty.ShouldNotBeNull();
+
+ PropertyInfo? dataProperty = relationshipProperty.PropertyType.GetProperty("Data");
+ dataProperty.ShouldNotBeNull();
+ dataProperty.Should().HaveNullabilityState(expectedState);
+ }
+}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/UpdateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/UpdateResourceTests.cs
similarity index 62%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/UpdateResourceTests.cs
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/UpdateResourceTests.cs
index 81f54f6dd0..41ce44c4e0 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/UpdateResourceTests.cs
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/UpdateResourceTests.cs
@@ -3,25 +3,24 @@
using FluentAssertions;
using FluentAssertions.Specialized;
using Newtonsoft.Json;
-using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode;
+using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOn.GeneratedCode;
using TestBuildingBlocks;
using Xunit;
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled;
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOn;
-public sealed class UpdateResourceTests
+public sealed class UpdateResourceTests : BaseOpenApiClientTests
{
- private readonly ResourceFieldValidationFakers _fakers = new();
+ private readonly NrtOffMsvOnFakers _fakers = new();
[Fact]
- public async Task Cannot_exclude_id()
+ public async Task Cannot_omit_Id()
{
// Arrange
var requestDocument = new ResourcePatchRequestDocument
{
Data = new ResourceDataInPatchRequest
{
- Id = "1",
Attributes = _fakers.PatchAttributes.Generate(),
Relationships = new ResourceRelationshipsInPatchRequest
{
@@ -33,17 +32,13 @@ public async Task Cannot_exclude_id()
}
};
- ResourceDataInPatchRequest emptyDataObject = new();
- requestDocument.Data.Id = emptyDataObject.Id;
-
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
// Act
- Func action = async () => await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(999, requestDocument));
+ Func action = async () => await apiClient.PatchResourceAsync(Unknown.TypedId.Int32, requestDocument);
// Assert
- await action.Should().ThrowAsync();
ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
JsonSerializationException exception = assertion.Subject.Single();
@@ -57,7 +52,7 @@ public async Task Cannot_exclude_id()
[InlineData(nameof(ResourceAttributesInPatchRequest.RequiredValueType), "requiredValueType")]
[InlineData(nameof(ResourceAttributesInPatchRequest.NullableValueType), "nullableValueType")]
[InlineData(nameof(ResourceAttributesInPatchRequest.RequiredNullableValueType), "requiredNullableValueType")]
- public async Task Can_exclude_attribute(string attributePropertyName, string jsonPropertyName)
+ public async Task Can_omit_attribute(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePatchRequestDocument
@@ -76,25 +71,22 @@ public async Task Can_exclude_attribute(string attributePropertyName, string jso
}
};
- ResourceAttributesInPatchRequest emptyAttributesObject = new();
- object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName);
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument));
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.attributes").With(attributesObject =>
+ document.Should().ContainPath("data.attributes").With(attributesObject =>
{
- attributesObject.ShouldNotContainPath(jsonPropertyName);
+ attributesObject.Should().NotContainPath(jsonPropertyName);
});
}
@@ -103,7 +95,7 @@ public async Task Can_exclude_attribute(string attributePropertyName, string jso
[InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredToOne), "requiredToOne")]
[InlineData(nameof(ResourceRelationshipsInPatchRequest.ToMany), "toMany")]
[InlineData(nameof(ResourceRelationshipsInPatchRequest.RequiredToMany), "requiredToMany")]
- public async Task Can_exclude_relationship(string relationshipPropertyName, string jsonPropertyName)
+ public async Task Can_omit_relationship(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePatchRequestDocument
@@ -122,25 +114,22 @@ public async Task Can_exclude_relationship(string relationshipPropertyName, stri
}
};
- ResourceRelationshipsInPatchRequest emptyRelationshipsObject = new();
- object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName);
- requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOffMsvOnClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument));
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PatchResourceAsync(int.Parse(requestDocument.Data.Id), requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.relationships").With(relationshipsObject =>
+ document.Should().ContainPath("data.relationships").With(relationshipsObject =>
{
- relationshipsObject.ShouldNotContainPath(jsonPropertyName);
+ relationshipsObject.Should().NotContainPath(jsonPropertyName);
});
}
}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/swagger.g.json
similarity index 87%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/swagger.g.json
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/swagger.g.json
index f366cc19e2..cbf832157e 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/swagger.g.json
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/swagger.g.json
@@ -5,10 +5,10 @@
"version": "1.0"
},
"paths": {
- "/Resource": {
+ "/resources": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceCollection",
"responses": {
@@ -26,7 +26,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceCollection",
"responses": {
@@ -44,7 +44,7 @@
},
"post": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "postResource",
"requestBody": {
@@ -73,10 +73,10 @@
}
}
},
- "/Resource/{id}": {
+ "/resources/{id}": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResource",
"parameters": [
@@ -105,7 +105,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResource",
"parameters": [
@@ -134,7 +134,7 @@
},
"patch": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "patchResource",
"parameters": [
@@ -175,7 +175,7 @@
},
"delete": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "deleteResource",
"parameters": [
@@ -196,10 +196,10 @@
}
}
},
- "/Resource/{id}/requiredToMany": {
+ "/resources/{id}/requiredToMany": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceRequiredToMany",
"parameters": [
@@ -219,7 +219,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyCollectionResponseDocument"
}
}
}
@@ -228,7 +228,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceRequiredToMany",
"parameters": [
@@ -248,7 +248,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyCollectionResponseDocument"
}
}
}
@@ -256,10 +256,10 @@
}
}
},
- "/Resource/{id}/relationships/requiredToMany": {
+ "/resources/{id}/relationships/requiredToMany": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceRequiredToManyRelationship",
"parameters": [
@@ -279,7 +279,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyIdentifierCollectionResponseDocument"
}
}
}
@@ -288,7 +288,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceRequiredToManyRelationship",
"parameters": [
@@ -308,7 +308,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyIdentifierCollectionResponseDocument"
}
}
}
@@ -317,7 +317,7 @@
},
"post": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "postResourceRequiredToManyRelationship",
"parameters": [
@@ -335,7 +335,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
}
}
@@ -348,7 +348,7 @@
},
"patch": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "patchResourceRequiredToManyRelationship",
"parameters": [
@@ -366,7 +366,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
}
}
@@ -379,7 +379,7 @@
},
"delete": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "deleteResourceRequiredToManyRelationship",
"parameters": [
@@ -397,7 +397,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
}
}
@@ -409,10 +409,10 @@
}
}
},
- "/Resource/{id}/requiredToOne": {
+ "/resources/{id}/requiredToOne": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceRequiredToOne",
"parameters": [
@@ -432,7 +432,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument"
+ "$ref": "#/components/schemas/emptySecondaryResponseDocument"
}
}
}
@@ -441,7 +441,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceRequiredToOne",
"parameters": [
@@ -461,7 +461,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceSecondaryResponseDocument"
+ "$ref": "#/components/schemas/emptySecondaryResponseDocument"
}
}
}
@@ -469,10 +469,10 @@
}
}
},
- "/Resource/{id}/relationships/requiredToOne": {
+ "/resources/{id}/relationships/requiredToOne": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceRequiredToOneRelationship",
"parameters": [
@@ -492,7 +492,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument"
+ "$ref": "#/components/schemas/emptyIdentifierResponseDocument"
}
}
}
@@ -501,7 +501,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceRequiredToOneRelationship",
"parameters": [
@@ -521,7 +521,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceIdentifierResponseDocument"
+ "$ref": "#/components/schemas/emptyIdentifierResponseDocument"
}
}
}
@@ -530,7 +530,7 @@
},
"patch": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "patchResourceRequiredToOneRelationship",
"parameters": [
@@ -548,7 +548,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toOneEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toOneEmptyInRequest"
}
}
}
@@ -560,10 +560,10 @@
}
}
},
- "/Resource/{id}/toMany": {
+ "/resources/{id}/toMany": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceToMany",
"parameters": [
@@ -583,7 +583,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyCollectionResponseDocument"
}
}
}
@@ -592,7 +592,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceToMany",
"parameters": [
@@ -612,7 +612,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyCollectionResponseDocument"
}
}
}
@@ -620,10 +620,10 @@
}
}
},
- "/Resource/{id}/relationships/toMany": {
+ "/resources/{id}/relationships/toMany": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceToManyRelationship",
"parameters": [
@@ -643,7 +643,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyIdentifierCollectionResponseDocument"
}
}
}
@@ -652,7 +652,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceToManyRelationship",
"parameters": [
@@ -672,7 +672,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/emptyResourceIdentifierCollectionResponseDocument"
+ "$ref": "#/components/schemas/emptyIdentifierCollectionResponseDocument"
}
}
}
@@ -681,7 +681,7 @@
},
"post": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "postResourceToManyRelationship",
"parameters": [
@@ -699,7 +699,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
}
}
@@ -712,7 +712,7 @@
},
"patch": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "patchResourceToManyRelationship",
"parameters": [
@@ -730,7 +730,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
}
}
@@ -743,7 +743,7 @@
},
"delete": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "deleteResourceToManyRelationship",
"parameters": [
@@ -761,7 +761,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
}
}
@@ -773,10 +773,10 @@
}
}
},
- "/Resource/{id}/toOne": {
+ "/resources/{id}/toOne": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceToOne",
"parameters": [
@@ -796,7 +796,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument"
+ "$ref": "#/components/schemas/nullableEmptySecondaryResponseDocument"
}
}
}
@@ -805,7 +805,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceToOne",
"parameters": [
@@ -825,7 +825,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableEmptyResourceSecondaryResponseDocument"
+ "$ref": "#/components/schemas/nullableEmptySecondaryResponseDocument"
}
}
}
@@ -833,10 +833,10 @@
}
}
},
- "/Resource/{id}/relationships/toOne": {
+ "/resources/{id}/relationships/toOne": {
"get": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "getResourceToOneRelationship",
"parameters": [
@@ -856,7 +856,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument"
+ "$ref": "#/components/schemas/nullableEmptyIdentifierResponseDocument"
}
}
}
@@ -865,7 +865,7 @@
},
"head": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "headResourceToOneRelationship",
"parameters": [
@@ -885,7 +885,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableEmptyResourceIdentifierResponseDocument"
+ "$ref": "#/components/schemas/nullableEmptyIdentifierResponseDocument"
}
}
}
@@ -894,7 +894,7 @@
},
"patch": {
"tags": [
- "Resource"
+ "resources"
],
"operationId": "patchResourceToOneRelationship",
"parameters": [
@@ -912,7 +912,7 @@
"content": {
"application/vnd.api+json": {
"schema": {
- "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest"
+ "$ref": "#/components/schemas/nullableToOneEmptyInRequest"
}
}
}
@@ -927,7 +927,7 @@
},
"components": {
"schemas": {
- "emptyResourceCollectionResponseDocument": {
+ "emptyCollectionResponseDocument": {
"required": [
"data",
"links"
@@ -937,7 +937,7 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/emptyResourceDataInResponse"
+ "$ref": "#/components/schemas/emptyDataInResponse"
}
},
"meta": {
@@ -953,7 +953,7 @@
},
"additionalProperties": false
},
- "emptyResourceDataInResponse": {
+ "emptyDataInResponse": {
"required": [
"id",
"links",
@@ -962,7 +962,7 @@
"type": "object",
"properties": {
"type": {
- "$ref": "#/components/schemas/emptyResourceResourceType"
+ "$ref": "#/components/schemas/emptyResourceType"
},
"id": {
"minLength": 1,
@@ -978,7 +978,7 @@
},
"additionalProperties": false
},
- "emptyResourceIdentifier": {
+ "emptyIdentifier": {
"required": [
"id",
"type"
@@ -986,7 +986,7 @@
"type": "object",
"properties": {
"type": {
- "$ref": "#/components/schemas/emptyResourceResourceType"
+ "$ref": "#/components/schemas/emptyResourceType"
},
"id": {
"minLength": 1,
@@ -995,7 +995,7 @@
},
"additionalProperties": false
},
- "emptyResourceIdentifierCollectionResponseDocument": {
+ "emptyIdentifierCollectionResponseDocument": {
"required": [
"data",
"links"
@@ -1005,7 +1005,7 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
}
},
"meta": {
@@ -1021,7 +1021,7 @@
},
"additionalProperties": false
},
- "emptyResourceIdentifierResponseDocument": {
+ "emptyIdentifierResponseDocument": {
"required": [
"data",
"links"
@@ -1029,7 +1029,7 @@
"type": "object",
"properties": {
"data": {
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
},
"meta": {
"type": "object",
@@ -1044,13 +1044,13 @@
},
"additionalProperties": false
},
- "emptyResourceResourceType": {
+ "emptyResourceType": {
"enum": [
- "emptyResources"
+ "empties"
],
"type": "string"
},
- "emptyResourceSecondaryResponseDocument": {
+ "emptySecondaryResponseDocument": {
"required": [
"data",
"links"
@@ -1058,7 +1058,7 @@
"type": "object",
"properties": {
"data": {
- "$ref": "#/components/schemas/emptyResourceDataInResponse"
+ "$ref": "#/components/schemas/emptyDataInResponse"
},
"meta": {
"type": "object",
@@ -1254,7 +1254,7 @@
},
"nullable": true
},
- "nullableEmptyResourceIdentifierResponseDocument": {
+ "nullableEmptyIdentifierResponseDocument": {
"required": [
"data",
"links"
@@ -1264,7 +1264,7 @@
"data": {
"oneOf": [
{
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
},
{
"$ref": "#/components/schemas/nullValue"
@@ -1284,7 +1284,7 @@
},
"additionalProperties": false
},
- "nullableEmptyResourceSecondaryResponseDocument": {
+ "nullableEmptySecondaryResponseDocument": {
"required": [
"data",
"links"
@@ -1294,7 +1294,7 @@
"data": {
"oneOf": [
{
- "$ref": "#/components/schemas/emptyResourceDataInResponse"
+ "$ref": "#/components/schemas/emptyDataInResponse"
},
{
"$ref": "#/components/schemas/nullValue"
@@ -1314,7 +1314,7 @@
},
"additionalProperties": false
},
- "nullableToOneEmptyResourceInRequest": {
+ "nullableToOneEmptyInRequest": {
"required": [
"data"
],
@@ -1323,7 +1323,7 @@
"data": {
"oneOf": [
{
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
},
{
"$ref": "#/components/schemas/nullValue"
@@ -1333,7 +1333,7 @@
},
"additionalProperties": false
},
- "nullableToOneEmptyResourceInResponse": {
+ "nullableToOneEmptyInResponse": {
"required": [
"links"
],
@@ -1342,7 +1342,7 @@
"data": {
"oneOf": [
{
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
},
{
"$ref": "#/components/schemas/nullValue"
@@ -1609,16 +1609,16 @@
"type": "object",
"properties": {
"toOne": {
- "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest"
+ "$ref": "#/components/schemas/nullableToOneEmptyInRequest"
},
"requiredToOne": {
- "$ref": "#/components/schemas/toOneEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toOneEmptyInRequest"
},
"toMany": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
},
"requiredToMany": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
},
"additionalProperties": false
@@ -1630,16 +1630,16 @@
"type": "object",
"properties": {
"toOne": {
- "$ref": "#/components/schemas/nullableToOneEmptyResourceInRequest"
+ "$ref": "#/components/schemas/nullableToOneEmptyInRequest"
},
"requiredToOne": {
- "$ref": "#/components/schemas/toOneEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toOneEmptyInRequest"
},
"toMany": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
},
"requiredToMany": {
- "$ref": "#/components/schemas/toManyEmptyResourceInRequest"
+ "$ref": "#/components/schemas/toManyEmptyInRequest"
}
},
"additionalProperties": false
@@ -1651,27 +1651,27 @@
"type": "object",
"properties": {
"toOne": {
- "$ref": "#/components/schemas/nullableToOneEmptyResourceInResponse"
+ "$ref": "#/components/schemas/nullableToOneEmptyInResponse"
},
"requiredToOne": {
- "$ref": "#/components/schemas/toOneEmptyResourceInResponse"
+ "$ref": "#/components/schemas/toOneEmptyInResponse"
},
"toMany": {
- "$ref": "#/components/schemas/toManyEmptyResourceInResponse"
+ "$ref": "#/components/schemas/toManyEmptyInResponse"
},
"requiredToMany": {
- "$ref": "#/components/schemas/toManyEmptyResourceInResponse"
+ "$ref": "#/components/schemas/toManyEmptyInResponse"
}
},
"additionalProperties": false
},
"resourceResourceType": {
"enum": [
- "Resource"
+ "resources"
],
"type": "string"
},
- "toManyEmptyResourceInRequest": {
+ "toManyEmptyInRequest": {
"required": [
"data"
],
@@ -1680,13 +1680,13 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
}
}
},
"additionalProperties": false
},
- "toManyEmptyResourceInResponse": {
+ "toManyEmptyInResponse": {
"required": [
"links"
],
@@ -1695,7 +1695,7 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
}
},
"links": {
@@ -1708,26 +1708,26 @@
},
"additionalProperties": false
},
- "toOneEmptyResourceInRequest": {
+ "toOneEmptyInRequest": {
"required": [
"data"
],
"type": "object",
"properties": {
"data": {
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
}
},
"additionalProperties": false
},
- "toOneEmptyResourceInResponse": {
+ "toOneEmptyInResponse": {
"required": [
"links"
],
"type": "object",
"properties": {
"data": {
- "$ref": "#/components/schemas/emptyResourceIdentifier"
+ "$ref": "#/components/schemas/emptyIdentifier"
},
"links": {
"$ref": "#/components/schemas/linksInRelationshipObject"
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/CreateResourceTests.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/CreateResourceTests.cs
similarity index 59%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/CreateResourceTests.cs
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/CreateResourceTests.cs
index 0ca3a6fb82..c3b777b726 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/CreateResourceTests.cs
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/CreateResourceTests.cs
@@ -4,15 +4,15 @@
using FluentAssertions;
using FluentAssertions.Specialized;
using Newtonsoft.Json;
-using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled.GeneratedCode;
+using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOn.ModelStateValidationOff.GeneratedCode;
using TestBuildingBlocks;
using Xunit;
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled;
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOn.ModelStateValidationOff;
-public sealed class CreateResourceTests : OpenApiClientTests
+public sealed class CreateResourceTests : BaseOpenApiClientTests
{
- private readonly ResourceFieldValidationFakers _fakers = new();
+ private readonly NrtOnMsvOffFakers _fakers = new();
[Theory]
[InlineData(nameof(ResourceAttributesInPostRequest.NullableReferenceType), "nullableReferenceType")]
@@ -39,33 +39,32 @@ public async Task Can_clear_attribute(string attributePropertyName, string jsonP
}
};
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, null);
+ SetPropertyToDefaultValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
Expression> includeAttributeSelector =
- CreateIncludedAttributeSelector(attributePropertyName);
+ CreateAttributeSelectorFor(attributePropertyName);
- using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.attributes").With(attributesObject =>
+ document.Should().ContainPath("data.attributes").With(attributesObject =>
{
- attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null));
+ attributesObject.Should().ContainPath(jsonPropertyName).With(attribute => attribute.ValueKind.Should().Be(JsonValueKind.Null));
});
}
[Theory]
[InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")]
- public async Task Can_set_default_value_to_attribute(string attributePropertyName, string jsonPropertyName)
+ public async Task Can_set_attribute_to_default_value(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -85,34 +84,32 @@ public async Task Can_set_default_value_to_attribute(string attributePropertyNam
}
};
- object? defaultValue = requestDocument.Data.Attributes.GetDefaultValueForProperty(attributePropertyName);
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue);
-
- Expression> includeAttributeSelector =
- CreateIncludedAttributeSelector(attributePropertyName);
+ SetPropertyToDefaultValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ Expression> includeAttributeSelector =
+ CreateAttributeSelectorFor(attributePropertyName);
+
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.attributes").With(attributesObject =>
+ document.Should().ContainPath("data.attributes").With(attributesObject =>
{
- attributesObject.ShouldContainPath(jsonPropertyName).With(attribute => attribute.ShouldBeInteger(0));
+ attributesObject.Should().ContainPath(jsonPropertyName).With(attribute => attribute.Should().Be(0));
});
}
[Theory]
[InlineData(nameof(ResourceAttributesInPostRequest.NonNullableReferenceType), "nonNullableReferenceType")]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredNonNullableReferenceType), "requiredNonNullableReferenceType")]
- public async Task Cannot_clear_attribute(string clrPropertyName, string jsonPropertyName)
+ public async Task Cannot_clear_attribute(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -132,26 +129,25 @@ public async Task Cannot_clear_attribute(string clrPropertyName, string jsonProp
}
};
- requestDocument.Data.Attributes.SetPropertyValue(clrPropertyName, null);
+ SetPropertyToDefaultValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
Expression> includeAttributeSelector =
- CreateIncludedAttributeSelector(clrPropertyName);
+ CreateAttributeSelectorFor(attributePropertyName);
- using (apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector))
- {
- // Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument, includeAttributeSelector);
- // Assert
- ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
- JsonSerializationException exception = assertion.Subject.Single();
+ // Act
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
- exception.Message.Should().Contain($"Cannot write a null value for property '{jsonPropertyName}'.");
- }
+ // Assert
+ ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
+ JsonSerializationException exception = assertion.Subject.Single();
+
+ exception.Message.Should().StartWith($"Cannot write a null value for property '{jsonPropertyName}'.");
+ exception.Message.Should().EndWith("Path 'data.attributes'.");
}
[Theory]
@@ -159,7 +155,7 @@ public async Task Cannot_clear_attribute(string clrPropertyName, string jsonProp
[InlineData(nameof(ResourceAttributesInPostRequest.NullableReferenceType), "nullableReferenceType")]
[InlineData(nameof(ResourceAttributesInPostRequest.ValueType), "valueType")]
[InlineData(nameof(ResourceAttributesInPostRequest.NullableValueType), "nullableValueType")]
- public async Task Can_exclude_attribute(string attributePropertyName, string jsonPropertyName)
+ public async Task Can_omit_attribute(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -179,25 +175,22 @@ public async Task Can_exclude_attribute(string attributePropertyName, string jso
}
};
- ResourceAttributesInPostRequest emptyAttributesObject = new();
- object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName);
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.attributes").With(attributesObject =>
+ document.Should().ContainPath("data.attributes").With(attributesObject =>
{
- attributesObject.ShouldNotContainPath(jsonPropertyName);
+ attributesObject.Should().NotContainPath(jsonPropertyName);
});
}
@@ -206,7 +199,7 @@ public async Task Can_exclude_attribute(string attributePropertyName, string jso
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableReferenceType), "requiredNullableReferenceType")]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredValueType), "requiredValueType")]
[InlineData(nameof(ResourceAttributesInPostRequest.RequiredNullableValueType), "requiredNullableValueType")]
- public async Task Cannot_exclude_attribute(string attributePropertyName, string jsonPropertyName)
+ public async Task Cannot_omit_attribute(string attributePropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -226,32 +219,28 @@ public async Task Cannot_exclude_attribute(string attributePropertyName, string
}
};
- ResourceAttributesInPostRequest emptyAttributesObject = new();
- object? defaultValue = emptyAttributesObject.GetPropertyValue(attributePropertyName);
- requestDocument.Data.Attributes.SetPropertyValue(attributePropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Attributes, attributePropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
-
- // Assert
- ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
- InvalidOperationException exception = assertion.Subject.Single();
- exception.Message.Should().Be($"The following property should not be omitted: data.attributes.{jsonPropertyName}.");
- }
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
+
+ // Assert
+ ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
+ InvalidOperationException exception = assertion.Subject.Single();
+
+ exception.Message.Should().Be(
+ $"Required property '{attributePropertyName}' at JSON path 'data.attributes.{jsonPropertyName}' is not set. If sending its default value is intended, include it explicitly.");
}
[Theory]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), nameof(ResourceRelationshipsInPostRequest.NullableToOne.Data), "nullableToOne")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne.Data),
- "requiredNullableToOne")]
- public async Task Can_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName,
- string jsonPropertyName)
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), "nullableToOne")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), "requiredNullableToOne")]
+ public async Task Can_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -271,33 +260,29 @@ public async Task Can_clear_relationship_with_partial_attribute_serialization(st
}
};
- object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName);
- relationshipObject!.SetPropertyValue(dataPropertyName, null);
+ SetDataPropertyToNull(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
// Act
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject =>
+ document.Should().ContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject =>
{
relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null);
});
}
[Theory]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), nameof(ResourceRelationshipsInPostRequest.NullableToOne.Data), "nullableToOne")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne.Data),
- "requiredNullableToOne")]
- public async Task Can_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName,
- string jsonPropertyName)
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), "nullableToOne")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNullableToOne), "requiredNullableToOne")]
+ public async Task Can_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -317,8 +302,7 @@ public async Task Can_clear_relationship_without_partial_attribute_serialization
}
};
- object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName);
- relationshipObject!.SetPropertyValue(dataPropertyName, null);
+ SetDataPropertyToNull(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
@@ -327,23 +311,20 @@ public async Task Can_clear_relationship_without_partial_attribute_serialization
await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject =>
+ document.Should().ContainPath($"data.relationships.{jsonPropertyName}.data").With(relationshipDataObject =>
{
relationshipDataObject.ValueKind.Should().Be(JsonValueKind.Null);
});
}
[Theory]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), nameof(ResourceRelationshipsInPostRequest.NonNullableToOne.Data),
- "nonNullableToOne")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne.Data),
- "requiredNonNullableToOne")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")]
- public async Task Cannot_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName,
- string jsonPropertyName)
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), "nonNullableToOne")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), "requiredNonNullableToOne")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany")]
+ public async Task Cannot_clear_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -363,36 +344,30 @@ public async Task Cannot_clear_relationship_with_partial_attribute_serialization
}
};
- object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName);
- relationshipObject!.SetPropertyValue(dataPropertyName, null);
+ SetDataPropertyToNull(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
- // Assert
- ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
- JsonSerializationException exception = assertion.Subject.Single();
+ // Act
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
- exception.Message.Should()
- .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'.");
- }
+ // Assert
+ ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
+ JsonSerializationException exception = assertion.Subject.Single();
+
+ exception.Message.Should().Be(
+ $"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'.");
}
[Theory]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), nameof(ResourceRelationshipsInPostRequest.NonNullableToOne.Data),
- "nonNullableToOne")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne.Data),
- "requiredNonNullableToOne")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), nameof(ResourceRelationshipsInPostRequest.ToMany.Data), "toMany")]
- [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), nameof(ResourceRelationshipsInPostRequest.RequiredToMany.Data), "requiredToMany")]
- public async Task Cannot_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string dataPropertyName,
- string jsonPropertyName)
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), "nonNullableToOne")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), "requiredNonNullableToOne")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")]
+ [InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredToMany), "requiredToMany")]
+ public async Task Cannot_clear_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -412,29 +387,27 @@ public async Task Cannot_clear_relationship_without_partial_attribute_serializat
}
};
- object? relationshipObject = requestDocument.Data.Relationships.GetPropertyValue(relationshipPropertyName);
- relationshipObject!.SetPropertyValue(dataPropertyName, null);
+ SetDataPropertyToNull(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
// Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
// Assert
ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
JsonSerializationException exception = assertion.Subject.Single();
- exception.Message.Should()
- .Be($"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'.");
+ exception.Message.Should().Be(
+ $"Cannot write a null value for property 'data'. Property requires a value. Path 'data.relationships.{jsonPropertyName}'.");
}
[Theory]
[InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), "nonNullableToOne")]
[InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), "nullableToOne")]
[InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")]
- public async Task Can_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
+ public async Task Can_omit_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -454,25 +427,22 @@ public async Task Can_exclude_relationship_with_partial_attribute_serialization(
}
};
- ResourceRelationshipsInPostRequest emptyRelationshipsObject = new();
- object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName);
- requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
// Act
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.relationships").With(relationshipsObject =>
+ document.Should().ContainPath("data.relationships").With(relationshipsObject =>
{
- relationshipsObject.ShouldNotContainPath(jsonPropertyName);
+ relationshipsObject.Should().NotContainPath(jsonPropertyName);
});
}
@@ -480,7 +450,7 @@ public async Task Can_exclude_relationship_with_partial_attribute_serialization(
[InlineData(nameof(ResourceRelationshipsInPostRequest.NonNullableToOne), "nonNullableToOne")]
[InlineData(nameof(ResourceRelationshipsInPostRequest.NullableToOne), "nullableToOne")]
[InlineData(nameof(ResourceRelationshipsInPostRequest.ToMany), "toMany")]
- public async Task Can_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
+ public async Task Can_omit_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -500,31 +470,26 @@ public async Task Can_exclude_relationship_without_partial_attribute_serializati
}
};
- ResourceRelationshipsInPostRequest emptyRelationshipsObject = new();
- object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName);
- requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
- }
+ // Act
+ await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
// Assert
- JsonElement document = wrapper.ParseRequestBody();
+ JsonElement document = wrapper.GetRequestBodyAsJson();
- document.ShouldContainPath("data.relationships").With(relationshipsObject =>
+ document.Should().ContainPath("data.relationships").With(relationshipsObject =>
{
- relationshipsObject.ShouldNotContainPath(jsonPropertyName);
+ relationshipsObject.Should().NotContainPath(jsonPropertyName);
});
}
[Theory]
[InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), "requiredNonNullableToOne")]
- public async Task Cannot_exclude_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
+ public async Task Cannot_omit_relationship_with_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -544,31 +509,27 @@ public async Task Cannot_exclude_relationship_with_partial_attribute_serializati
}
};
- ResourceRelationshipsInPostRequest emptyRelationshipsObject = new();
- object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName);
- requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
- using (apiClient.WithPartialAttributeSerialization(requestDocument))
- {
- // Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
+ using IDisposable _ = apiClient.WithPartialAttributeSerialization(requestDocument);
+
+ // Act
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
- // Assert
- ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
- JsonSerializationException exception = assertion.Subject.Single();
+ // Assert
+ ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
+ JsonSerializationException exception = assertion.Subject.Single();
- exception.Message.Should()
- .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'.");
- }
+ exception.Message.Should().Be(
+ $"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'.");
}
[Theory]
[InlineData(nameof(ResourceRelationshipsInPostRequest.RequiredNonNullableToOne), "requiredNonNullableToOne")]
- public async Task Cannot_exclude_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
+ public async Task Cannot_omit_relationship_without_partial_attribute_serialization(string relationshipPropertyName, string jsonPropertyName)
{
// Arrange
var requestDocument = new ResourcePostRequestDocument
@@ -588,22 +549,19 @@ public async Task Cannot_exclude_relationship_without_partial_attribute_serializ
}
};
- ResourceRelationshipsInPostRequest emptyRelationshipsObject = new();
- object? defaultValue = emptyRelationshipsObject.GetPropertyValue(relationshipPropertyName);
- requestDocument.Data.Relationships.SetPropertyValue(relationshipPropertyName, defaultValue);
+ SetPropertyToInitialValue(requestDocument.Data.Relationships, relationshipPropertyName);
using var wrapper = FakeHttpClientWrapper.Create(HttpStatusCode.NoContent, null);
var apiClient = new NrtOnMsvOffClient(wrapper.HttpClient);
// Act
- Func> action = async () =>
- await ApiResponse.TranslateAsync(async () => await apiClient.PostResourceAsync(requestDocument));
+ Func> action = async () => await apiClient.PostResourceAsync(requestDocument);
// Assert
ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync();
JsonSerializationException exception = assertion.Subject.Single();
- exception.Message.Should()
- .Be($"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'.");
+ exception.Message.Should().Be(
+ $"Cannot write a null value for property 'id'. Property requires a value. Path 'data.relationships.{jsonPropertyName}.data'.");
}
}
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/GeneratedCode/NrtOnMsvOffClient.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/GeneratedCode/NrtOnMsvOffClient.cs
similarity index 85%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/GeneratedCode/NrtOnMsvOffClient.cs
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/GeneratedCode/NrtOnMsvOffClient.cs
index c44cdbe3d7..77b0854984 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesEnabled/ModelStateValidationDisabled/GeneratedCode/NrtOnMsvOffClient.cs
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/GeneratedCode/NrtOnMsvOffClient.cs
@@ -1,7 +1,7 @@
using JsonApiDotNetCore.OpenApi.Client;
using Newtonsoft.Json;
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesEnabled.ModelStateValidationDisabled.GeneratedCode;
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOn.ModelStateValidationOff.GeneratedCode;
internal partial class NrtOnMsvOffClient : JsonApiClient
{
diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/NrtOnMsvOffFakers.cs
similarity index 52%
rename from test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs
rename to test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/NrtOnMsvOffFakers.cs
index e3dbbd0d36..4b6365764e 100644
--- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesDisabled/ModelStateValidationEnabled/ResourceFieldValidationFakers.cs
+++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/NrtOnMsvOffFakers.cs
@@ -1,9 +1,9 @@
using Bogus;
-using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled.GeneratedCode;
+using OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOn.ModelStateValidationOff.GeneratedCode;
-namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesDisabled.ModelStateValidationEnabled;
+namespace OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOn.ModelStateValidationOff;
-internal sealed class ResourceFieldValidationFakers
+internal sealed class NrtOnMsvOffFakers
{
private readonly Lazy> _lazyPostAttributesFaker = new(() =>
FakerFactory.Instance.Create());
@@ -11,18 +11,18 @@ internal sealed class ResourceFieldValidationFakers
private readonly Lazy