Juli4nG/elm-json-render/3.0.0

JsonRender.Expr

The json-render expression / binding dialect, scoped to this package's supported set and pinned to @json-render/core v0.19.0 (see SUPPORT.md).

Every dynamic value in a manifest is a plain JSON object carrying a single $-prefixed discriminant key. This module decodes the supported forms into a typed Expr and resolves them against a host-owned state Value plus an optional repeat scope, using RFC 6901 JSON Pointers throughout.

Fail-closed: an object carrying an unsupported $-directive (e.g. $computed) fails the decode. json-render's own runtime is fail-open here; we are not. The supported set is exactly the v1 subset (which now includes $cond).

Expressions

type Expr
= ELiteral Value
| EState String
| EItem String
| EIndex
| EBindState String
| EBindItem String
| ETemplate String

A decoded json-render expression. The discriminant maps 1:1 to the pinned $-forms:

  • ELiteral v: any value with no $-directive (scalar, array, or plain object).
  • EState ptr: { "$state": "/ptr" }, read global state.
  • EItem field: { "$item": "field" }, read the current repeat item (field "" = whole item).
  • EIndex: { "$index": true }, the current repeat index.
  • EBindState ptr: { "$bindState": "/ptr" }, two-way bind to global state.
  • EBindItem field: { "$bindItem": "field" }, two-way bind to a repeat-item field.
  • ETemplate tmpl: { "$template": "…${/ptr}…${bare}…" }, string interpolation.
  • ECond cond t e: { "$cond": <condition>, "$then": <expr>, "$else": <expr> }, picks t when cond holds, else e. Both branches are themselves expressions (recursive). A two-way $bindState / $bindItem inside the selected branch keeps its write-back: writeBackPath targets the branch chosen at render time. See Condition.
= CBool Bool
| CSingle SingleCondition
| CEvery (List SingleCondition)
| CAnd (List Condition)
| COr (List Condition)

A json-render condition — the predicate of a $cond, and the same grammar as core's visible / hidden (pinned to @json-render/core v0.19.0). Evaluated to a Bool by resolve when picking a $cond branch:

  • CBool b: a literal true / false.
  • CSingle c: one $state / $item / $index comparison.
  • CEvery cs: a bare JSON array of single conditions, an implicit AND (all true).
  • CAnd cs / COr cs: explicit { "$and": [ … ] } / { "$or": [ … ] }, recursive.

Malformed conditions (unknown keys, mixed sources, a non-true not, a non-numeric operand for gt/gte/lt/lte) fail the decode, mirroring the rest of this module.

type alias SingleCondition =
{ source : CondSource
, comparison : Comparison
, negate : Bool
}

One comparison against a single source value. negate is core's "not": true modifier, which inverts whatever the operator (or the bare truthiness test) yields.

= SrcState String
| SrcItem String
| SrcIndex

The left-hand side of a single condition: $state pointer, $item field, or the repeat $index.

= CmpTruthy
| CmpEq Comparand
| CmpNeq Comparand
| CmpGt Comparand
| CmpGte Comparand
| CmpLt Comparand
| CmpLte Comparand

The comparison operator. CmpTruthy is the no-operator case (JS Boolean(value)). eq / neq compare against any value by JSON scalar equality; gt / gte / lt / lte compare numbers only: a non-numeric literal operand is rejected at decode, and a non-numeric source value yields False at eval time (per core).

A missing path (an absent $state / $item / $index source or {$state} operand) is JS undefined, kept distinct from JSON null: it is falsy, eq-equal only to another missing path (undefined === undefined; undefined !== null), and never orders.

= CLiteral Value
| CStateRef String

The right-hand side of a comparison: either a literal JSON value or a { "$state": "/ptr" } reference resolved against global state at eval time (core's resolveComparisonValue).

decoder : Decoder Expr

Decode a prop value into an Expr.

A non-object (string / number / bool / array / null) decodes to ELiteral. An object with no $-prefixed key decodes to ELiteral. An object with exactly one supported $-directive decodes to that form. An object with an unsupported $-directive, or more than one $-directive, fails. This is the fail-closed boundary.

Resolution scope

type alias Context =
{ state : Value
, item : Maybe Value
, index : Maybe Int
, basePath : Maybe String
, allowedOrigins : List String
}

The resolution scope handed to every expression.

  • state: the host-owned global state Value (the single source of truth).
  • item: the current repeat item, if inside a repeat scope.
  • index: the current repeat index, if inside a repeat scope.
  • basePath: the repeat item's absolute JSON Pointer (statePath ++ "/" ++ index), used to compute $bindItem / top-level $item write-back paths.
  • allowedOrigins: the host-provided iframe origin allowlist. The Iframe element only renders when a resolved src's origin is an exact member of this list (fail-closed).
rootContext : List String -> Value -> Context

The top-level scope: global state only, no repeat item. allowedOrigins is the host-provided iframe origin allowlist, threaded to every expression scope.

childContext : String -> Int -> Value -> Context -> Context

Derive a per-item scope for a repeat element. statePath is the pointer to the state array; index and item identify the current row. basePath becomes statePath ++ "/" ++ index per the pinned reference.

JSON Pointers

getByPath : String -> Value -> Maybe Value

Resolve an RFC 6901 JSON Pointer against a Value. Returns Nothing if any token is missing. The empty pointer "" returns the whole value. Tokens are unescaped (~1 → /, ~0 → ~). A leading / is honored; a bare token (no leading /, as produced by $item field lookups) is tolerated as a single step.

Resolving

resolve : Context -> Expr -> Value

Resolve an expression to a JSON Value. A missing pointer / item field yields null. Two-way $bindState / $bindItem read exactly like their read-only siblings; their write-back path is exposed separately via writeBackPath.

resolveDisplay : Context -> Expr -> String

Resolve an expression to its display string (for Text / Badge). Scalars render as themselves; null / missing renders as ""; objects / arrays render as "".

resolveBool : Context -> Expr -> Bool

Resolve an expression to a boolean (for Checkbox checked). Only a JSON true is True; everything else (including missing) is False.

writeBackPath : Context -> Expr -> Maybe String

The absolute JSON Pointer a two-way binding writes back to, or Nothing for read-only expressions. $bindState writes its own pointer; $bindItem writes basePath ++ "/" ++ field. A $cond passes through to the branch it selects at render time (see Expr), so a $bindState / $bindItem inside the chosen branch keeps its write-back handler.

resolveParams : Context -> Value -> Value

Resolve an action's params object per the pinned resolveActionParam semantics:

  • A top-level { "$item": "field" } resolves to the absolute state path (basePath ++ "/" ++ field) so the host can target the row.
  • A top-level { "$index": true } resolves to the index number.
  • Everything else (including $item nested in an array) resolves to the value by deep recursion.

So { "ids": [ { "$item": "id" } ] } yields { "ids": ["<id>"] }: the literal id, never a path.

validatedParams : Decoder Value

A Decoder for an action's params that validates fail-closed at decode time: it returns the params Value unchanged, but fails if any nested object carrying a $-directive is not a supported, well-formed Expr (e.g. an unsupported $computed, or a malformed { "$item": 123 }). This closes the gap where params are stored raw and only resolved at dispatch: a bad directive is rejected with the rest of the manifest, never emitted to the host.