Shared mechanics for portable persisted claims, claim-key normalization, and registered-claim merging.
Protocol modules retain ownership of their registered-claim policy. This module defines the lossless persisted-JSON boundary used by grant stores and makes map-key handling explicit before claims are serialized or merged.
Summary
Functions
Compare an audience claim with an expected audience under an explicit protocol mode.
Merge caller-supplied claims into authoritative registered claims.
Normalize the keys in map to JSON member-name binaries.
Return whether value is a portable, lossless JSON object for persisted
grant context.
Match a JOSE typ value against an explicit accepted set using the
RFC 7515 application/ bare-subtype convention.
Types
@type atom_policy() :: :reject | :convert
@type audience_mode() :: :scalar_only | :array | :single_element | :all_array_members
@type normalize_error() :: {:invalid_key, term()} | :invalid_atom_policy
Functions
@spec audience_matches?(term(), term(), audience_mode()) :: boolean()
Compare an audience claim with an expected audience under an explicit protocol mode.
The caller must select the mode because audience forms are protocol policy, not a single shared rule:
:scalar_onlyaccepts only a binary claim.:arrayaccepts a binary claim or a non-empty all-binary array with intersection semantics.:single_elementaccepts a scalar or exactly[expected].:all_array_membersrequires every member of an array to be expected.
This is a value-level predicate; callers retain responsibility for error mapping and any surrounding claim validation.
@spec merge_registered(map(), map(), keyword()) :: {:ok, map()} | {:error, normalize_error() | :reserved_claim_conflict}
Merge caller-supplied claims into authoritative registered claims.
The caller map is normalized first. Keys in reserved are rejected before
the merge; by default every key already present in registered is reserved.
atom_keys is passed to normalize_keys/2 as its :atoms policy.
@spec normalize_keys( map(), keyword() ) :: {:ok, map()} | {:error, normalize_error()}
Normalize the keys in map to JSON member-name binaries.
With atoms: :reject (the default), every key must already be a binary.
With atoms: :convert, atom keys are converted with Atom.to_string/1;
all other non-binary keys are rejected.
Return whether value is a portable, lossless JSON object for persisted
grant context.
Objects must have string keys and values must be the lossless I-JSON subset:
strings, booleans, nil, exact-range integers, arrays, or nested objects.
Floats are rejected because JSONB can canonicalize them, and integers are
limited to -(2^53)+1 .. (2^53)-1 (RFC 7493 §2.2). Strings must be valid
UTF-8 and cannot contain U+0000, which PostgreSQL JSONB cannot store.
Other Elixir atoms (including atom keys), tuples, structs, PIDs, references,
and other VM terms are rejected. This predicate deliberately does not
normalize or copy values, so callers can use it before persisting a context
that must round-trip unchanged. Composite nesting depth is capped at 64,
counting the root object as depth 1, to keep JSONB operations within
PostgreSQL's stack limits.
Match a JOSE typ value against an explicit accepted set using the
RFC 7515 application/ bare-subtype convention.
The normalization option is intentionally required. Callers with exact or otherwise protocol-specific type rules must keep those rules local.