A2UICore.Pointer (A2UICore v0.1.1)

Copy Markdown View Source

RFC 6901 JSON Pointer parsing, building, and resolution.

Matches the reference implementation exactly:

  • "/" or "" parse to the root (empty token list).
  • Empty segments are dropped, so "//a///b//" ⇔ "/a/b".
  • Unescape order is ~1 → / then ~0 → ~ (so "~01" decodes to "~1").
  • Forbidden segments after unescaping: "__proto__", "constructor", "prototype".
  • Array indices follow the RFC §4 numeric rule: 0 or non-zero-leading digits only ("01" is not an index).
  • Maximum array index 10_000 guards against OOM/DoS by construction.

This module only handles pointer parsing/building; the store that enforces traversal rules lives in A2UICore.State.DataModel.

Summary

Types

One segment of a parsed pointer, as stored after unescape.

Functions

True if segment can be used as an array index per RFC 6901 §4 — i.e. either "0" or a positive integer with no leading zeros.

Builds an absolute pointer string from unescaped tokens (inverse of parse/1).

The forbidden path segments (after unescaping), as a MapSet.

The maximum allowed array index (inclusive).

Parses a pointer string into its segments, unescaping them.

Resolves path against context_path

Types

token()

(since 0.1.0)
@type token() :: String.t()

One segment of a parsed pointer, as stored after unescape.

Functions

array_index?(segment)

(since 0.1.0)
@spec array_index?(term()) :: boolean()

True if segment can be used as an array index per RFC 6901 §4 — i.e. either "0" or a positive integer with no leading zeros.

build(tokens)

(since 0.1.0)
@spec build([token()]) :: String.t()

Builds an absolute pointer string from unescaped tokens (inverse of parse/1).

forbidden_segments()

(since 0.1.0)
@spec forbidden_segments() :: MapSet.t(String.t())

The forbidden path segments (after unescaping), as a MapSet.

These guard against prototype pollution.

max_array_index()

(since 0.1.0)
@spec max_array_index() :: pos_integer()

The maximum allowed array index (inclusive).

parse(path)

(since 0.1.0)
@spec parse(term()) :: {:ok, [token()]} | {:error, A2UICore.Error.t()}

Parses a pointer string into its segments, unescaping them.

Returns {:ok, tokens} or {:error, %A2UICore.Error{}} with category :data if a segment is in the forbidden set or path is invalid.

Examples

iex> A2UICore.Pointer.parse("/user/name")
{:ok, ["user", "name"]}
iex> A2UICore.Pointer.parse("/a~1b/~0c")
{:ok, ["a/b", "~c"]}
iex> A2UICore.Pointer.parse("/")
{:ok, []}
iex> A2UICore.Pointer.parse("")
{:ok, []}
iex> {:error, error} = A2UICore.Pointer.parse("/__proto__")
iex> error.category
:data
iex> error.code
"DATA_ERROR"

resolve(path, context_path \\ nil)

(since 0.1.0)
@spec resolve(String.t(), String.t() | nil) :: String.t()

Resolves path against context_path:

resolve("/user", anything)      → "/user"
resolve("name", "/user")       → "/user/name"
resolve("name", "/")           → "/name"
resolve("name", nil)           → "/name"

This is the simple client-scope join rule from the reference. The collection-scope semantics are in A2UICore.Resolution.DataContext.