From e9c44c90456ca4132ada498f1ef0c14d142f1471 Mon Sep 17 00:00:00 2001
From: Bart Koelman <10324372+bkoelman@users.noreply.github.com>
Date: Tue, 7 Feb 2023 11:01:08 +0100
Subject: [PATCH 01/58] Increment version to 5.1.3 (used for pre-release builds
from ci)
---
Directory.Build.props | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Directory.Build.props b/Directory.Build.props
index 15a7e94c7c..34807583da 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -6,7 +6,7 @@
7.0.*
4.4.*
2.14.1
- 5.1.2
+ 5.1.3
$(MSBuildThisFileDirectory)CodingGuidelines.ruleset
9999
enable
From 22761fe5fbe5efa101303bdd807a0b0397d904fd Mon Sep 17 00:00:00 2001
From: Bart Koelman <10324372+bkoelman@users.noreply.github.com>
Date: Thu, 9 Feb 2023 12:31:21 +0100
Subject: [PATCH 02/58] Empty commit to trigger cibuild
From 0818619a8684a8c3ef31c9e3699e7b5a4e103b48 Mon Sep 17 00:00:00 2001
From: Bart Koelman <10324372+bkoelman@users.noreply.github.com>
Date: Thu, 9 Feb 2023 13:19:07 +0100
Subject: [PATCH 03/58] Update GIT_ACCESS_TOKEN to use bkoelman instead of
bart-degreed GitHub account
---
appveyor.yml | 2 +-
docs/usage/toc.md | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/appveyor.yml b/appveyor.yml
index ff9191da7c..3328af0009 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -10,7 +10,7 @@ environment:
PGUSER: postgres
PGPASSWORD: Password12!
GIT_ACCESS_TOKEN:
- secure: vw2jhp7V38fTOqphzFgnXtLwHoHRW2zM2K5RJgDAnmkoaIKT6jXLDIfkFdyVz9nJ
+ secure: WPzhuEyDE7yuHeEgLi3RoGJ8we+AHU6nMksbFoWQ0AmI/HJLh4bjOR0Jnnzc6aaG
branches:
only:
diff --git a/docs/usage/toc.md b/docs/usage/toc.md
index c23d8f7308..61d3da8de0 100644
--- a/docs/usage/toc.md
+++ b/docs/usage/toc.md
@@ -23,7 +23,6 @@
# [Errors](errors.md)
# [Metadata](meta.md)
# [Caching](caching.md)
-
# [Common Pitfalls](common-pitfalls.md)
# Extensibility
From 8e63c6851c79041c714b14fa6f63983fdc670c0e Mon Sep 17 00:00:00 2001
From: Bart Koelman <10324372+bkoelman@users.noreply.github.com>
Date: Thu, 9 Feb 2023 13:58:28 +0100
Subject: [PATCH 04/58] Dummy change to trigger docs deployment
---
docs/usage/options.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/usage/options.md b/docs/usage/options.md
index 919642c5c8..3512a9d9f2 100644
--- a/docs/usage/options.md
+++ b/docs/usage/options.md
@@ -42,7 +42,7 @@ If `IncludeTotalResourceCount` is set to `false` (or the inverse relationship is
## Relative Links
-All links are absolute by default. However, you can configure relative links.
+All links are absolute by default. However, you can configure relative links:
```c#
options.UseRelativeLinks = true;
From ac7d3ae179ec29376d06d8a3b7e67de90319f531 Mon Sep 17 00:00:00 2001
From: Bart Koelman <10324372+bkoelman@users.noreply.github.com>
Date: Thu, 16 Feb 2023 00:20:32 +0100
Subject: [PATCH 05/58] Fix formatting
---
test/SourceGeneratorTests/ControllerGenerationTests.cs | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/test/SourceGeneratorTests/ControllerGenerationTests.cs b/test/SourceGeneratorTests/ControllerGenerationTests.cs
index 9f5f9f83d3..7576e79417 100644
--- a/test/SourceGeneratorTests/ControllerGenerationTests.cs
+++ b/test/SourceGeneratorTests/ControllerGenerationTests.cs
@@ -490,9 +490,8 @@ public sealed class Item
GeneratorDriverRunResult runResult = driver.GetRunResult();
- runResult.Should()
- .HaveSingleDiagnostic(
- "(6,21): warning JADNC001: Type 'Item' must implement IIdentifiable when using ResourceAttribute to auto-generate ASP.NET controllers");
+ runResult.Should().HaveSingleDiagnostic(
+ "(6,21): warning JADNC001: Type 'Item' must implement IIdentifiable when using ResourceAttribute to auto-generate ASP.NET controllers");
runResult.Should().NotHaveProducedSourceCode();
}
From 8cad0173fdddccced88dbb8ce2267eb319693e2d Mon Sep 17 00:00:00 2001
From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com>
Date: Thu, 16 Feb 2023 13:06:45 +0100
Subject: [PATCH 06/58] Simplify assertions (ShouldContainKey already checks
for null)
---
.../Meta/TopLevelCountTests.cs | 4 ----
.../ResourceInheritanceReadTests.cs | 24 +++++--------------
test/NoEntityFrameworkTests/WorkItemTests.cs | 1 -
3 files changed, 6 insertions(+), 23 deletions(-)
diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/TopLevelCountTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/TopLevelCountTests.cs
index eee8fa75e3..f9639c99bc 100644
--- a/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/TopLevelCountTests.cs
+++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/TopLevelCountTests.cs
@@ -52,8 +52,6 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
- responseDocument.Meta.ShouldNotBeNull();
-
responseDocument.Meta.ShouldContainKey("total").With(value =>
{
JsonElement element = value.Should().BeOfType().Subject;
@@ -78,8 +76,6 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
- responseDocument.Meta.ShouldNotBeNull();
-
responseDocument.Meta.ShouldContainKey("total").With(value =>
{
JsonElement element = value.Should().BeOfType().Subject;
diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceReadTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceReadTests.cs
index ebca28dac9..ee974e0756 100644
--- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceReadTests.cs
+++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceReadTests.cs
@@ -614,9 +614,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
resource.Links.ShouldNotBeNull();
resource.Links.Self.Should().Be($"/chromeWheels/{resource.Id}");
- resource.Attributes.ShouldHaveCount(2);
- resource.Attributes.ShouldContainKey("radius");
- resource.Attributes.ShouldContainKey("paintColor");
+ resource.Attributes.ShouldOnlyContainKeys("radius", "paintColor");
}
foreach (ResourceObject resource in responseDocument.Data.ManyValue.Where(value => value.Type == "carbonWheels"))
@@ -624,9 +622,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
resource.Links.ShouldNotBeNull();
resource.Links.Self.Should().Be($"/carbonWheels/{resource.Id}");
- resource.Attributes.ShouldHaveCount(2);
- resource.Attributes.ShouldContainKey("radius");
- resource.Attributes.ShouldContainKey("hasTube");
+ resource.Attributes.ShouldOnlyContainKeys("radius", "hasTube");
}
foreach (ResourceObject resource in responseDocument.Data.ManyValue)
@@ -686,9 +682,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
resource.Links.ShouldNotBeNull();
resource.Links.Self.Should().Be($"/chromeWheels/{resource.Id}");
- resource.Attributes.ShouldHaveCount(2);
- resource.Attributes.ShouldContainKey("radius");
- resource.Attributes.ShouldContainKey("paintColor");
+ resource.Attributes.ShouldOnlyContainKeys("radius", "paintColor");
}
foreach (ResourceObject resource in responseDocument.Data.ManyValue.Where(value => value.Type == "carbonWheels"))
@@ -696,9 +690,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
resource.Links.ShouldNotBeNull();
resource.Links.Self.Should().Be($"/carbonWheels/{resource.Id}");
- resource.Attributes.ShouldHaveCount(2);
- resource.Attributes.ShouldContainKey("radius");
- resource.Attributes.ShouldContainKey("hasTube");
+ resource.Attributes.ShouldOnlyContainKeys("radius", "hasTube");
}
foreach (ResourceObject resource in responseDocument.Data.ManyValue)
@@ -752,9 +744,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
resource.Links.ShouldNotBeNull();
resource.Links.Self.Should().Be($"/chromeWheels/{resource.Id}");
- resource.Attributes.ShouldHaveCount(2);
- resource.Attributes.ShouldContainKey("radius");
- resource.Attributes.ShouldContainKey("paintColor");
+ resource.Attributes.ShouldOnlyContainKeys("radius", "paintColor");
}
foreach (ResourceObject resource in responseDocument.Data.ManyValue.Where(value => value.Type == "carbonWheels"))
@@ -762,9 +752,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
resource.Links.ShouldNotBeNull();
resource.Links.Self.Should().Be($"/carbonWheels/{resource.Id}");
- resource.Attributes.ShouldHaveCount(2);
- resource.Attributes.ShouldContainKey("radius");
- resource.Attributes.ShouldContainKey("hasTube");
+ resource.Attributes.ShouldOnlyContainKeys("radius", "hasTube");
}
foreach (ResourceObject resource in responseDocument.Data.ManyValue)
diff --git a/test/NoEntityFrameworkTests/WorkItemTests.cs b/test/NoEntityFrameworkTests/WorkItemTests.cs
index 623f05ed1d..7bf09d35aa 100644
--- a/test/NoEntityFrameworkTests/WorkItemTests.cs
+++ b/test/NoEntityFrameworkTests/WorkItemTests.cs
@@ -119,7 +119,6 @@ public async Task Can_create_WorkItem()
httpResponse.ShouldHaveStatusCode(HttpStatusCode.Created);
responseDocument.Data.SingleValue.ShouldNotBeNull();
- responseDocument.Data.SingleValue.Attributes.ShouldNotBeEmpty();
responseDocument.Data.SingleValue.Attributes.ShouldContainKey("isBlocked").With(value => value.Should().Be(newWorkItem.IsBlocked));
responseDocument.Data.SingleValue.Attributes.ShouldContainKey("title").With(value => value.Should().Be(newWorkItem.Title));
responseDocument.Data.SingleValue.Attributes.ShouldContainKey("durationInHours").With(value => value.Should().Be(newWorkItem.DurationInHours));
From 9f31f922fbce48eada8ccf8fc0375f751549f230 Mon Sep 17 00:00:00 2001
From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com>
Date: Thu, 16 Feb 2023 13:47:55 +0100
Subject: [PATCH 07/58] Fixed: on secondary endpoint, the incoming filter from
query string was not applied when determining total resource count via
inverse relationship
---
.../Queries/Internal/QueryLayerComposer.cs | 4 +-
.../IntegrationTests/Meta/SupportTicket.cs | 3 ++
.../Meta/TopLevelCountTests.cs | 40 +++++++++++++++++--
3 files changed, 40 insertions(+), 7 deletions(-)
diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs
index 4661a5bdda..0a1ff48ca0 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs
@@ -82,13 +82,11 @@ public QueryLayerComposer(IEnumerable constraintProvid
ExpressionInScope[] constraints = _constraintProviders.SelectMany(provider => provider.GetConstraints()).ToArray();
- var secondaryScope = new ResourceFieldChainExpression(hasManyRelationship);
-
// @formatter:wrap_chained_method_calls chop_always
// @formatter:keep_existing_linebreaks true
FilterExpression[] filtersInSecondaryScope = constraints
- .Where(constraint => secondaryScope.Equals(constraint.Scope))
+ .Where(constraint => constraint.Scope == null)
.Select(constraint => constraint.Expression)
.OfType()
.ToArray();
diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/SupportTicket.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/SupportTicket.cs
index 619542ad6d..9ce245e951 100644
--- a/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/SupportTicket.cs
+++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/SupportTicket.cs
@@ -10,4 +10,7 @@ public sealed class SupportTicket : Identifiable
{
[Attr]
public string Description { get; set; } = null!;
+
+ [HasOne]
+ public ProductFamily? ProductFamily { get; set; }
}
diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/TopLevelCountTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/TopLevelCountTests.cs
index eee8fa75e3..a795a15190 100644
--- a/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/TopLevelCountTests.cs
+++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/TopLevelCountTests.cs
@@ -32,19 +32,21 @@ public TopLevelCountTests(IntegrationTestContext,
}
[Fact]
- public async Task Renders_resource_count_for_collection()
+ public async Task Renders_resource_count_for_primary_resources_endpoint_with_filter()
{
// Arrange
- SupportTicket ticket = _fakers.SupportTicket.Generate();
+ List tickets = _fakers.SupportTicket.Generate(2);
+
+ tickets[1].Description = "Update firmware version";
await _testContext.RunOnDatabaseAsync(async dbContext =>
{
await dbContext.ClearTableAsync();
- dbContext.SupportTickets.Add(ticket);
+ dbContext.SupportTickets.AddRange(tickets);
await dbContext.SaveChangesAsync();
});
- const string route = "/supportTickets";
+ const string route = "/supportTickets?filter=startsWith(description,'Update ')";
// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route);
@@ -61,6 +63,36 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
});
}
+ [Fact]
+ public async Task Renders_resource_count_for_secondary_resources_endpoint_with_filter()
+ {
+ // Arrange
+ ProductFamily family = _fakers.ProductFamily.Generate();
+ family.Tickets = _fakers.SupportTicket.Generate(2);
+
+ family.Tickets[1].Description = "Update firmware version";
+
+ await _testContext.RunOnDatabaseAsync(async dbContext =>
+ {
+ dbContext.ProductFamilies.Add(family);
+ await dbContext.SaveChangesAsync();
+ });
+
+ string route = $"/productFamilies/{family.StringId}/tickets?filter=contains(description,'firmware')";
+
+ // Act
+ (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route);
+
+ // Assert
+ httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
+
+ responseDocument.Meta.ShouldContainKey("total").With(value =>
+ {
+ JsonElement element = value.Should().BeOfType().Subject;
+ element.GetInt32().Should().Be(1);
+ });
+ }
+
[Fact]
public async Task Renders_resource_count_for_empty_collection()
{
From 27321093f472db9846340ca1741f633db2acc960 Mon Sep 17 00:00:00 2001
From: Bart Koelman <10324372+bkoelman@users.noreply.github.com>
Date: Sat, 18 Feb 2023 12:00:59 +0100
Subject: [PATCH 08/58] Package updates; fix culture in range parsing (see
https://github.com/maxkoshevoi/DateOnlyTimeOnly.AspNet/issues/16)
---
benchmarks/Benchmarks.csproj | 2 +-
.../IntegrationTests/InputValidation/ModelState/SystemFile.cs | 4 ++--
test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj | 4 ++--
test/TestBuildingBlocks/TestBuildingBlocks.csproj | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks.csproj
index 185f2919ac..23a6876af9 100644
--- a/benchmarks/Benchmarks.csproj
+++ b/benchmarks/Benchmarks.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemFile.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemFile.cs
index 2fa659b6ef..ef90adcc79 100644
--- a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemFile.cs
+++ b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemFile.cs
@@ -22,10 +22,10 @@ public sealed class SystemFile : Identifiable
public long SizeInBytes { get; set; }
[Attr]
- [Range(typeof(DateOnly), "2000-01-01", "2050-01-01")]
+ [Range(typeof(DateOnly), "2000-01-01", "2050-01-01", ParseLimitsInInvariantCulture = true)]
public DateOnly CreatedOn { get; set; }
[Attr]
- [Range(typeof(TimeOnly), "09:00:00", "17:30:00")]
+ [Range(typeof(TimeOnly), "09:00:00", "17:30:00", ParseLimitsInInvariantCulture = true)]
public TimeOnly CreatedAt { get; set; }
}
diff --git a/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj b/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj
index 3bd3632461..8c4822b67a 100644
--- a/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj
+++ b/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj
@@ -1,4 +1,4 @@
-
+
$(TargetFrameworkName)
@@ -11,7 +11,7 @@
-
+
diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj
index 999498ebaa..16dabc8d54 100644
--- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj
+++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj
@@ -10,7 +10,7 @@
-
+
From a7668ae26a268c905e7d3337c154297904a55461 Mon Sep 17 00:00:00 2001
From: Bart Koelman <10324372+bkoelman@users.noreply.github.com>
Date: Thu, 16 Feb 2023 01:41:33 +0100
Subject: [PATCH 09/58] Changes LiteralConstantExpression to contain a typed
constant instead of a string value. This effectively moves the type
resolving/conversion logic from the EF query production phase to the filter
parsing phase. By providing a richer QueryLayer model, it becomes easier to
implement non-relational and non-EF Core-based repositories. And it enables
to produce better errors earlier.
Note we still need to wrap nullable values (see WhereClauseBuilder.ResolveCommonType), due to https://bradwilson.typepad.com/blog/2008/07/creating-nullab.html.
---
.../Expressions/LiteralConstantExpression.cs | 25 ++--
.../Queries/Internal/Parsing/FilterParser.cs | 120 ++++++++++--------
.../Queries/Internal/QueryLayerComposer.cs | 8 +-
.../QueryableBuilding/WhereClauseBuilder.cs | 44 +++----
.../CompositeKeys/CarExpressionRewriter.cs | 7 +-
.../Filtering/FilterDataTypeTests.cs | 7 +-
.../Filtering/FilterOperatorTests.cs | 44 +++++++
7 files changed, 156 insertions(+), 99 deletions(-)
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs
index 578643d5db..e8041f0cf2 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs
@@ -8,13 +8,22 @@ namespace JsonApiDotNetCore.Queries.Expressions;
[PublicAPI]
public class LiteralConstantExpression : IdentifierExpression
{
- public string Value { get; }
+ private readonly string _stringValue;
- public LiteralConstantExpression(string text)
+ public object TypedValue { get; }
+
+ public LiteralConstantExpression(object typedValue)
+ : this(typedValue, typedValue.ToString()!)
+ {
+ }
+
+ public LiteralConstantExpression(object typedValue, string stringValue)
{
- ArgumentGuard.NotNull(text);
+ ArgumentGuard.NotNull(typedValue);
+ ArgumentGuard.NotNull(stringValue);
- Value = text;
+ TypedValue = typedValue;
+ _stringValue = stringValue;
}
public override TResult Accept(QueryExpressionVisitor visitor, TArgument argument)
@@ -24,8 +33,8 @@ public override TResult Accept(QueryExpressionVisitor rightConstantValueConverter;
+
+ if (leftTerm is CountExpression)
+ {
+ rightConstantValueConverter = GetConstantValueConverterForCount();
+ }
+ else if (leftTerm is ResourceFieldChainExpression fieldChain && fieldChain.Fields[^1] is AttrAttribute attribute)
+ {
+ rightConstantValueConverter = GetConstantValueConverterForAttribute(attribute);
+ }
+ else
+ {
+ // This temporary value never survives; it gets discarded during the second pass below.
+ rightConstantValueConverter = _ => 0;
+ }
EatSingleCharacterToken(TokenKind.Comma);
- QueryExpression rightTerm = ParseCountOrConstantOrNullOrField(FieldChainRequirements.EndsInAttribute);
+ QueryExpression rightTerm = ParseCountOrConstantOrNullOrField(FieldChainRequirements.EndsInAttribute, rightConstantValueConverter);
EatSingleCharacterToken(TokenKind.CloseParen);
- if (leftTerm is ResourceFieldChainExpression leftChain)
+ if (leftTerm is ResourceFieldChainExpression leftChain && leftChain.Fields[^1] is RelationshipAttribute && rightTerm is not NullConstantExpression)
{
- if (leftChainRequirements.HasFlag(FieldChainRequirements.EndsInToOne) && rightTerm is not NullConstantExpression)
- {
- // Run another pass over left chain to have it fail when chain ends in relationship.
- OnResolveFieldChain(leftChain.ToString(), FieldChainRequirements.EndsInAttribute);
- }
-
- PropertyInfo leftProperty = leftChain.Fields[^1].Property;
-
- if (leftProperty.Name == nameof(Identifiable