Add URL search params serialization for Seam API requests - #314
Draft
razor-x wants to merge 3 commits into
Draft
Conversation
Port @seamapi/url-search-params-serializer to C# as Seam.Client.UrlSearchParamsSerializer over a Seam.Client.UrlSearchParams pair collection, byte-for-byte identical to the TypeScript reference implementation. The test suite mirrors the reference and the other SDK suites, covering every branch of the standard. The .NET primitives each diverge from the standard, so the port implements them directly: Uri.EscapeDataString is RFC 3986 flavored (escapes *, keeps ~) where the WHATWG form encoding does the opposite, and WebUtility.UrlEncode emits lowercase hex; the shortest round-tripping number format switches to exponent notation at the wrong thresholds and spells them E+16, so numbers follow the ECMAScript Number::toString algorithm; and List.Sort is unstable, which would lose array element order, so the sort is stable and ordinal, as URLSearchParams.sort() is. C# has a single absence value, so Seam.Client.Null adds the explicit null sentinel: null means the safe option of omitting a param, and sending null is always spelled Null.Value. The sentinel declares its own JsonConverter, so a param set to it is sent as JSON null in a request body under any serializer settings, while an omitted param is still dropped by EmitDefaultValue. Every SDK request sends a JSON body, so that is where the sentinel reaches the API; the serializers are exported for callers that build their own requests. StrictUrlSearchParamsSerializer wraps the base serializer and appends _strict=true to any non-empty query, telling the Seam API to use strict, schema-aware parsing. The flag is appended after the sort so it always sits last, a caller-supplied _strict param is replaced rather than repeated, and a query with no serializable params stays empty. The flag is Seam API behavior, not part of the serialization standard, so it is isolated in the wrapper and the base serializer stays a pure implementation of the standard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BLJcDjH3YHxDgtPgfq82g4
Consume the blueprint's preferredMethod so a route calls the client method the Seam API prefers for it, rather than posting everything. Nine endpoints become GET and one becomes DELETE; the rest keep a body as POST, PUT or PATCH. Which transport carries the params follows from the method, so the generated route stays the same shape for every endpoint and the client decides: a GET or DELETE serializes its params into the query string with StrictUrlSearchParamsSerializer, and everything else sends a JSON body as before. This is what applies the serialization standard to the SDK's own requests, where until now it was only exported for callers building their own. The params are converted to search params through the request's JSON contract rather than by reflection, so a param carries the same name and the same value on either transport: DataMember names, string enum values, and EmitDefaultValue omission all behave as they do in a body. A param left unset is absent from that contract, so a null in it can only be the Null sentinel and is restored as one, which serializes to an empty value. One byte of the query is not the serializer's: Uri normalizes a percent-encoded unreserved character back to its literal form, so `~` reaches the wire as `~` rather than as `%7E`. Both decode to the same param. The new transport tests assert what reaches an HttpListener for each preferred method: the request line, the serialized query, and the body. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BLJcDjH3YHxDgtPgfq82g4
Three elements prove nothing about stability: below 17 elements .NET's introsort degrades to an insertion sort, which keeps a short array in order whether or not the sort preserves it. Sort 32 instead, and assert the same string the PHP, Python, Ruby and JavaScript SDKs produce for the same params. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BLJcDjH3YHxDgtPgfq82g4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds comprehensive URL search parameter serialization support to the C# SDK, enabling proper serialization of complex types to query strings for HTTP GET requests to the Seam API. This includes a new
Nullsentinel type to distinguish between omitted parameters and explicitly null values.Key Changes
UrlSearchParams: A new mutable collection class that manages URL search parameter pairs, supporting append, set, delete, sort, and encoding/decoding operations with WHATWG application/x-www-form-urlencoded serialization.
UrlSearchParamsSerializer: A port of the reference
@seamapi/url-search-params-serializerimplementation that serializes objects to URL search parameters with byte-for-byte compatibility. Features include:StrictUrlSearchParamsSerializer: A wrapper around the base serializer that appends
_strict=trueto non-empty queries for Seam API strict, schema-aware parsing.Null sentinel: A new
Null.Valuetype that explicitly represents null values in request parameters:nullin request bodiesnullwhich means "omit this parameter"UnserializableParamError: A new exception type thrown when parameters cannot be serialized, with detailed error messages indicating the problematic parameter name and reason.
Comprehensive test coverage: Added 392 tests for serialization behavior, 131 tests for UrlSearchParams operations, 63 tests for strict serialization, and 53 tests for the Null sentinel.
Documentation: Updated README files with examples of setting null values and using the serializers with custom HTTP clients.
Notable Implementation Details
name=https://claude.ai/code/session_01BLJcDjH3YHxDgtPgfq82g4