Skip to content

Commit 8e1d991

Browse files
author
Bart Koelman
committed
JSON:API spec compliance: do not unescape brackets in response
From https://jsonapi.org/format/1.1/#appendix-query-details-square-brackets: > According to the query parameter serialization rules above, a compliant implementation will percent-encode these square brackets.
1 parent 2e0720b commit 8e1d991

File tree

3 files changed

+22
-26
lines changed

3 files changed

+22
-26
lines changed

src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ private bool ShouldIncludeTopLevelLink(LinkTypes linkType, ResourceType? resourc
114114

115115
private string GetLinkForTopLevelSelf()
116116
{
117+
// Note: in tests, this does not properly escape special characters due to WebApplicationFactory short-circuiting.
117118
return _options.UseRelativeLinks ? HttpContext.Request.GetEncodedPathAndQuery() : HttpContext.Request.GetEncodedUrl();
118119
}
119120

@@ -223,13 +224,7 @@ private string GetQueryStringInPaginationLink(int pageOffset, string? pageSizeVa
223224
parameters[PageNumberParameterName] = pageOffset.ToString();
224225
}
225226

226-
string queryStringValue = QueryString.Create(parameters).Value ?? string.Empty;
227-
return DecodeSpecialCharacters(queryStringValue);
228-
}
229-
230-
private static string DecodeSpecialCharacters(string uri)
231-
{
232-
return uri.Replace("%5B", "[").Replace("%5D", "]").Replace("%27", "'").Replace("%3A", ":");
227+
return QueryString.Create(parameters).Value ?? string.Empty;
233228
}
234229

235230
/// <inheritdoc />

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Pagination/PaginationWithTotalCountTests.cs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
6565

6666
responseDocument.Links.ShouldNotBeNull();
6767
responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}");
68-
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogPosts?page[size]=1");
69-
responseDocument.Links.Last.Should().Be(responseDocument.Links.Self);
68+
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogPosts?page%5Bsize%5D=1");
69+
responseDocument.Links.Last.Should().Be($"{HostPrefix}/blogPosts?page%5Bnumber%5D=2&page%5Bsize%5D=1");
7070
responseDocument.Links.Prev.Should().Be(responseDocument.Links.First);
7171
responseDocument.Links.Next.Should().BeNull();
7272
}
@@ -127,10 +127,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
127127

128128
responseDocument.Links.ShouldNotBeNull();
129129
responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}");
130-
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogs/{blog.StringId}/posts?page[size]=1");
130+
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogs/{blog.StringId}/posts?page%5Bsize%5D=1");
131131
responseDocument.Links.Last.Should().BeNull();
132132
responseDocument.Links.Prev.Should().Be(responseDocument.Links.First);
133-
responseDocument.Links.Next.Should().Be($"{HostPrefix}/blogs/{blog.StringId}/posts?page[number]=3&page[size]=1");
133+
responseDocument.Links.Next.Should().Be($"{HostPrefix}/blogs/{blog.StringId}/posts?page%5Bnumber%5D=3&page%5Bsize%5D=1");
134134
}
135135

136136
[Fact]
@@ -194,8 +194,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
194194

195195
responseDocument.Links.ShouldNotBeNull();
196196
responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}");
197-
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogs?include=posts&page[size]=2,posts:1");
198-
responseDocument.Links.Last.Should().Be($"{HostPrefix}/blogs?include=posts&page[number]=2&page[size]=2,posts:1");
197+
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogs?include=posts&page%5Bsize%5D=2,posts%3A1");
198+
responseDocument.Links.Last.Should().Be($"{HostPrefix}/blogs?include=posts&page%5Bnumber%5D=2&page%5Bsize%5D=2,posts%3A1");
199199
responseDocument.Links.Prev.Should().BeNull();
200200
responseDocument.Links.Next.Should().Be(responseDocument.Links.Last);
201201
}
@@ -260,7 +260,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
260260

261261
responseDocument.Links.ShouldNotBeNull();
262262
responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}");
263-
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogs/{blog.StringId}/relationships/posts?page[size]=1");
263+
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogs/{blog.StringId}/relationships/posts?page%5Bsize%5D=1");
264264
responseDocument.Links.Last.Should().BeNull();
265265
responseDocument.Links.Prev.Should().Be(responseDocument.Links.First);
266266
responseDocument.Links.Next.Should().BeNull();
@@ -302,7 +302,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
302302

303303
responseDocument.Links.ShouldNotBeNull();
304304
responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}");
305-
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogPosts?include=labels&page[size]=labels:1");
305+
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogPosts?include=labels&page%5Bsize%5D=labels%3A1");
306306
responseDocument.Links.Last.Should().Be(responseDocument.Links.First);
307307
responseDocument.Links.Prev.Should().BeNull();
308308
responseDocument.Links.Next.Should().BeNull();
@@ -335,7 +335,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
335335

336336
responseDocument.Links.ShouldNotBeNull();
337337
responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}");
338-
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogPosts/{post.StringId}/relationships/labels?page[size]=1");
338+
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogPosts/{post.StringId}/relationships/labels?page%5Bsize%5D=1");
339339
responseDocument.Links.Last.Should().BeNull();
340340
responseDocument.Links.Prev.Should().Be(responseDocument.Links.First);
341341
responseDocument.Links.Next.Should().BeNull();
@@ -384,8 +384,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
384384

385385
responseDocument.Links.ShouldNotBeNull();
386386
responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}");
387-
responseDocument.Links.First.Should().Be($"{linkPrefix}&page[size]=1,owner.posts:1,owner.posts.comments:1");
388-
responseDocument.Links.Last.Should().Be($"{linkPrefix}&page[size]=1,owner.posts:1,owner.posts.comments:1&page[number]=2");
387+
responseDocument.Links.First.Should().Be($"{linkPrefix}&page%5Bsize%5D=1,owner.posts%3A1,owner.posts.comments%3A1");
388+
responseDocument.Links.Last.Should().Be($"{linkPrefix}&page%5Bsize%5D=1,owner.posts%3A1,owner.posts.comments%3A1&page%5Bnumber%5D=2");
389389
responseDocument.Links.Prev.Should().Be(responseDocument.Links.First);
390390
responseDocument.Links.Next.Should().BeNull();
391391
}
@@ -467,7 +467,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
467467
responseDocument.Links.First.Should().Be(responseDocument.Links.Self);
468468
responseDocument.Links.Last.Should().BeNull();
469469
responseDocument.Links.Prev.Should().BeNull();
470-
responseDocument.Links.Next.Should().Be($"{HostPrefix}/blogs/{blog.StringId}/posts?page[number]=2");
470+
responseDocument.Links.Next.Should().Be($"{HostPrefix}/blogs/{blog.StringId}/posts?page%5Bnumber%5D=2");
471471
}
472472

473473
[Fact]
@@ -587,7 +587,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
587587

588588
static string SetPageNumberInUrl(string url, int pageNumber)
589589
{
590-
return pageNumber != 1 ? $"{url}&page[number]={pageNumber}" : url;
590+
string link = pageNumber != 1 ? $"{url}&page[number]={pageNumber}" : url;
591+
return link.Replace("[", "%5B").Replace("]", "%5D").Replace("'", "%27");
591592
}
592593
}
593594
}

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Pagination/PaginationWithoutTotalCountTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
7878

7979
responseDocument.Links.ShouldNotBeNull();
8080
responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}");
81-
responseDocument.Links.First.Should().Be($"{HostPrefix}{route}");
81+
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogPosts?page%5Bsize%5D=8&foo=bar");
8282
responseDocument.Links.Last.Should().BeNull();
8383
responseDocument.Links.Prev.Should().BeNull();
8484
responseDocument.Links.Next.Should().BeNull();
@@ -137,7 +137,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
137137
responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}");
138138
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogPosts?foo=bar");
139139
responseDocument.Links.Last.Should().BeNull();
140-
responseDocument.Links.Prev.Should().Be($"{HostPrefix}/blogPosts?foo=bar&page[number]=2");
140+
responseDocument.Links.Prev.Should().Be($"{HostPrefix}/blogPosts?foo=bar&page%5Bnumber%5D=2");
141141
responseDocument.Links.Next.Should().BeNull();
142142
}
143143

@@ -168,8 +168,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
168168
responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}");
169169
responseDocument.Links.First.Should().Be($"{HostPrefix}/blogPosts?foo=bar");
170170
responseDocument.Links.Last.Should().BeNull();
171-
responseDocument.Links.Prev.Should().Be($"{HostPrefix}/blogPosts?page[number]=2&foo=bar");
172-
responseDocument.Links.Next.Should().Be($"{HostPrefix}/blogPosts?page[number]=4&foo=bar");
171+
responseDocument.Links.Prev.Should().Be($"{HostPrefix}/blogPosts?page%5Bnumber%5D=2&foo=bar");
172+
responseDocument.Links.Next.Should().Be($"{HostPrefix}/blogPosts?page%5Bnumber%5D=4&foo=bar");
173173
}
174174

175175
[Fact]
@@ -199,8 +199,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
199199
responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}");
200200
responseDocument.Links.First.Should().Be($"{HostPrefix}/webAccounts/{account.StringId}/posts?foo=bar");
201201
responseDocument.Links.Last.Should().BeNull();
202-
responseDocument.Links.Prev.Should().Be($"{HostPrefix}/webAccounts/{account.StringId}/posts?page[number]=2&foo=bar");
203-
responseDocument.Links.Next.Should().Be($"{HostPrefix}/webAccounts/{account.StringId}/posts?page[number]=4&foo=bar");
202+
responseDocument.Links.Prev.Should().Be($"{HostPrefix}/webAccounts/{account.StringId}/posts?page%5Bnumber%5D=2&foo=bar");
203+
responseDocument.Links.Next.Should().Be($"{HostPrefix}/webAccounts/{account.StringId}/posts?page%5Bnumber%5D=4&foo=bar");
204204
}
205205
}
206206
}

0 commit comments

Comments
 (0)