Skip to content

Commit b33a19d

Browse files
authored
✨ add key-aware decoding to the query string parser (#9)
1 parent b01db9f commit b33a19d

File tree

6 files changed

+734
-32
lines changed

6 files changed

+734
-32
lines changed

QsNet.Tests/DecodeOptionsTests.cs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System;
2+
using System.Collections.Generic;
13
using System.Text;
24
using FluentAssertions;
35
using QsNet.Enums;
@@ -108,4 +110,111 @@ public void CopyWith_WithModifications_ShouldReturnModifiedOptions()
108110
newOptions.ParseLists.Should().BeTrue();
109111
newOptions.StrictNullHandling.Should().BeFalse();
110112
}
113+
114+
[Fact]
115+
public void DecodeKey_ShouldThrow_When_DecodeDotInKeysTrue_And_AllowDotsFalse()
116+
{
117+
var options = new DecodeOptions
118+
{
119+
AllowDots = false,
120+
DecodeDotInKeys = true
121+
};
122+
123+
Action act = () => options.DecodeKey("a%2Eb", Encoding.UTF8);
124+
act.Should().Throw<ArgumentException>()
125+
.Where(e => e.Message.Contains("decodeDotInKeys", StringComparison.OrdinalIgnoreCase)
126+
&& e.Message.Contains("allowDots", StringComparison.OrdinalIgnoreCase));
127+
}
128+
129+
[Fact]
130+
public void DecodeKey_DecodesPercentSequences_LikeValues()
131+
{
132+
var options = new DecodeOptions
133+
{
134+
AllowDots = true,
135+
DecodeDotInKeys = false
136+
};
137+
138+
options.DecodeKey("a%2Eb", Encoding.UTF8).Should().Be("a.b");
139+
options.DecodeKey("a%2eb", Encoding.UTF8).Should().Be("a.b");
140+
}
141+
142+
[Fact]
143+
public void DecodeValue_DecodesPercentSequences_Normally()
144+
{
145+
var options = new DecodeOptions();
146+
options.DecodeValue("%2E", Encoding.UTF8).Should().Be(".");
147+
}
148+
149+
[Fact]
150+
public void DecoderWithKind_IsUsed_For_Key_And_Value()
151+
{
152+
var calls = new List<(string? s, DecodeKind kind)>();
153+
var options = new DecodeOptions
154+
{
155+
DecoderWithKind = (s, enc, kind) =>
156+
{
157+
calls.Add((s, kind));
158+
return s;
159+
}
160+
};
161+
162+
options.DecodeKey("x", Encoding.UTF8).Should().Be("x");
163+
options.DecodeValue("y", Encoding.UTF8).Should().Be("y");
164+
165+
calls.Should().HaveCount(2);
166+
calls[0].kind.Should().Be(DecodeKind.Key);
167+
calls[0].s.Should().Be("x");
168+
calls[1].kind.Should().Be(DecodeKind.Value);
169+
calls[1].s.Should().Be("y");
170+
}
171+
172+
[Fact]
173+
public void DecoderWithKind_NullReturn_IsHonored_NoFallback()
174+
{
175+
var options = new DecodeOptions
176+
{
177+
DecoderWithKind = (s, enc, kind) => null
178+
};
179+
180+
options.DecodeValue("foo", Encoding.UTF8).Should().BeNull();
181+
options.DecodeKey("bar", Encoding.UTF8).Should().BeNull();
182+
}
183+
184+
[Fact]
185+
public void LegacyDecoder_IsUsed_When_NoKindAwareDecoder_IsProvided()
186+
{
187+
var options = new DecodeOptions
188+
{
189+
Decoder = (s, enc) => s is null ? null : s.ToUpperInvariant()
190+
};
191+
192+
options.DecodeValue("abc", Encoding.UTF8).Should().Be("ABC");
193+
// For keys, legacy decoder is also used when no kind-aware decoder is set
194+
options.DecodeKey("a%2Eb", Encoding.UTF8).Should().Be("A%2EB");
195+
}
196+
197+
[Fact]
198+
public void CopyWith_PreservesAndOverrides_Decoders()
199+
{
200+
var original = new DecodeOptions
201+
{
202+
Decoder = (s, enc) => s == null ? null : $"L:{s}",
203+
DecoderWithKind = (s, enc, k) => s == null ? null : $"K:{k}:{s}"
204+
};
205+
206+
// Copy without overrides preserves both decoders
207+
var copy = original.CopyWith();
208+
copy.DecodeValue("v", Encoding.UTF8).Should().Be("K:Value:v");
209+
copy.DecodeKey("k", Encoding.UTF8).Should().Be("K:Key:k");
210+
211+
// Override only the legacy decoder; kind-aware remains
212+
var copy2 = original.CopyWith(decoder: (s, enc) => s == null ? null : $"L2:{s}");
213+
copy2.DecodeValue("v", Encoding.UTF8).Should().Be("K:Value:v"); // still kind-aware takes precedence
214+
215+
// Override kind-aware decoder
216+
var copy3 = original.CopyWith(decoderWithKind: (s, enc, k) => s == null ? null : $"K2:{k}:{s}");
217+
copy3.DecodeValue("v", Encoding.UTF8).Should().Be("K2:Value:v");
218+
copy3.DecodeKey("k", Encoding.UTF8).Should().Be("K2:Key:k");
219+
}
111220
}

0 commit comments

Comments
 (0)