Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Unreleased

### Bug Fixes and Improvements

* Expand the public structural `$ref` API in `JSONSchex.Ref` with rebasing (`rebase/3`), canonical target identity (`target_uri/1`), external resource collection (`collect_external_resources/2`), structured bundle output (`bundle/3`), and mount-aware preserved-ref rendering (`render_ref/3` with `mode: :mounted`) alongside discovery (`scan/2`), single-step resolution (`resolve/3`), transitive traversal with cycle reporting (`walk/2`), callback-based transformation (`transform/3`), and location-keyed walk indexing (`index_walk_events/1`)
* Improve internal scope scanning so `contentSchema` is traversed for nested `$id`, anchor, and local `$ref` discovery

## v0.6.0 (2026-05-09)

### Bug Fixes and Improvements
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ end
- `f` — `format_assertion: true`
- `c` — `content_assertion: true`

For compile-time embeddable options such as `:external_loader`, prefer remote
For compile-time embeddable options such as `:loader`, prefer remote
captures like `&MyLoader.fetch/1` over anonymous functions.

`~X` is preferred over `~J` to avoid the common sigil-name conflict with Jason.
Expand Down Expand Up @@ -181,7 +181,7 @@ Enum.map(errors, &JSONSchex.format_error/1)

`JSONSchex.compile/2` accepts an optional keyword list with the following options:

- `:external_loader` — Function for loading remote `$ref` schemas (see [Loader guide](guide/loader.md))
- `:loader` — Function for loading remote `$ref` schemas (see [Loader guide](guide/loader.md))
- `:base_uri` — Starting base URI for resolving relative references (see [Loader guide](guide/loader.md))
- `:format_assertion` — Enable strict `format` validation (default: `false`; the built-in Draft 2020-12 dialect keeps `format` annotation-only unless explicitly enabled, see [Content and format guide](guide/content_and_format.md))
- `:content_assertion` — Enable strict content vocabulary validation (default: `false`, see [Content and format guide](guide/content_and_format.md))
Expand Down Expand Up @@ -212,6 +212,7 @@ end

See the `guide/` directory for detailed documentation:

- [Structural `$ref` discovery, rebasing, and bundling helpers](guide/ref.md)
- [Loader and remote `$ref` handling](guide/loader.md)
- [Dialect and `$vocabulary` behavior](guide/dialect_and_vocabulary.md)
- [Feature matrix (Draft 2020-12 support)](guide/feature_matrix.md)
Expand Down
6 changes: 3 additions & 3 deletions guide/dialect_and_vocabulary.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ JSONSchex resolves dialect in this order:

1. If the root schema declares the canonical Draft 2020-12 meta-schema URI (`https://json-schema.org/draft/2020-12/schema`), JSONSchex treats it as a built-in dialect and does not invoke the external loader for that URI.
2. For the built-in Draft 2020-12 dialect, JSONSchex uses the standard Draft 2020-12 active vocabulary defaults, while still honoring an explicit root-level `$vocabulary` declaration when present.
3. If the root schema contains another `$schema` URI and an `external_loader` is provided, JSONSchex attempts to load that meta-schema remotely.
3. If the root schema contains another `$schema` URI and an `loader` is provided, JSONSchex attempts to load that meta-schema remotely.
4. If a custom meta-schema loads successfully, JSONSchex reads `$vocabulary` from it to build the enabled vocabulary set.
5. If no loader is available or the meta-schema cannot be loaded, JSONSchex proceeds with the implementation default capability set.

Expand Down Expand Up @@ -150,7 +150,7 @@ schema = %{
}

# Compile with the custom loader
{:ok, compiled} = JSONSchex.compile(schema, external_loader: loader)
{:ok, compiled} = JSONSchex.compile(schema, loader: loader)

# The schema compiles, but 'allOf' is ignored because the applicator
# vocabulary is not in the restricted meta-schema's vocabulary list
Expand Down Expand Up @@ -192,7 +192,7 @@ JSONSchex.validate(compiled2, "not-an-email") # => {:error, [...]}
## Practical guidance

- Use the canonical Draft 2020-12 `$schema` URI when you want standard behavior without requiring a remote meta-schema fetch.
- If you rely on a custom meta-schema or vocabulary, provide an `external_loader`.
- If you rely on a custom meta-schema or vocabulary, provide an `loader`.
- Use `$schema` to make the dialect explicit and predictable.
- Use an explicit root `$vocabulary` only when you need to override the built-in active vocabulary set for the selected dialect.
- Avoid mixing keywords from unsupported vocabularies unless you also ship a loader that resolves the meta-schema.
2 changes: 1 addition & 1 deletion guide/feature_matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,4 @@ This guide summarizes JSONSchex support for Draft 2020-12 keywords and vocabular
## Notes

- When assertion options are not enabled, the corresponding keywords are accepted but do not enforce validation.
- For remote meta-schema resolution, provide an `external_loader` to `JSONSchex.compile/2`.
- For remote meta-schema resolution, provide an `loader` to `JSONSchex.compile/2`.
16 changes: 9 additions & 7 deletions guide/loader.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

This guide explains how JSONSchex resolves remote references and how to supply an external loader when compiling schemas.

If you need low-level structural `$ref` discovery and traversal before compilation, see the [Structural `$ref` guide](ref.md) and `JSONSchex.Ref`. That API uses a related but distinct loader contract.

## Overview

JSONSchex supports:
Expand All @@ -10,11 +12,11 @@ JSONSchex supports:
- `$schema` resolution when a meta-schema needs to be fetched
- `$id`-based base URI scoping for nested schemas

Remote fetching is **opt-in** via the `external_loader` option passed to `JSONSchex.compile/2`.
Remote fetching is **opt-in** via the `loader` option passed to `JSONSchex.compile/2`.

## Loader contract

Your loader is a function that receives a URI string and returns one of:
Your loader is a function that receives a resolved **document URI without the fragment** and returns one of:

- `{:ok, map}` — a decoded JSON Schema map
- `{:error, term}` — any error reason you want to propagate
Expand All @@ -40,7 +42,7 @@ loader = fn uri ->
end

{:ok, compiled} =
JSONSchex.compile(schema, external_loader: loader, base_uri: "https://example.com/root.json")
JSONSchex.compile(schema, loader: loader, base_uri: "https://example.com/root.json")
```

### HTTP-based loader example
Expand Down Expand Up @@ -94,7 +96,7 @@ schema = %{
"$ref" => "https://json-schema.org/draft/2020-12/schema"
}

{:ok, compiled} = JSONSchex.compile(schema, external_loader: &MyApp.SchemaLoader.loader/1)
{:ok, compiled} = JSONSchex.compile(schema, loader: &MyApp.SchemaLoader.loader/1)
```

**Important considerations for HTTP loaders:**
Expand All @@ -109,7 +111,7 @@ schema = %{

The loader is invoked when:

1. A `$ref` points to a **remote URI** that is not already in the registry.
1. A `$ref` points to an unresolved **external** resource that is not already in the registry. This includes `http(s)` refs as well as other non-local refs such as path-like file refs.
2. A `$schema` URI must be loaded to resolve dialect and `$vocabulary` (if a loader is provided).

If no loader is supplied, JSONSchex skips remote fetches and proceeds with defaults where possible.
Expand All @@ -120,8 +122,8 @@ At a high level:

1. Resolve the `$ref` against the current base URI.
2. Check the local registry for a match.
3. If the ref is remote and not in the registry, call the loader.
4. Compile the remote schema and merge its registry into the root context.
3. If the ref points to an unresolved external resource, call the loader with the resolved document URI without the fragment.
4. Compile the loaded schema and merge its registry into the root context.
5. Continue validation from the referenced fragment, if any.

## :base_uri option and $id interaction
Expand Down
Loading