Skip to content

feat: lenient JSON options for secret payloads (enum names + case-insensitive)#48

Merged
windischb merged 1 commit into
developfrom
feature/secrets-lenient-json-options
May 29, 2026
Merged

feat: lenient JSON options for secret payloads (enum names + case-insensitive)#48
windischb merged 1 commit into
developfrom
feature/secrets-lenient-json-options

Conversation

@windischb

Copy link
Copy Markdown
Contributor

Summary

Secret<T> previously (de)serialized its payload with JsonSerializerOptions.Default — case-sensitive, enums as ordinals. Enum-as-ordinal is fragile: if someone later reorders the enum, the stored number silently maps to a different member. This switches secret payloads to a shared, lenient SecretValueSerialization.Options:

  • enums serialize as names (round-trip-safe against reordering)
  • reading still accepts numeric enums and any property casing

Why it's safe for existing secrets

The change is strictly more permissive on read, so existing encrypted envelopes at rest (enum-as-number, PascalCase) stay fully readable — no migration. Only the in-memory form of newly serialized typed secret values changes (enum name instead of ordinal); the encrypted bytes at rest are produced by a separate encryptor path and are untouched.

Why a name/case-insensitive contract is the right default

Only the type-aware path inside the library (Secret<T> ctor) ever serializes a typed value (enum/object). Everything from outside — the CLI, hand-edited files, or a future browser encryptor — supplies plain JSON and would naturally send an enum's name. A name-based, case-insensitive contract handles all of those robustly. (The plaintext-config read path already used the lenient pipeline options; this aligns the decrypted-envelope path with it.)

Changes

  • SecretValueSerialization.Options (case-insensitive + JsonStringEnumConverter), applied to Secret<T> ctor serialize + Open() deserialize.
  • CLI encrypt --value help: pass enum names, not ordinals.
  • 7 tests: enum name/number read, name-write (not ordinal), case-insensitive object, FromPlain round-trips (enum, record-with-enum, nullable enum).

Verification

  • Full solution Release build: 0 errors
  • Full test suite (excl. Performance): 616 passing, 0 failing (Secrets 135, incl. 7 new) — no regressions.

Notes

Independent of #47 (LocalStorage). This is a small, focused enabler that also makes the future browser-encrypted-secrets idea (and complex/enum secret authoring in general) far more forgiving.

🤖 Generated with Claude Code

…ensitive)

Secret<T> previously (de)serialized its payload with JsonSerializerOptions.Default
(case-sensitive, enums as ordinals). Enum-as-ordinal is fragile: reordering the enum
silently remaps stored values. Switch to a shared, lenient SecretValueSerialization.Options:

- enums serialize as NAMES (round-trip-safe against reordering)
- reading still accepts numeric enums AND any property casing

This is strictly more permissive on read, so existing encrypted envelopes at rest stay
fully readable -- no migration. Only the in-memory form of newly serialized typed secret
values changes (enum name instead of ordinal). Only the type-aware path (Secret<T> ctor)
serializes a typed value; external sources (CLI/browser/files) supply plain JSON, which a
name-based, case-insensitive contract handles best.

- SecretValueSerialization.Options applied to Secret<T> ctor serialize + Open deserialize
- CLI encrypt --value help: pass enum names, not ordinals
- 7 tests (enum name/number read, name write, case-insensitive object, FromPlain round-trips)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@windischb windischb merged commit 8f72f1c into develop May 29, 2026
3 checks passed
@windischb windischb deleted the feature/secrets-lenient-json-options branch May 29, 2026 13:21
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