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
28 changes: 27 additions & 1 deletion .claude/skills/FOLLOW-UPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,40 @@ ones 3.2 doesn't need.
`apidom-ns-json-schema-2020-12/src/index.ts` doesn't re-export them from
`src/traversal/visitor.ts` (`apidom-ns-openapi-3-1` re-exports its own). Both apidom-reference's
and this repository's JSON Schema dereference strategies need them; recorded in ApiDOM's
`add-spec-version/references/json-schema-dialects.md` as a phase 3 prerequisite.
`add-spec-version/references/json-schema-dialects.md` as a phase 3 prerequisite. 2026-10-26:
exported on the ApiDOM branch `feat/json-schema-2020-12/phase-3` (`1fbd257e2`), not released yet;
`feat/json-schema-2020-12` here uses it as a tarball.

### 2.2 ⏳ Swagger UI: `fn.resolve` lacks the 3.2 strategy

`src/core/plugins/swagger-client/index.js` in swagger-ui lists 3.1, 3.0, 2.0 and generic for
`fn.resolve`, while `resolveSubtree` (the path Swagger UI uses) has 3.2. `resolveSpec` is
deprecated, so it only affects callers of `fn.resolve`; recorded in swagger-ui's FOLLOW-UPS.

### 2.3 ⏳ ApiDOM: the `uri` selector misses relative `$id`s nested in relative `$id`s

apidom-reference's `dereference/strategies/{openapi-3-1,openapi-3-2,json-schema-2020-12}/selectors/uri`
resolves each candidate schema's `$id` chain against the `$ref` target (`resolveSchema$idField(uriStrippedHash, e)`)
instead of the document's base URI. In a document without an absolute root `$id`, a `$ref` to an
embedded resource whose relative `$id` sits under another relative `$id` (`$id: "nested/"` then
`$id: "tag.json"`) finds nothing, falls back to retrieving `nested/tag.json`, and stays a `$ref`.
OpenAPI 3.1 Schema Objects behave the same. Fix in ApiDOM (evaluate the chain against the
retrieval URI of the document holding the candidates); swagger-client's fixture
`json-schema-2020-12-swagger-client/json-schema/__fixtures__/id-relative-nested` is a
`test.failing` that starts failing once the fix is installed (then make it a regular test).

### 2.4 ⏳ ApiDOM: the `$anchor` selector searches embedded schema resources

apidom-reference's `dereference/strategies/{openapi-3-1,openapi-3-2,json-schema-2020-12}/selectors/$anchor`
finds the first schema in pre-order whose `$anchor` (in 2020-12 also `$dynamicAnchor`) matches, including
schemas inside embedded `$id` resources. An anchor belongs to the schema resource containing it
(JSON Schema Core 8.2.2), so when an embedded resource reuses the name, `"$ref": "#tag"` can
select the embedded resource's schema instead of the referencing resource's. OpenAPI 3.1 Schema
Objects behave the same. Fix in the same ApiDOM follow-up as 2.3 (stop the search at nested
`$id`s); swagger-client's fixture
`json-schema-2020-12-swagger-client/json-schema/__fixtures__/anchor-embedded-resource` is a
`test.failing` that starts failing once the fix is installed (then make it a regular test).

## 3. Evals

### 3.1 ⏳ Not run yet
Expand Down
5 changes: 3 additions & 2 deletions .claude/skills/add-json-schema-support/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: add-json-schema-support
description: Adds resolution of standalone JSON Schema documents (root `$schema`, no `openapi`/`swagger` field) to swagger-client, 2020-12 first, then 2019-09, Draft 7, 6 and 4, as in the "Standalone JSON Schema" design doc. Checks that the installed ApiDOM has the dialect's namespace and apidom-reference strategies, then builds a root-only `$schema` predicate, the `json-schema-<dialect>-apidom` resolve strategy over ApiDOM (so `$ref`s resolve against `$id` base URIs and `$anchor`s), a schema-only dereference strategy, parsers, registration ahead of the generic strategy and tests. Swagger UI resolves `$ref`s in its preview through this strategy. Use whenever someone wants swagger-client, Swagger UI or SwaggerEditor to resolve or dereference `$ref`s in a JSON Schema document or a specific draft, or asks why `$id`/`$anchor` references in a standalone schema don't resolve, even if they don't name this skill. Not for Schema Objects inside OpenAPI documents (that is add-oas-support).
description: Adds resolution of standalone JSON Schema documents (root `$schema`, no `openapi`/`swagger`/`asyncapi`/`arazzo` field) to swagger-client, 2020-12 first, then 2019-09, Draft 7, 6 and 4, as in the "Standalone JSON Schema" design doc. Checks that the installed ApiDOM has the dialect's namespace and apidom-reference strategies, then builds a root-only `$schema` predicate, the `json-schema-<dialect>-apidom` resolve strategy over ApiDOM (so `$ref`s resolve against `$id` base URIs and `$anchor`s), a schema-only dereference strategy, parsers, registration ahead of the generic strategy and tests. Swagger UI resolves `$ref`s in its preview through this strategy. Use whenever someone wants swagger-client, Swagger UI or SwaggerEditor to resolve or dereference `$ref`s in a JSON Schema document or a specific draft, or asks why `$id`/`$anchor` references in a standalone schema don't resolve, even if they don't name this skill. Not for Schema Objects inside OpenAPI documents (that is add-oas-support).
---

# Add Standalone JSON Schema Resolution to swagger-client
Expand Down Expand Up @@ -52,7 +52,8 @@ resolves `$ref`s through swagger-client, so swagger-client owns `$ref` resolutio
Read `references/strategy-design.md` and `../add-oas-support/references/review-lessons.md`, then:

1. Predicate `isJSONSchema<suffix>` in `src/helpers/json-schema-predicates.js` (root only, the
exact identifier with an empty fragment normalised, no `openapi`/`swagger` field).
exact identifier with an empty fragment normalised, no `openapi`, `swagger`, `asyncapi` or
`arazzo` field).
2. Strategy `src/resolver/strategies/json-schema-<dialect>-apidom/` (`index.js`, `resolve.js`,
`normalize.js`).
3. Parsers `src/resolver/apidom/reference/parse/parsers/json-schema-{json,yaml}-<dialect>/`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ focus: trace
weight: 1
---

PLAN.md matches documents with a root-only $schema predicate (exact 2020-12 identifier, empty fragment normalised, no openapi or swagger field) that leaves the http:// variant, custom meta-schemas and nested $schema to the generic strategy.
PLAN.md matches documents with a root-only $schema predicate (exact 2020-12 identifier, empty fragment normalised, no openapi, swagger, asyncapi or arazzo field) that leaves the http:// variant, custom meta-schemas and nested $schema to the generic strategy.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,31 @@ Answered (record them in the tracking doc as given):
5. **allOf merging and macros** (2026-10-25): as for OpenAPI 3.1 Schema Objects. The dereference
strategy merges `allOf` unless `mode: 'strict'` and applies `modelPropertyMacro`, so a schema
resolves the same standalone and inside an OpenAPI 3.1 document.
6. **References that cross dialects** (2026-10-26): out of scope in all four repositories. A
`$ref` from a 2020-12 document into another dialect's document is refracted as 2020-12.
7. **Unreleased upstream** (2026-10-26): a step starts once the ApiDOM PR is ready, on tarballs
packed from its branch (`../../add-oas-support/references/unreleased-upstream.md`), and merges
only after the ApiDOM release, with the tarballs replaced and every gate run again. No
prerelease on npm.
8. **Content-detecting JSON Schema parsers** (2026-10-26): only in this strategy's parser list,
never in one the OpenAPI strategies use: external files of OpenAPI documents often declare a
root `$schema`, and the OpenAPI dereference visitors expect them as generic ApiDOM. A
regression test resolves an OpenAPI 3.1 document whose external `$ref` target has a root
2020-12 `$schema` (apidom-reference's saturated configuration turns detection off for the
same reason).
9. **`$ref` to a `$dynamicAnchor`** (2026-10-26): resolves like one to an `$anchor`; it comes with
apidom-reference's 2020-12 `$anchor` selector, so use that selector.
10. **API description documents with a root `$schema`** (2026-10-27): a document is standalone
JSON Schema only when its root has the dialect's `$schema` and none of the API description
fields `openapi`, `swagger`, `asyncapi` and `arazzo`. AsyncAPI and Arazzo documents with a
root `$schema` keep the strategy they had (`generic`); regression tests compare them with a
`generic`-only resolve.
11. **`$ref`s to ancestors of a resolved subtree** (2026-10-27): the resolve strategy seeds the
dereference lineage with the document root and every element along `pathDiscriminator`, so
a `$ref` from the fragment to an ancestor (`"$ref": "#"`) is a cycle, not endless expansion.
The JSON Schema strategy gets it in its own PR; the same defect in `openapi-3-1-apidom` and
`openapi-3-2-apidom` is fixed in a separate PR off `main`
(`fix/resolver-subtree-ancestor-cycles`), not in the JSON Schema PR.

## PR Plan

Expand Down
6 changes: 4 additions & 2 deletions .claude/skills/add-json-schema-support/references/dialects.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ Draft 5 kept the Draft 4 meta-schema, so Draft 4 documents cover it.

## Matching (same as Swagger UI)

- Only the document root's `$schema`, and only when there is no `openapi` or `swagger` field:
OpenAPI 3.1+ Schema Objects carry `$schema` too.
- Only the document root's `$schema`, and only when the root has none of the API description
fields `openapi`, `swagger`, `asyncapi` and `arazzo` (decision 10 in `design-and-delivery.md`):
OpenAPI 3.1+ Schema Objects carry `$schema` too, and API description documents can declare a
root `$schema`; they keep their own strategy or `generic`.
- The identifier exactly, with an empty fragment normalised: up to Draft 7 the identifiers end in
`#`, from 2019-09 they don't; documents add or drop it.
- Nothing else: the other scheme (`http://json-schema.org/draft/2020-12/schema`), custom
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ From the installed ApiDOM release (Step 0's `spec-status.mjs` checks all three):

## Predicate: `src/helpers/json-schema-predicates.js`

`isJSONSchema202012(spec)`: a plain object, no `openapi` or `swagger` key, `$schema` a string
`isJSONSchema202012(spec)`: a plain object, none of the keys `openapi`, `swagger`, `asyncapi` and
`arazzo` (whatever their value), `$schema` a string
equal to the identifier after removing an empty trailing `#`. Wrap in `try`/`catch` like
`openapi-predicates.js`. Matching rules: `dialects.md`.

Expand All @@ -29,19 +30,22 @@ equal to the identifier after removing an empty trailing `#`. Wrap in `try`/`cat
returning its input: the OpenAPI normalization plugins (operation ids, parameters, security)
have nothing to act on. Because of that the subtree resolver's `skipNormalization` list needs
no entry; add one if the strategy ever normalizes.
- `resolve.js` from `openapi-3-1-apidom/resolve.js`, with `JSONSchemaElement.refract(spec)` (and
its `WeakMap` cache), `mediaTypes.latest()` of the namespace, apidom-reference's
- `resolve.js` from `openapi-3-1-apidom/resolve.js`, with the namespace's `refract(spec)` (and
its `WeakMap` cache; not `JSONSchemaElement.refract`, which apidom-ns-arazzo-1 reassigns to its
own refractor when both are loaded), `mediaTypes.latest()` of the namespace, apidom-reference's
`json-schema-2020-12` resolve strategy, the parsers below plus JSON, YAML and binary, and the
dereference strategy below. Keep the base URI logic, fragments (`pathDiscriminator`),
`ReferenceSet`, `circularReplacer`, error collection and the invalid-pointer fallback: Swagger
UI resolves subtrees lazily, so fragments are the main path.
UI resolves subtrees lazily, so fragments are the main path. Seed the `ancestors` lineage with
the root and every element along `pathDiscriminator`, not the fragment alone (decision 11):
otherwise `"$ref": "#"` inside a resolved subtree expands until the stack overflows.
- `normalize.js`: the identity, exported for the same shape as the other strategies.

## Parsers: `src/resolver/apidom/reference/parse/parsers/json-schema-{json,yaml}-2020-12/`

From `openapi-json-3-1` / `openapi-yaml-3-1`: parser names
`json-schema-{json,yaml}-2020-12-swagger-client`, the namespace's media types, `JSON.parse` /
js-yaml `JSON_SCHEMA`, `JSONSchemaElement.refract`. Detect with the predicate on the parsed value
js-yaml `JSON_SCHEMA`, the namespace's `refract`. Detect with the predicate on the parsed value
rather than a regex over the text: a regex also matches a nested `$schema`. External files
without a root `$schema` stay with the generic JSON/YAML parsers; the dereference visitor
refracts what a `$ref` points at into a schema.
Expand All @@ -55,7 +59,10 @@ refracts what a `$ref` points at into a schema.
(the namespace's traversal key), with its lineage, cycle, `$$ref` and error handling, and the
`$anchor`/`uri` selectors imported from `dereference/strategies/json-schema-2020-12/selectors`.
Drop `ReferenceElement`, `PathItemElement`, `LinkElement` and `ExampleElement`: they serve
OpenAPI objects. `$dynamicRef` stays untouched (decision 2).
OpenAPI objects. `$dynamicRef` stays untouched (decision 2). Unlike an OpenAPI root, a JSON
Schema root can have a `$ref`: its replacement keeps the `result` class (as apidom-reference's
2020-12 visitor does), or every later `$ref` into the document finds no parse result and stays
unresolved (fixture `root-ref`).
- `visitors/root.js`: merge `dereference.js`, and per decision 5 `properties.js`
(`modelPropertyMacro`) and `all-of.js` (unless `mode: 'strict'`), both moved to
`JSONSchema202012Element`. No `parameters.js`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ are in `.claude/skills/add-oas-support/references/review-lessons.md` (numbers in
- [ ] OpenAPI: the regex accepts every patch of the line and nothing else; `isOpenAPI3` includes
it. Run it against the new version, the parent and the next line.
- [ ] JSON Schema: root `$schema` only, the exact identifier with an empty fragment normalised,
no `openapi`/`swagger` field; refuses the other scheme, custom meta-schemas and a nested
`$schema`.
no `openapi`, `swagger`, `asyncapi` or `arazzo` field; refuses the other scheme, custom
meta-schemas, a nested `$schema` and API description documents with a root `$schema`.

## 3. Strategy

Expand Down
4 changes: 4 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,13 @@ module.exports = {
'^@swagger-api/apidom-reference/dereference/strategies/openapi-3-2$',
'^@swagger-api/apidom-reference/dereference/strategies/openapi-3-2/selectors/\\$anchor$',
'^@swagger-api/apidom-reference/dereference/strategies/openapi-3-2/selectors/uri$',
'^@swagger-api/apidom-reference/dereference/strategies/json-schema-2020-12$',
'^@swagger-api/apidom-reference/dereference/strategies/json-schema-2020-12/selectors/\\$anchor$',
'^@swagger-api/apidom-reference/dereference/strategies/json-schema-2020-12/selectors/uri$',
'^@swagger-api/apidom-reference/resolve/resolvers/file$',
'^@swagger-api/apidom-reference/resolve/strategies/openapi-3-1$',
'^@swagger-api/apidom-reference/resolve/strategies/openapi-3-2$',
'^@swagger-api/apidom-reference/resolve/strategies/json-schema-2020-12$',
'^@swagger-api/apidom-reference/parse/parsers/binary$',
'^@swagger-api/apidom-json-pointer/modern$',
],
Expand Down
7 changes: 6 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This document provides comprehensive guidance for AI assistants working on the s
- OpenAPI 3.0.x (3.0.0 through 3.0.4)
- Swagger/OpenAPI 2.0
- Legacy Swagger 1.x (via version 2.x branch)
- Standalone JSON Schema 2020-12 documents (resolution only: root `$schema`, no `openapi`/`swagger`/`asyncapi`/`arazzo` field)

**Current Version**: 3.36.0

Expand Down Expand Up @@ -304,10 +305,14 @@ Handles $ref resolution and spec normalization. Uses different strategies based
- `openapi-3-1-apidom`: Uses ApiDOM library for OpenAPI 3.1
- `openapi-3-0`: Custom resolver for OpenAPI 3.0.x
- `openapi-2-0`: Custom resolver for Swagger 2.0
- `json-schema-2020-12-apidom`: Uses ApiDOM for standalone JSON Schema 2020-12 documents (`$id`, `$anchor`; `$dynamicRef` left in place)
- `generic`: Fallback for unknown specs

**Strategy Selection** (in `src/resolver/index.js`):
Strategies are tried in order until one matches. ApiDOM is preferred for OAS 3.1.
Strategies are tried in order until one matches. ApiDOM is preferred for OAS 3.1. The JSON Schema
strategy comes right before `generic`; its predicate (`src/helpers/json-schema-predicates.js`) refuses
OpenAPI, Swagger, AsyncAPI and Arazzo documents and any root `$schema` other than the 2020-12
identifier.

**SpecMap** (`src/resolver/specmap/`):
Legacy resolution engine used by non-ApiDOM strategies. Handles:
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ Swagger Client Version | Release Date | OpenAPI Spec compatibility
3.10.x | 2020-01-17 | 2.0, 3.0.0, 3.0.1, 3.0.2, 3.0.3 | [tag v3.10.0](https://github.com/swagger-api/swagger-client/tree/v3.10.0)
2.1.32 | 2017-01-12 | 1.0, 1.1, 1.2 | [tag v2.1.32](https://github.com/swagger-api/swagger-client/tree/v2.1.32). This [release](https://github.com/swagger-api/swagger-client/releases/tag/v2.1.32) is only available on GitHub.

### Standalone JSON Schema

Swagger Client also resolves standalone JSON Schema 2020-12 documents: documents whose root `$schema` is
`https://json-schema.org/draft/2020-12/schema` (with or without an empty fragment) and which have no `openapi`,
`swagger`, `asyncapi` or `arazzo` field. Their `$ref`s resolve against `$id` base URIs, `$anchor`s and external documents as in
OpenAPI 3.1 Schema Objects, `allOf` is merged unless `mode: 'strict'`, and every `$dynamicRef` is left in place.
Documents with any other `$schema` value keep the generic resolver.

## Anonymized analytics

Swagger Client uses [Scarf](https://scarf.sh/) to collect [anonymized installation analytics](https://github.com/scarf-sh/scarf-js?tab=readme-ov-file#as-a-user-of-a-package-using-scarf-js-what-information-does-scarf-js-send-about-me). These analytics help support the maintainers of this library and ONLY run during installation. To [opt out](https://github.com/scarf-sh/scarf-js?tab=readme-ov-file#as-a-user-of-a-package-using-scarf-js-how-can-i-opt-out-of-analytics), you can set the `scarfSettings.enabled` field to `false` in your project's `package.json`:
Expand Down
2 changes: 2 additions & 0 deletions docs/usage/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ which means you can run `npm install --omit=optional` to speed up the installati
"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.11.3",
"@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.11.3",
"@swagger-api/apidom-parser-adapter-json": "^1.11.3",
"@swagger-api/apidom-parser-adapter-json-schema-json-2020-12": "^1.11.3",
"@swagger-api/apidom-parser-adapter-json-schema-yaml-2020-12": "^1.11.3",
"@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.11.3",
"@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.11.3",
"@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.11.3",
Expand Down
Loading