Skip to content

Deepen escape strategies and pooled builder parity#33

Open
CorentinGS wants to merge 11 commits into
feature/zastring-finalfrom
escape-strategies-pooled-builder-net10
Open

Deepen escape strategies and pooled builder parity#33
CorentinGS wants to merge 11 commits into
feature/zastring-finalfrom
escape-strategies-pooled-builder-net10

Conversation

@CorentinGS

Copy link
Copy Markdown
Owner

Summary

  • Extract JSON, HTML, CSV, URL, and form-URL escaping into reusable span-to-span strategy modules
  • Add pooled builder escaping parity for JSON, HTML, CSV, URL, and form-URL
  • Add parity and zero-allocation guardrail tests
  • Update project, test, benchmark, and demo targets to net10.0

Verification

  • dotnet test tests/ZaString.Tests/ZaString.Tests.csproj --no-restore: 369/369 passed

Notes

  • PR is based on feature/zastring-final so docs and local PRD files are not included in this PR diff.

@CorentinGS CorentinGS force-pushed the escape-strategies-pooled-builder-net10 branch 2 times, most recently from c38e202 to 0d01293 Compare June 7, 2026 15:45
@CorentinGS CorentinGS force-pushed the feature/zastring-final branch from 03d0319 to d6b00e3 Compare June 7, 2026 15:47
@CorentinGS CorentinGS requested a review from Copilot June 7, 2026 15:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the escaping/encoding implementation to use reusable span-to-span “escape strategy” modules (JSON/HTML/CSV/URL/form-url), adds equivalent escaping APIs for ZaPooledStringBuilder, and updates projects to target .NET 10.

Changes:

  • Introduces ZaString.Escaping.*EscapeStrategy helpers and updates ZaSpanStringBuilder escaping extensions to use them.
  • Adds pooled-builder escaping methods and adds parity + zero-allocation guardrail tests for all escaping kinds.
  • Retargets library/tests/benchmarks/demo projects to net10.0.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
tests/ZaString.Tests/ZaString.Tests.csproj Retargets test project to net10.0 only.
tests/ZaString.Tests/ZaPooledStringBuilderTests.cs Adds pooled builder escaping parity + growth tests.
tests/ZaString.Tests/UrlEscapeStrategyTests.cs Adds direct tests for URL escape strategy behavior.
tests/ZaString.Tests/JsonEscapeStrategyTests.cs Adds direct tests for JSON escape strategy behavior.
tests/ZaString.Tests/HtmlEscapeStrategyTests.cs Adds direct tests for HTML escape strategy behavior.
tests/ZaString.Tests/FormUrlEscapeStrategyTests.cs Adds direct tests for form-url strategy behavior (incl. surrogate handling).
tests/ZaString.Tests/EscapeStrategyParityTests.cs Adds parity + allocation guardrail tests across builders/strategies.
tests/ZaString.Tests/CsvEscapeStrategyTests.cs Adds direct tests for CSV escape strategy behavior.
tests/ZaString.Benchmarks/ZaString.Benchmarks.csproj Retargets benchmarks to net10.0.
src/ZaString/ZaString.csproj Retargets library to net10.0 and updates package description.
src/ZaString/Extensions/ZaSpanStringBuilderExtensions.cs Replaces inline escaping logic with escape strategies.
src/ZaString/Escaping/UrlEscapeStrategy.cs Adds URL percent-encoding implementation.
src/ZaString/Escaping/JsonEscapeStrategy.cs Adds JSON escaping implementation.
src/ZaString/Escaping/HtmlEscapeStrategy.cs Adds HTML escaping implementation.
src/ZaString/Escaping/FormUrlEscapeStrategy.cs Adds form-url encoding implementation.
src/ZaString/Escaping/CsvEscapeStrategy.cs Adds CSV field escaping implementation.
src/ZaString/Core/ZaPooledStringBuilder.cs Adds append-span/advance support and pooled escaping APIs.
samples/ZaString.Demo/ZaString.Demo.csproj Retargets demo app to net10.0.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 665 to 669
public static bool TryAppendJsonEscaped(ref this ZaSpanStringBuilder builder, ReadOnlySpan<char> value)
{
var needsEscape = value.IndexOfAny("\"\\\b\f\n\r\t".AsSpan()) >= 0;
if (!needsEscape)
{
for (int i = 0; i < value.Length; i++)
{
if (value[i] < ' ' || value[i] is '\u2028' or '\u2029')
{
needsEscape = true;
break;
}
}
}

if (!needsEscape)
{
return builder.TryAppend(value);
}

var required = GetJsonEscapedLength(value);
var required = JsonEscapeStrategy.GetEscapedLength(value);
if (required > builder.RemainingSpan.Length)
{
Comment on lines 701 to 705
public static bool TryAppendHtmlEscaped(ref this ZaSpanStringBuilder builder, ReadOnlySpan<char> value)
{
if (value.IndexOfAny("&<>\"'".AsSpan()) < 0)
{
return builder.TryAppend(value);
}

var required = GetHtmlEscapedLength(value);
var required = HtmlEscapeStrategy.GetEscapedLength(value);
if (required > builder.RemainingSpan.Length)
{
Comment on lines 724 to 728
public static bool TryAppendCsvEscaped(ref this ZaSpanStringBuilder builder, ReadOnlySpan<char> value)
{
var needsQuote = NeedsCsvQuoting(value);
if (!needsQuote)
{
return builder.TryAppend(value);
}

var quoteCount = 0;
foreach (var t in value)
if (t == '"')
quoteCount++;

var required = value.Length + quoteCount + 2;
var required = CsvEscapeStrategy.GetEscapedLength(value);
if (required > builder.RemainingSpan.Length)
{
Comment on lines 761 to 765
public static bool TryAppendUrlEncoded(ref this ZaSpanStringBuilder builder, ReadOnlySpan<char> value)
{
var needsEncoding = false;
for (int i = 0; i < value.Length; i++)
{
if (!IsUnreservedAscii(value[i]))
{
needsEncoding = true;
break;
}
}

if (!needsEncoding)
{
return builder.TryAppend(value);
}

var required = GetUrlEncodedLength(value);
var required = UrlEscapeStrategy.GetEscapedLength(value);
if (required > builder.RemainingSpan.Length)
{
Comment on lines 787 to 791
public static bool TryAppendFormUrlEncoded(ref this ZaSpanStringBuilder builder, ReadOnlySpan<char> value)
{
var needsEncoding = false;
for (int i = 0; i < value.Length; i++)
{
if (value[i] == ' ' || !IsUnreservedAscii(value[i]))
{
needsEncoding = true;
break;
}
}

if (!needsEncoding)
{
return builder.TryAppend(value);
}

var required = GetFormUrlEncodedLengthReplacingInvalid(value);
var required = FormUrlEscapeStrategy.GetEscapedLength(value);
if (required > builder.RemainingSpan.Length)
{
Comment on lines +262 to +269
public ZaPooledStringBuilder AppendJsonEscaped(ReadOnlySpan<char> value)
{
var required = JsonEscapeStrategy.GetEscapedLength(value);
GetAppendSpan(required, out var destination);
JsonEscapeStrategy.TryEscape(value, destination, out var written);
Advance(written);
return this;
}
Comment on lines +271 to +278
public ZaPooledStringBuilder AppendHtmlEscaped(ReadOnlySpan<char> value)
{
var required = HtmlEscapeStrategy.GetEscapedLength(value);
GetAppendSpan(required, out var destination);
HtmlEscapeStrategy.TryEscape(value, destination, out var written);
Advance(written);
return this;
}
Comment on lines +280 to +287
public ZaPooledStringBuilder AppendCsvEscaped(ReadOnlySpan<char> value)
{
var required = CsvEscapeStrategy.GetEscapedLength(value);
GetAppendSpan(required, out var destination);
CsvEscapeStrategy.TryEscape(value, destination, out var written);
Advance(written);
return this;
}
Comment on lines +289 to +296
public ZaPooledStringBuilder AppendUrlEncoded(ReadOnlySpan<char> value)
{
var required = UrlEscapeStrategy.GetEscapedLength(value);
GetAppendSpan(required, out var destination);
UrlEscapeStrategy.TryEscape(value, destination, out var written);
Advance(written);
return this;
}
Comment on lines +298 to +305
public ZaPooledStringBuilder AppendFormUrlEncoded(ReadOnlySpan<char> value)
{
var required = FormUrlEscapeStrategy.GetEscapedLength(value);
GetAppendSpan(required, out var destination);
FormUrlEscapeStrategy.TryEscape(value, destination, out var written);
Advance(written);
return this;
}
@CorentinGS CorentinGS force-pushed the escape-strategies-pooled-builder-net10 branch from 0d01293 to fb51237 Compare June 7, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants