Skip to content

feat: add PropertyValue tagged type and form encoder#196

Merged
t-unit merged 4 commits into
mainfrom
property-value-form-encoder
Jul 7, 2026
Merged

feat: add PropertyValue tagged type and form encoder#196
t-unit merged 4 commits into
mainfrom
property-value-form-encoder

Conversation

@t-unit

@t-unit t-unit commented Jul 7, 2026

Copy link
Copy Markdown
Owner

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) and PropertyValue.array(List<String>), each holding raw (unescaped) strings. The tag lets the map distinguish a scalar from a single-element array (scalar('x') and array(['x']) both flatten to ['x'] under a plain Map<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.
  • Form encoder — an extension Map<String, PropertyValue>.toForm(...) that performs all object form percent-encoding in one place via String.uriEncode. It consults per-property explode (from FormFieldEncoding) 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 (EmptyValueException for an empty map, empty scalar, or empty array when allowEmpty is false).

Behavior

  • Object 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.
  • Object explode: false → a single collapsed k1,v1,k2,v2 entry, byte-identical to the existing map form encoder at allowReserved: false; fieldEncodings is ignored in this mode.
  • Per-property allowReserved applies to values only; keys are always component-encoded. A present per-property descriptor is the complete source of truth for that property, so its allowReserved (non-nullable, default false) takes precedence over the object-level flag.

Tests

Unit tests in tonik_util cover repeated-key explode, the three-way empty-array distinction, comma-in-element encoding with and without allowReserved, the collapse byte-parity case, per-property overrides and precedence, useQueryComponent space rendering, and all three EmptyValueException cases plus their non-throwing counterparts. Expected wire values are literal strings. 100% patch coverage.

Design notes

PropertyValue deliberately omits ==/hashCode and a defensive list copy, following the in-package TonikResult sealed-ADT precedent: it is consumed only by an exhaustive switch and is never compared as an instance — tests assert on the resulting ParameterEntry records, which have value equality. This differs from the sibling FormFieldEncoding, 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.

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

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.76%. Comparing base (354c998) to head (35475fe).

Additional details and impacted files

Impacted file tree graph

@@            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              
Files with missing lines Coverage Δ
...nik_util/lib/src/encoding/form_field_encoding.dart 80.00% <ø> (ø)
...es/tonik_util/lib/src/encoding/property_value.dart 100.00% <100.00%> (ø)
.../lib/src/encoding/property_value_form_encoder.dart 100.00% <100.00%> (ø)
Files with missing lines Coverage Δ
...nik_util/lib/src/encoding/form_field_encoding.dart 80.00% <ø> (ø)
...es/tonik_util/lib/src/encoding/property_value.dart 100.00% <100.00%> (ø)
.../lib/src/encoding/property_value_form_encoder.dart 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

t-unit added 3 commits July 7, 2026 21:03
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.
@t-unit t-unit marked this pull request as ready for review July 7, 2026 19:20
@t-unit t-unit merged commit 5a65b64 into main Jul 7, 2026
14 checks passed
@t-unit t-unit deleted the property-value-form-encoder branch July 7, 2026 19:42
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.

1 participant