diff --git a/package-versions.props b/package-versions.props index e77a2ae86b..cc85cee892 100644 --- a/package-versions.props +++ b/package-versions.props @@ -40,7 +40,6 @@ 9.0.* 9.0.* - 9.0.0-* @@ -50,6 +49,5 @@ 8.0.* 8.0.* - $(EntityFrameworkCoreVersion) diff --git a/src/Examples/DapperExample/DapperExample.csproj b/src/Examples/DapperExample/DapperExample.csproj index ed7bd358eb..0685dc0343 100644 --- a/src/Examples/DapperExample/DapperExample.csproj +++ b/src/Examples/DapperExample/DapperExample.csproj @@ -16,6 +16,6 @@ - + diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/SelectStatementBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/SelectStatementBuilder.cs index 23a2b7d2d0..0a1e3b5a0d 100644 --- a/src/Examples/DapperExample/TranslationToSql/Builders/SelectStatementBuilder.cs +++ b/src/Examples/DapperExample/TranslationToSql/Builders/SelectStatementBuilder.cs @@ -552,7 +552,7 @@ public override SqlTreeNode VisitResourceFieldChain(ResourceFieldChainExpression throw new JsonApiException(new ErrorObject(HttpStatusCode.BadRequest) { Title = "Sorting or filtering on the requested attribute is unavailable.", - Detail = $"Sorting or filtering on attribute '{attribute.PublicName}' is unavailable because it is unmapped." + Detail = $"Sorting or filtering on attribute '{attribute}' is unavailable because it is unmapped." }); } diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs index 593b0a905d..6f54635827 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs @@ -129,6 +129,11 @@ protected void AssertIsIdentifiable(object? resource) return _publicName ?? (_property != null ? _property.Name : base.ToString()); } + public string ToFullString() + { + return $"{_type?.PublicName}:{ToString()}"; + } + /// public override bool Equals(object? obj) { diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs index 95365ed6a9..a10d6a904c 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs @@ -130,7 +130,7 @@ private static void ValidateAttributesInDerivedType(ResourceType resourceType) if (resourceType.FindAttributeByPublicName(attribute.PublicName) == null) { throw new InvalidConfigurationException( - $"Attribute '{attribute.PublicName}' from base type '{resourceType.BaseType.ClrType}' does not exist in derived type '{resourceType.ClrType}'."); + $"Attribute '{attribute}' from base type '{resourceType.BaseType.ClrType}' does not exist in derived type '{resourceType.ClrType}'."); } } } @@ -142,7 +142,7 @@ private static void ValidateRelationshipsInDerivedType(ResourceType resourceType if (resourceType.FindRelationshipByPublicName(relationship.PublicName) == null) { throw new InvalidConfigurationException( - $"Relationship '{relationship.PublicName}' from base type '{resourceType.BaseType.ClrType}' does not exist in derived type '{resourceType.ClrType}'."); + $"Relationship '{relationship}' from base type '{resourceType.BaseType.ClrType}' does not exist in derived type '{resourceType.ClrType}'."); } } } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs index 306a98d1aa..6b44b7d52f 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs @@ -56,9 +56,9 @@ private string InnerToString(bool toFullString) builder.Append(Keywords.Any); builder.Append('('); - builder.Append(toFullString ? TargetAttribute.ToFullString() : TargetAttribute); + builder.Append(toFullString ? TargetAttribute.ToFullString() : TargetAttribute.ToString()); builder.Append(','); - builder.Append(string.Join(",", Constants.Select(constant => toFullString ? constant.ToFullString() : constant.ToString()).OrderBy(value => value))); + builder.Append(string.Join(',', Constants.Select(constant => toFullString ? constant.ToFullString() : constant.ToString()).OrderBy(value => value))); builder.Append(')'); return builder.ToString(); diff --git a/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs index 5c40039d4e..1d5f381a3b 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs @@ -58,12 +58,12 @@ private string InnerToString(bool toFullString) var builder = new StringBuilder(); builder.Append(Keywords.Has); builder.Append('('); - builder.Append(toFullString ? TargetCollection.ToFullString() : TargetCollection); + builder.Append(toFullString ? TargetCollection.ToFullString() : TargetCollection.ToString()); if (Filter != null) { builder.Append(','); - builder.Append(toFullString ? Filter.ToFullString() : Filter); + builder.Append(toFullString ? Filter.ToFullString() : Filter.ToString()); } builder.Append(')'); diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs index 9e98deefe3..3ee86e6ea2 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs @@ -57,12 +57,12 @@ public override string ToFullString() private string InnerToString(bool toFullString) { var builder = new StringBuilder(); - builder.Append(toFullString ? $"{Relationship.LeftType.PublicName}:{Relationship.PublicName}" : Relationship.PublicName); + builder.Append(toFullString ? $"{Relationship.LeftType}:{Relationship}" : Relationship.ToString()); if (Children.Count > 0) { builder.Append('{'); - builder.Append(string.Join(",", Children.Select(child => toFullString ? child.ToFullString() : child.ToString()).OrderBy(name => name))); + builder.Append(string.Join(',', Children.Select(child => toFullString ? child.ToFullString() : child.ToString()).OrderBy(name => name))); builder.Append('}'); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs index 50b152caf7..71d6bf84c2 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs @@ -50,7 +50,7 @@ public override string ToFullString() private string InnerToString(bool toFullString) { IReadOnlyCollection chains = IncludeChainConverter.Instance.GetRelationshipChains(this); - return string.Join(",", chains.Select(field => toFullString ? field.ToFullString() : field.ToString()).Distinct().OrderBy(name => name)); + return string.Join(',', chains.Select(field => toFullString ? field.ToFullString() : field.ToString()).Distinct().OrderBy(name => name)); } public override bool Equals(object? obj) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IsTypeExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IsTypeExpression.cs index e2f68ee8ec..b8d85c4f6f 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IsTypeExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IsTypeExpression.cs @@ -71,7 +71,7 @@ private string InnerToString(bool toFullString) if (TargetToOneRelationship != null) { - builder.Append(toFullString ? TargetToOneRelationship.ToFullString() : TargetToOneRelationship); + builder.Append(toFullString ? TargetToOneRelationship.ToFullString() : TargetToOneRelationship.ToString()); } builder.Append(','); @@ -80,7 +80,7 @@ private string InnerToString(bool toFullString) if (Child != null) { builder.Append(','); - builder.Append(toFullString ? Child.ToFullString() : Child); + builder.Append(toFullString ? Child.ToFullString() : Child.ToString()); } builder.Append(')'); diff --git a/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs index 47af9f8b74..456526e4af 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs @@ -78,7 +78,7 @@ private string InnerToString(bool toFullString) builder.Append(Operator.ToString().Camelize()); builder.Append('('); - builder.Append(string.Join(",", Terms.Select(term => toFullString ? term.ToFullString() : term.ToString()))); + builder.Append(string.Join(',', Terms.Select(term => toFullString ? term.ToFullString() : term.ToString()))); builder.Append(')'); return builder.ToString(); diff --git a/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs index 390940e9d1..96568b7a67 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs @@ -71,8 +71,8 @@ private string InnerToString(bool toFullString) builder.Append('('); builder.Append(toFullString - ? string.Join(",", TargetAttribute.ToFullString(), TextValue.ToFullString()) - : string.Join(",", TargetAttribute, TextValue)); + ? string.Join(',', TargetAttribute.ToFullString(), TextValue.ToFullString()) + : string.Join(',', TargetAttribute.ToString(), TextValue.ToString())); builder.Append(')'); diff --git a/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs index 2a70ea7d8c..70c3905136 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs @@ -32,12 +32,12 @@ public override TResult Accept(QueryExpressionVisitor element.ToString())); + return string.Join(',', Elements.Select(element => element.ToString())); } public override string ToFullString() { - return string.Join(",", Elements.Select(element => element.ToFullString())); + return string.Join(',', Elements.Select(element => element.ToFullString())); } public override bool Equals(object? obj) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs index 35d5ecc4a1..a34872dc6b 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs @@ -45,12 +45,12 @@ public override TResult Accept(QueryExpressionVisitor field.PublicName)); + return string.Join('.', Fields.Select(field => field.ToString())); } public override string ToFullString() { - return string.Join(".", Fields.Select(field => $"{field.Type.PublicName}:{field.PublicName}")); + return string.Join('.', Fields.Select(field => field.ToFullString())); } public override bool Equals(object? obj) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs index 293c1d9c71..0270be01d8 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs @@ -59,7 +59,7 @@ private string InnerToString(bool toFullString) builder.Append('-'); } - builder.Append(toFullString ? Target.ToFullString() : Target); + builder.Append(toFullString ? Target.ToFullString() : Target.ToString()); return builder.ToString(); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs index d51ab6dff0..68033328aa 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs @@ -43,12 +43,12 @@ public override TResult Accept(QueryExpressionVisitor child.ToString())); + return string.Join(',', Elements.Select(child => child.ToString())); } public override string ToFullString() { - return $"{string.Join(",", Elements.Select(child => child.ToFullString()))}{(IsAutoGenerated ? " (auto-generated)" : "")}"; + return $"{string.Join(',', Elements.Select(child => child.ToFullString()))}{(IsAutoGenerated ? " (auto-generated)" : "")}"; } public override bool Equals(object? obj) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs index e075c3f915..ddf11f2f0a 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs @@ -33,12 +33,12 @@ public override TResult Accept(QueryExpressionVisitor field.PublicName).OrderBy(name => name)); + return string.Join(',', Fields.Select(field => field.ToString()).OrderBy(name => name)); } public override string ToFullString() { - return string.Join(".", Fields.Select(field => $"{field.Type.PublicName}:{field.PublicName}").OrderBy(name => name)); + return string.Join(',', Fields.Select(field => $"{field.ToFullString()}").OrderBy(name => name)); } public override bool Equals(object? obj) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs index fc1e9fb88b..0c84ae956e 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs @@ -49,9 +49,9 @@ private string InnerToString(bool toFullString) builder.Append(','); } - builder.Append(resourceType.PublicName); + builder.Append(resourceType); builder.Append('('); - builder.Append(toFullString ? fieldSet.ToFullString() : fieldSet); + builder.Append(toFullString ? fieldSet.ToFullString() : fieldSet.ToString()); builder.Append(')'); } diff --git a/src/JsonApiDotNetCore/Queries/FieldSelection.cs b/src/JsonApiDotNetCore/Queries/FieldSelection.cs index b929db2c80..4c7ba3c6e8 100644 --- a/src/JsonApiDotNetCore/Queries/FieldSelection.cs +++ b/src/JsonApiDotNetCore/Queries/FieldSelection.cs @@ -34,28 +34,38 @@ public FieldSelectors GetOrCreateSelectors(ResourceType resourceType) } public override string ToString() + { + return InnerToString(false); + } + + public string ToFullString() + { + return InnerToString(true); + } + + private string InnerToString(bool toFullString) { var builder = new StringBuilder(); var writer = new IndentingStringWriter(builder); - WriteSelection(writer); + WriteSelection(writer, toFullString); return builder.ToString(); } - internal void WriteSelection(IndentingStringWriter writer) + internal void WriteSelection(IndentingStringWriter writer, bool toFullString) { using (writer.Indent()) { foreach (ResourceType type in GetResourceTypes()) { writer.WriteLine($"{nameof(FieldSelectors)}<{type.ClrType.Name}>"); - WriterSelectors(writer, type); + WriterSelectors(writer, toFullString, type); } } } - private void WriterSelectors(IndentingStringWriter writer, ResourceType type) + private void WriterSelectors(IndentingStringWriter writer, bool toFullString, ResourceType type) { using (writer.Indent()) { @@ -63,11 +73,12 @@ private void WriterSelectors(IndentingStringWriter writer, ResourceType type) { if (nextLayer == null) { - writer.WriteLine(field.ToString()); + writer.WriteLine(toFullString ? field.ToFullString() : field.ToString()); } else { - nextLayer.WriteLayer(writer, $"{field.PublicName}: "); + string prefix = $"{(toFullString ? field.ToFullString() : field.ToString())}: "; + nextLayer.WriteLayer(writer, toFullString, prefix); } } } diff --git a/src/JsonApiDotNetCore/Queries/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Parsing/FilterParser.cs index f89bf3dc39..914abd7802 100644 --- a/src/JsonApiDotNetCore/Queries/Parsing/FilterParser.cs +++ b/src/JsonApiDotNetCore/Queries/Parsing/FilterParser.cs @@ -452,7 +452,7 @@ private static ResourceType ResolveDerivedType(ResourceType baseType, string der if (derivedType == null) { - throw new QueryParseException($"Resource type '{derivedTypeName}' does not exist or does not derive from '{baseType.PublicName}'.", position); + throw new QueryParseException($"Resource type '{derivedTypeName}' does not exist or does not derive from '{baseType}'.", position); } return derivedType; @@ -571,7 +571,7 @@ protected override void ValidateField(ResourceFieldAttribute field, int position if (field.IsFilterBlocked()) { string kind = field is AttrAttribute ? "attribute" : "relationship"; - throw new QueryParseException($"Filtering on {kind} '{field.PublicName}' is not allowed.", position); + throw new QueryParseException($"Filtering on {kind} '{field}' is not allowed.", position); } } diff --git a/src/JsonApiDotNetCore/Queries/Parsing/SortParser.cs b/src/JsonApiDotNetCore/Queries/Parsing/SortParser.cs index e8df9b4a88..814a3e4c9e 100644 --- a/src/JsonApiDotNetCore/Queries/Parsing/SortParser.cs +++ b/src/JsonApiDotNetCore/Queries/Parsing/SortParser.cs @@ -142,7 +142,7 @@ protected override void ValidateField(ResourceFieldAttribute field, int position if (field is AttrAttribute attribute && !attribute.Capabilities.HasFlag(AttrCapabilities.AllowSort)) { - throw new QueryParseException($"Sorting on attribute '{attribute.PublicName}' is not allowed.", position); + throw new QueryParseException($"Sorting on attribute '{attribute}' is not allowed.", position); } } } diff --git a/src/JsonApiDotNetCore/Queries/Parsing/SparseFieldSetParser.cs b/src/JsonApiDotNetCore/Queries/Parsing/SparseFieldSetParser.cs index 9bf6a89daa..fb49e9c1dd 100644 --- a/src/JsonApiDotNetCore/Queries/Parsing/SparseFieldSetParser.cs +++ b/src/JsonApiDotNetCore/Queries/Parsing/SparseFieldSetParser.cs @@ -55,7 +55,7 @@ protected override void ValidateField(ResourceFieldAttribute field, int position if (field.IsViewBlocked()) { string kind = field is AttrAttribute ? "attribute" : "relationship"; - throw new QueryParseException($"Retrieving the {kind} '{field.PublicName}' is not allowed.", position); + throw new QueryParseException($"Retrieving the {kind} '{field}' is not allowed.", position); } } } diff --git a/src/JsonApiDotNetCore/Queries/QueryLayer.cs b/src/JsonApiDotNetCore/Queries/QueryLayer.cs index 49a9ee92a2..adc4ad2b09 100644 --- a/src/JsonApiDotNetCore/Queries/QueryLayer.cs +++ b/src/JsonApiDotNetCore/Queries/QueryLayer.cs @@ -29,16 +29,26 @@ public QueryLayer(ResourceType resourceType) } public override string ToString() + { + return InnerToString(false); + } + + public string ToFullString() + { + return InnerToString(true); + } + + private string InnerToString(bool toFullString) { var builder = new StringBuilder(); var writer = new IndentingStringWriter(builder); - WriteLayer(writer, null); + WriteLayer(writer, toFullString, null); return builder.ToString(); } - internal void WriteLayer(IndentingStringWriter writer, string? prefix) + internal void WriteLayer(IndentingStringWriter writer, bool toFullString, string? prefix) { writer.WriteLine($"{prefix}{nameof(QueryLayer)}<{ResourceType.ClrType.Name}>"); @@ -46,28 +56,28 @@ internal void WriteLayer(IndentingStringWriter writer, string? prefix) { if (Include is { Elements.Count: > 0 }) { - writer.WriteLine($"{nameof(Include)}: {Include}"); + writer.WriteLine($"{nameof(Include)}: {(toFullString ? Include.ToFullString() : Include.ToString())}"); } if (Filter != null) { - writer.WriteLine($"{nameof(Filter)}: {Filter}"); + writer.WriteLine($"{nameof(Filter)}: {(toFullString ? Filter.ToFullString() : Filter.ToString())}"); } if (Sort != null) { - writer.WriteLine($"{nameof(Sort)}: {Sort}"); + writer.WriteLine($"{nameof(Sort)}: {(toFullString ? Sort.ToFullString() : Sort.ToString())}"); } if (Pagination != null) { - writer.WriteLine($"{nameof(Pagination)}: {Pagination}"); + writer.WriteLine($"{nameof(Pagination)}: {(toFullString ? Pagination.ToFullString() : Pagination.ToString())}"); } if (Selection is { IsEmpty: false }) { writer.WriteLine(nameof(Selection)); - Selection.WriteSelection(writer); + Selection.WriteSelection(writer, toFullString); } } } diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs index d0def4e9cd..27b870edba 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs @@ -86,8 +86,8 @@ private static void AssertIsCompatibleResourceType(ResourceType actual, Resource if (expected != null && !expected.ClrType.IsAssignableFrom(actual.ClrType)) { string message = relationshipName != null - ? $"Type '{actual.PublicName}' is not convertible to type '{expected.PublicName}' of relationship '{relationshipName}'." - : $"Type '{actual.PublicName}' is not convertible to type '{expected.PublicName}'."; + ? $"Type '{actual}' is not convertible to type '{expected}' of relationship '{relationshipName}'." + : $"Type '{actual}' is not convertible to type '{expected}'."; throw new ModelConversionException(state.Position, "Incompatible resource type found.", message, HttpStatusCode.Conflict); } @@ -245,7 +245,7 @@ protected static void AssertIsKnownRelationship([NotNull] RelationshipAttribute? if (relationship == null) { throw new ModelConversionException(state.Position, "Unknown relationship found.", - $"Relationship '{relationshipName}' does not exist on resource type '{resourceType.PublicName}'."); + $"Relationship '{relationshipName}' does not exist on resource type '{resourceType}'."); } } @@ -262,7 +262,7 @@ protected internal static void AssertToManyInAddOrRemoveRelationship(Relationshi ? "Only to-many relationships can be targeted through this operation." : "Only to-many relationships can be targeted through this endpoint."; - throw new ModelConversionException(state.Position, message, $"Relationship '{relationship.PublicName}' is not a to-many relationship.", + throw new ModelConversionException(state.Position, message, $"Relationship '{relationship}' is not a to-many relationship.", HttpStatusCode.Forbidden); } } @@ -294,7 +294,7 @@ private static void AssertSetRelationshipNotBlocked(RelationshipAttribute relati if (relationship.IsSetBlocked()) { throw new ModelConversionException(state.Position, "Relationship cannot be assigned.", - $"The relationship '{relationship.PublicName}' on resource type '{relationship.LeftType.PublicName}' cannot be assigned to."); + $"The relationship '{relationship}' on resource type '{relationship.LeftType}' cannot be assigned to."); } } @@ -303,7 +303,7 @@ private static void AssertAddToRelationshipNotBlocked(HasManyAttribute relations if (!relationship.Capabilities.HasFlag(HasManyCapabilities.AllowAdd)) { throw new ModelConversionException(state.Position, "Relationship cannot be added to.", - $"The relationship '{relationship.PublicName}' on resource type '{relationship.LeftType.PublicName}' cannot be added to."); + $"The relationship '{relationship}' on resource type '{relationship.LeftType}' cannot be added to."); } } @@ -312,7 +312,7 @@ private static void AssertRemoveFromRelationshipNotBlocked(HasManyAttribute rela if (!relationship.Capabilities.HasFlag(HasManyCapabilities.AllowRemove)) { throw new ModelConversionException(state.Position, "Relationship cannot be removed from.", - $"The relationship '{relationship.PublicName}' on resource type '{relationship.LeftType.PublicName}' cannot be removed from."); + $"The relationship '{relationship}' on resource type '{relationship.LeftType}' cannot be removed from."); } } } diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceObjectAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceObjectAdapter.cs index 5f9b4dd05c..71fafe7363 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceObjectAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceObjectAdapter.cs @@ -76,7 +76,7 @@ private static void AssertIsKnownAttribute([NotNull] AttrAttribute? attr, string if (attr == null) { throw new ModelConversionException(state.Position, "Unknown attribute found.", - $"Attribute '{attributeName}' does not exist on resource type '{resourceType.PublicName}'."); + $"Attribute '{attributeName}' does not exist on resource type '{resourceType}'."); } } @@ -101,7 +101,7 @@ private static void AssertSetAttributeInCreateResourceNotBlocked(AttrAttribute a if (state.Request.WriteOperation == WriteOperationKind.CreateResource && !attr.Capabilities.HasFlag(AttrCapabilities.AllowCreate)) { throw new ModelConversionException(state.Position, "Attribute value cannot be assigned when creating resource.", - $"The attribute '{attr.PublicName}' on resource type '{resourceType.PublicName}' cannot be assigned to."); + $"The attribute '{attr}' on resource type '{resourceType}' cannot be assigned to."); } } @@ -110,7 +110,7 @@ private static void AssertSetAttributeInUpdateResourceNotBlocked(AttrAttribute a if (state.Request.WriteOperation == WriteOperationKind.UpdateResource && !attr.Capabilities.HasFlag(AttrCapabilities.AllowChange)) { throw new ModelConversionException(state.Position, "Attribute value cannot be assigned when updating resource.", - $"The attribute '{attr.PublicName}' on resource type '{resourceType.PublicName}' cannot be assigned to."); + $"The attribute '{attr}' on resource type '{resourceType}' cannot be assigned to."); } } @@ -119,7 +119,7 @@ private static void AssertNotReadOnly(AttrAttribute attr, ResourceType resourceT if (attr.Property.SetMethod == null) { throw new ModelConversionException(state.Position, "Attribute is read-only.", - $"Attribute '{attr.PublicName}' on resource type '{resourceType.PublicName}' is read-only."); + $"Attribute '{attr}' on resource type '{resourceType}' is read-only."); } } diff --git a/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs b/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs index 2ded4ae896..7417adc6ff 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs @@ -244,7 +244,7 @@ public override string ToString() } else if (_childrenByRelationship != null) { - builder.Append($", children: {string.Join(',', _childrenByRelationship.Select(pair => $"{pair.Key.PublicName} ({pair.Value.Count})"))}"); + builder.Append($", children: {string.Join(',', _childrenByRelationship.Select(pair => $"{pair.Key} ({pair.Value.Count})"))}"); } return builder.ToString(); diff --git a/test/DapperTests/IntegrationTests/QueryStrings/IncludeTests.cs b/test/DapperTests/IntegrationTests/QueryStrings/IncludeTests.cs index 84625b463f..0aa63dff83 100644 --- a/test/DapperTests/IntegrationTests/QueryStrings/IncludeTests.cs +++ b/test/DapperTests/IntegrationTests/QueryStrings/IncludeTests.cs @@ -79,8 +79,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => value.Should().NotBeNull(); value.Data.ManyValue.Should().HaveCount(2); value.Data.ManyValue.Should().AllSatisfy(resource => resource.Type.Should().Be("tags")); - value.Data.ManyValue[0].Id.Should().Be(todoItems[0].Tags.ElementAt(0).StringId); - value.Data.ManyValue[1].Id.Should().Be(todoItems[0].Tags.ElementAt(1).StringId); + value.Data.ManyValue.Should().ContainSingle(resource => resource.Id == todoItems[0].Tags.ElementAt(0).StringId); + value.Data.ManyValue.Should().ContainSingle(resource => resource.Id == todoItems[0].Tags.ElementAt(1).StringId); }); }); @@ -109,8 +109,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => value.Should().NotBeNull(); value.Data.ManyValue.Should().HaveCount(2); value.Data.ManyValue.Should().AllSatisfy(resource => resource.Type.Should().Be("tags")); - value.Data.ManyValue[0].Id.Should().Be(todoItems[1].Tags.ElementAt(0).StringId); - value.Data.ManyValue[1].Id.Should().Be(todoItems[1].Tags.ElementAt(1).StringId); + value.Data.ManyValue.Should().ContainSingle(resource => resource.Id == todoItems[1].Tags.ElementAt(0).StringId); + value.Data.ManyValue.Should().ContainSingle(resource => resource.Id == todoItems[1].Tags.ElementAt(1).StringId); }); }); diff --git a/test/DapperTests/IntegrationTests/ReadWrite/Relationships/FetchRelationshipTests.cs b/test/DapperTests/IntegrationTests/ReadWrite/Relationships/FetchRelationshipTests.cs index 961f4e9f9c..eeb2c3e786 100644 --- a/test/DapperTests/IntegrationTests/ReadWrite/Relationships/FetchRelationshipTests.cs +++ b/test/DapperTests/IntegrationTests/ReadWrite/Relationships/FetchRelationshipTests.cs @@ -141,8 +141,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Data.ManyValue.Should().HaveCount(2); responseDocument.Data.ManyValue.Should().AllSatisfy(resource => resource.Type.Should().Be("tags")); - responseDocument.Data.ManyValue[0].Id.Should().Be(todoItem.Tags.ElementAt(0).StringId); - responseDocument.Data.ManyValue[1].Id.Should().Be(todoItem.Tags.ElementAt(1).StringId); + responseDocument.Data.ManyValue.Should().ContainSingle(resource => resource.Id == todoItem.Tags.ElementAt(0).StringId); + responseDocument.Data.ManyValue.Should().ContainSingle(resource => resource.Id == todoItem.Tags.ElementAt(1).StringId); responseDocument.Meta.Should().ContainTotal(2); diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Queries/QueryExpressionTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Queries/QueryExpressionTests.cs index c8ed8ec3f9..5b0ae957af 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Queries/QueryExpressionTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Queries/QueryExpressionTests.cs @@ -2,6 +2,7 @@ using FluentAssertions; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Queries; using JsonApiDotNetCore.Queries.Expressions; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; @@ -179,6 +180,104 @@ public void Expressions_can_accept_visitor(QueryExpression left, QueryExpression right.Accept(EmptyQueryExpressionVisitor.Instance, null).Should().BeNull(); } + [Fact] + public void Can_convert_QueryLayer_to_string() + { + // Arrange + QueryLayer queryLayer = TestQueryLayerFactory.Instance.Default(); + + // Act + string text = queryLayer.ToString(); + + // Assert + text.Should().Be(""" + QueryLayer + { + Include: parent.children + Filter: and(contains(text,'example'),not(equals(text,'example'))) + Sort: -count(children) + Pagination: Page number: 2, size: 5 + Selection + { + FieldSelectors + { + text + parent: QueryLayer + { + Selection + { + FieldSelectors + { + text + } + } + } + children: QueryLayer + { + Selection + { + FieldSelectors + { + text + } + } + } + } + } + } + + """); + } + + [Fact] + public void Can_convert_QueryLayer_to_full_string() + { + // Arrange + QueryLayer queryLayer = TestQueryLayerFactory.Instance.Default(); + + // Act + string text = queryLayer.ToFullString(); + + // Assert + text.Should().Be(""" + QueryLayer + { + Include: baseTestResources:parent.baseTestResources:children + Filter: and(contains(baseTestResources:text,'example'),not(equals(baseTestResources:text,'example'))) + Sort: -count(baseTestResources:children) + Pagination: Page number: 2, size: 5 + Selection + { + FieldSelectors + { + derivedTestResources:text + derivedTestResources:parent: QueryLayer + { + Selection + { + FieldSelectors + { + derivedTestResources:text + } + } + } + derivedTestResources:children: QueryLayer + { + Selection + { + FieldSelectors + { + derivedTestResources:text + } + } + } + } + } + } + + """); + } + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] private class BaseTestResource : Identifiable { @@ -202,6 +301,7 @@ private sealed class TestExpressionFactory private readonly AttrAttribute _textAttribute; private readonly RelationshipAttribute _parentRelationship; private readonly RelationshipAttribute _childrenRelationship; + public static TestExpressionFactory Instance { get; } = new(); private TestExpressionFactory() @@ -262,7 +362,7 @@ public LiteralConstantExpression LiteralConstant() public LogicalExpression Logical() { - return new LogicalExpression(LogicalOperator.Or, Comparison(), MatchText()); + return new LogicalExpression(LogicalOperator.And, MatchText(), Not()); } public MatchTextExpression MatchText() @@ -364,4 +464,63 @@ private EmptyQueryExpressionVisitor() { } } + + private sealed class TestQueryLayerFactory + { + public static TestQueryLayerFactory Instance { get; } = new(); + + private TestQueryLayerFactory() + { + } + + public QueryLayer Default() + { + var options = new JsonApiOptions(); + + var builder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance); + builder.Add(); + builder.Add(); + IResourceGraph resourceGraph = builder.Build(); + + ResourceType resourceType = resourceGraph.GetResourceType(); + AttrAttribute textAttribute = resourceType.GetAttributeByPropertyName(nameof(DerivedTestResource.Text)); + RelationshipAttribute parentRelationship = resourceType.GetRelationshipByPropertyName(nameof(DerivedTestResource.Parent)); + RelationshipAttribute childrenRelationship = resourceType.GetRelationshipByPropertyName(nameof(DerivedTestResource.Children)); + + return new QueryLayer(resourceType) + { + Include = TestExpressionFactory.Instance.Include(), + Filter = TestExpressionFactory.Instance.Logical(), + Sort = TestExpressionFactory.Instance.Sort(), + Pagination = TestExpressionFactory.Instance.Pagination(), + Selection = new FieldSelection + { + [resourceType] = new FieldSelectors + { + [textAttribute] = null, + [parentRelationship] = new QueryLayer(resourceType) + { + Selection = new FieldSelection + { + [resourceType] = new FieldSelectors + { + [textAttribute] = null + } + } + }, + [childrenRelationship] = new QueryLayer(resourceType) + { + Selection = new FieldSelection + { + [resourceType] = new FieldSelectors + { + [textAttribute] = null + } + } + } + } + } + }; + } + } }