Skip to content

Commit 6b01035

Browse files
Fixed CursorParser to use '\' as escape instead of ':' (#7962)
Co-authored-by: Michael Staib <[email protected]>
1 parent b55d403 commit 6b01035

File tree

27 files changed

+400
-263
lines changed

27 files changed

+400
-263
lines changed

src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/DataLoaderTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ file static class Extensions
288288
{
289289
public static Snapshot AddSql(this Snapshot snapshot, List<string> queries)
290290
{
291-
snapshot.Add(string.Join("\n", queries), "SQL");
291+
snapshot.Add(string.Join("\n", queries).Replace("@__p_1_startswith", "@__p_1_rewritten"), "SQL");
292292
return snapshot;
293293
}
294294

src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/DataLoaderTests.Filter_With_Filtering.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44

55
```text
66
.param set @__keys_0 '[1]'
7-
.param set @__p_1_startswith 'Product%'
7+
.param set @__p_1_rewritten 'Product%'
88
99
SELECT "p"."Id", "p"."AvailableStock", "p"."BrandId", "p"."Description", "p"."ImageFileName", "p"."MaxStockThreshold", "p"."Name", "p"."OnReorder", "p"."Price", "p"."RestockThreshold", "p"."TypeId"
1010
FROM "Products" AS "p"
1111
WHERE "p"."BrandId" IN (
1212
SELECT "k"."value"
1313
FROM json_each(@__keys_0) AS "k"
14-
) AND "p"."Name" LIKE @__p_1_startswith ESCAPE '\'
14+
) AND "p"."Name" LIKE @__p_1_rewritten ESCAPE '\'
1515
```
1616

1717
## Result

src/HotChocolate/Pagination/src/Pagination.Core/Expressions/CursorParser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace HotChocolate.Pagination.Expressions;
99
/// </summary>
1010
public static class CursorParser
1111
{
12-
private const byte _escape = (byte)':';
12+
private const byte _escape = (byte)'\\';
1313
private const byte _separator = (byte)':';
1414

1515
/// <summary>

src/HotChocolate/Pagination/src/Pagination.Core/Serialization/CursorKeySerializerHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public static class CursorKeySerializerHelper
7373

7474
public static bool TryFormat(object? key, ICursorKeySerializer serializer, Span<byte> buffer, out int written)
7575
{
76-
var success = false;
76+
bool success;
7777

7878
if (key is null)
7979
{
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using System.Linq.Expressions;
2+
using System.Text;
3+
using System.Text.Unicode;
4+
using HotChocolate.Pagination.Expressions;
5+
using HotChocolate.Pagination.Serialization;
6+
7+
namespace HotChocolate.Data;
8+
9+
public class CursorFormatterTests
10+
{
11+
[Fact]
12+
public void Format_Single_Key()
13+
{
14+
// arrange
15+
var entity = new MyClass { Name = "test" };
16+
Expression<Func<MyClass, object>> selector = x => x.Name;
17+
var serializer = new StringCursorKeySerializer();
18+
19+
// act
20+
var result = CursorFormatter.Format(entity, [new CursorKey(selector, serializer)]);
21+
22+
// assert
23+
Assert.Equal("dGVzdA==", result);
24+
Assert.Equal("test", Encoding.UTF8.GetString(Convert.FromBase64String(result)));
25+
}
26+
27+
[Fact]
28+
public void Format_Single_Key_With_Colon()
29+
{
30+
// arrange
31+
var entity = new MyClass { Name = "test:test" };
32+
Expression<Func<MyClass, object>> selector = x => x.Name;
33+
var serializer = new StringCursorKeySerializer();
34+
35+
// act
36+
var result = CursorFormatter.Format(entity, [new CursorKey(selector, serializer)]);
37+
38+
// assert
39+
Assert.Equal("dGVzdFw6dGVzdA==", result);
40+
Assert.Equal("test\\:test", Encoding.UTF8.GetString(Convert.FromBase64String(result)));
41+
}
42+
43+
[Fact]
44+
public void Format_Two_Keys()
45+
{
46+
// arrange
47+
var entity = new MyClass { Name = "test", Description = "description" };
48+
Expression<Func<MyClass, object?>> selector1 = x => x.Name;
49+
Expression<Func<MyClass, object?>> selector2 = x => x.Description;
50+
var serializer = new StringCursorKeySerializer();
51+
52+
// act
53+
var result = CursorFormatter.Format(
54+
entity,
55+
[
56+
new CursorKey(selector1, serializer),
57+
new CursorKey(selector2, serializer)
58+
]);
59+
60+
// assert
61+
Assert.Equal("dGVzdDpkZXNjcmlwdGlvbg==", result);
62+
Assert.Equal("test:description", Encoding.UTF8.GetString(Convert.FromBase64String(result)));
63+
}
64+
65+
[Fact]
66+
public void Format_Two_Keys_With_Colon()
67+
{
68+
// arrange
69+
var entity = new MyClass { Name = "test:345", Description = "description:123" };
70+
Expression<Func<MyClass, object?>> selector1 = x => x.Name;
71+
Expression<Func<MyClass, object?>> selector2 = x => x.Description;
72+
var serializer = new StringCursorKeySerializer();
73+
74+
// act
75+
var result = CursorFormatter.Format(
76+
entity,
77+
[
78+
new CursorKey(selector1, serializer),
79+
new CursorKey(selector2, serializer)
80+
]);
81+
82+
// assert
83+
Assert.Equal("dGVzdFw6MzQ1OmRlc2NyaXB0aW9uXDoxMjM=", result);
84+
Assert.Equal("test\\:345:description\\:123", Encoding.UTF8.GetString(Convert.FromBase64String(result)));
85+
}
86+
87+
[Fact]
88+
public void Format_And_Parse_Two_Keys_With_Colon()
89+
{
90+
// arrange
91+
var entity = new MyClass { Name = "test:345", Description = "description:123" };
92+
Expression<Func<MyClass, object?>> selector1 = x => x.Name;
93+
Expression<Func<MyClass, object?>> selector2 = x => x.Description;
94+
var serializer = new StringCursorKeySerializer();
95+
96+
// act
97+
var formatted = CursorFormatter.Format(
98+
entity,
99+
[
100+
new CursorKey(selector1, serializer),
101+
new CursorKey(selector2, serializer)
102+
]);
103+
var parsed =CursorParser.Parse(
104+
formatted,
105+
[
106+
new CursorKey(selector1, serializer),
107+
new CursorKey(selector2, serializer)
108+
]);
109+
110+
// assert
111+
Assert.Equal("test:345", parsed[0]);
112+
Assert.Equal("description:123", parsed[1]);
113+
}
114+
115+
public class MyClass
116+
{
117+
public string Name { get; set; } = default!;
118+
119+
public string? Description { get; set; } = default!;
120+
}
121+
}

src/HotChocolate/Pagination/test/Pagination.EntityFramework.Tests/CursorKeySerializerHelperTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,20 @@ public static void TryFormat_BufferTooSmall_ReturnsFalse()
141141
Assert.False(success);
142142
Assert.Equal(0, written);
143143
}
144+
145+
[Fact]
146+
public static void String_With_Colon_Format_And_Parse()
147+
{
148+
// arrange
149+
object key = "part1:part2";
150+
Span<byte> buffer = new byte[1024];
151+
152+
// act
153+
CursorKeySerializerHelper.TryFormat(key, _serializer, buffer, out var written);
154+
var parsedString =CursorKeySerializerHelper.Parse(buffer.Slice(0, written), _serializer);
155+
156+
157+
// assert
158+
Assert.Equal(key, parsedString);
159+
}
144160
}

src/HotChocolate/Pagination/test/Pagination.EntityFramework.Tests/PagingHelperIntegrationTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ public async Task GetDefaultPage_With_Nullable_Fallback_SecondPage()
376376
.SetDocument(
377377
"""
378378
{
379-
brandsNullableFallback(first: 2, after: "QnJhbmQxMToxMg==") {
379+
brandsNullableFallback(first: 2, after: "QnJhbmRcOjExOjEy") {
380380
edges {
381381
cursor
382382
}
@@ -1209,7 +1209,7 @@ private static async Task SeedAsync(string connectionString)
12091209
{
12101210
var brand = new Brand
12111211
{
1212-
Name = "Brand" + i,
1212+
Name = "Brand:" + i,
12131213
DisplayName = i % 2 == 0 ? "BrandDisplay" + i : null,
12141214
BrandDetails = new() { Country = new() { Name = "Country" + i } }
12151215
};

src/HotChocolate/Pagination/test/Pagination.EntityFramework.Tests/__snapshots__/IntegrationPagingHelperTests.GetDefaultPage.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,50 +25,50 @@ LIMIT @__p_0
2525
"nodes": [
2626
{
2727
"id": 1,
28-
"name": "Brand0"
28+
"name": "Brand:0"
2929
},
3030
{
3131
"id": 2,
32-
"name": "Brand1"
32+
"name": "Brand:1"
3333
},
3434
{
3535
"id": 11,
36-
"name": "Brand10"
36+
"name": "Brand:10"
3737
},
3838
{
3939
"id": 12,
40-
"name": "Brand11"
40+
"name": "Brand:11"
4141
},
4242
{
4343
"id": 13,
44-
"name": "Brand12"
44+
"name": "Brand:12"
4545
},
4646
{
4747
"id": 14,
48-
"name": "Brand13"
48+
"name": "Brand:13"
4949
},
5050
{
5151
"id": 15,
52-
"name": "Brand14"
52+
"name": "Brand:14"
5353
},
5454
{
5555
"id": 16,
56-
"name": "Brand15"
56+
"name": "Brand:15"
5757
},
5858
{
5959
"id": 17,
60-
"name": "Brand16"
60+
"name": "Brand:16"
6161
},
6262
{
6363
"id": 18,
64-
"name": "Brand17"
64+
"name": "Brand:17"
6565
}
6666
],
6767
"pageInfo": {
6868
"hasNextPage": true,
6969
"hasPreviousPage": false,
70-
"startCursor": "QnJhbmQwOjE=",
71-
"endCursor": "QnJhbmQxNzoxOA=="
70+
"startCursor": "QnJhbmRcOjA6MQ==",
71+
"endCursor": "QnJhbmRcOjE3OjE4"
7272
}
7373
}
7474
}

src/HotChocolate/Pagination/test/Pagination.EntityFramework.Tests/__snapshots__/IntegrationPagingHelperTests.GetDefaultPage2.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,50 +25,50 @@ LIMIT @__p_0
2525
"nodes": [
2626
{
2727
"id": 1,
28-
"name": "Brand0"
28+
"name": "Brand:0"
2929
},
3030
{
3131
"id": 2,
32-
"name": "Brand1"
32+
"name": "Brand:1"
3333
},
3434
{
3535
"id": 11,
36-
"name": "Brand10"
36+
"name": "Brand:10"
3737
},
3838
{
3939
"id": 12,
40-
"name": "Brand11"
40+
"name": "Brand:11"
4141
},
4242
{
4343
"id": 13,
44-
"name": "Brand12"
44+
"name": "Brand:12"
4545
},
4646
{
4747
"id": 14,
48-
"name": "Brand13"
48+
"name": "Brand:13"
4949
},
5050
{
5151
"id": 15,
52-
"name": "Brand14"
52+
"name": "Brand:14"
5353
},
5454
{
5555
"id": 16,
56-
"name": "Brand15"
56+
"name": "Brand:15"
5757
},
5858
{
5959
"id": 17,
60-
"name": "Brand16"
60+
"name": "Brand:16"
6161
},
6262
{
6363
"id": 18,
64-
"name": "Brand17"
64+
"name": "Brand:17"
6565
}
6666
],
6767
"pageInfo": {
6868
"hasNextPage": true,
6969
"hasPreviousPage": false,
70-
"startCursor": "QnJhbmQwOjE=",
71-
"endCursor": "QnJhbmQxNzoxOA=="
70+
"startCursor": "QnJhbmRcOjA6MQ==",
71+
"endCursor": "QnJhbmRcOjE3OjE4"
7272
}
7373
}
7474
}

0 commit comments

Comments
 (0)