feat: add PropertyValue tagged type and form encoder#196
Merged
Conversation
Add a sealed PropertyValue type (scalar/array variants) holding raw unescaped strings, plus a form encoder over Map<String, PropertyValue> that performs all object form percent-encoding in one place via String.uriEncode and consults per-property explode only when the winning value is an array. The encoder owns all form value-emptiness checks (empty map, empty scalar, empty array). Descriptor-less arrays comma-join, preserving the status-quo wire output; per-property explode produces repeated keys with empty arrays omitted. Ships dark - no callers yet.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #196 +/- ##
==========================================
+ Coverage 92.74% 92.76% +0.01%
==========================================
Files 174 176 +2
Lines 17765 17808 +43
==========================================
+ Hits 16476 16519 +43
Misses 1289 1289
🚀 New features to boost your workflow:
|
Add coverage for the object-level allowReserved fallback (both exploded and collapsed modes) where no per-property descriptor is present, pinning the key/value encoding asymmetry, and a test proving an empty exploded array still throws when allowEmpty is false. Document that a per-property descriptor is the complete source of truth for its allowReserved, and correct the encoder doc comment to describe it as the percent-encoding boundary rather than the sole object encoding site. Remove two duplicate tests.
Add tests pinning that a per-property descriptor's allowReserved default takes precedence over an object-level allowReserved flag, and that a non-exploded array with allowReserved keeps element reserved characters literal while joining with literal commas. Correct the PropertyValue rationale to use a genuinely colliding scalar/single-element-array example, note that per-property descriptors are inert in collapse mode, and annotate the variants @immutable to match the sibling descriptor type.
Add a test proving a per-property allowReserved descriptor is dropped in collapse mode, so a reserved character in the value stays percent-encoded, guarding against a future change that leaks per-property flags into the collapsed object path. Note on the explode descriptor that it is only consulted when the containing object is exploded.
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
Adds a tagged runtime value type and a form encoder over it in
tonik_util. This is additive and ships dark — nothing calls the new code yet, so there is no wire behavior change.PropertyValue— a sealed type with two variants,PropertyValue.scalar(String)andPropertyValue.array(List<String>), each holding raw (unescaped) strings. The tag lets the map distinguish a scalar from a single-element array (scalar('x')andarray(['x'])both flatten to['x']under a plainMap<String, List<String>>) and separates a scalar empty string (key=) from an omitted exploded empty array from a non-exploded empty array — distinctions the flat map cannot carry.Map<String, PropertyValue>.toForm(...)that performs all object form percent-encoding in one place viaString.uriEncode. It consults per-propertyexplode(fromFormFieldEncoding) only when the winning value is an array; a descriptor-less array comma-joins, preserving today's wire output. The encoder owns all form value-emptiness checks (EmptyValueExceptionfor an empty map, empty scalar, or empty array whenallowEmptyis false).Behavior
explode: true→ one entry per property; an exploded array yields repeated keys, an empty exploded array is omitted, and['']yields a single empty-value entry.explode: false→ a single collapsedk1,v1,k2,v2entry, byte-identical to the existing map form encoder atallowReserved: false;fieldEncodingsis ignored in this mode.allowReservedapplies to values only; keys are always component-encoded. A present per-property descriptor is the complete source of truth for that property, so itsallowReserved(non-nullable, default false) takes precedence over the object-level flag.Tests
Unit tests in
tonik_utilcover repeated-key explode, the three-way empty-array distinction, comma-in-element encoding with and withoutallowReserved, the collapse byte-parity case, per-property overrides and precedence,useQueryComponentspace rendering, and all threeEmptyValueExceptioncases plus their non-throwing counterparts. Expected wire values are literal strings. 100% patch coverage.Design notes
PropertyValuedeliberately omits==/hashCodeand a defensive list copy, following the in-packageTonikResultsealed-ADT precedent: it is consumed only by an exhaustive switch and is never compared as an instance — tests assert on the resultingParameterEntryrecords, which have value equality. This differs from the siblingFormFieldEncoding, which implements equality because it is a descriptor looked up by key and compared. Both can be revisited if a future consumer starts comparing or retaining these values.