-
Notifications
You must be signed in to change notification settings - Fork 2k
fix(validators): honor declared draft-07/06 JSON Schema dialects instead of rejecting them #2534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| '@modelcontextprotocol/server': patch | ||
| '@modelcontextprotocol/client': patch | ||
| --- | ||
|
|
||
| The default validator now honors declared 2019-09 and draft-07/06 dialects instead of rejecting them: a schema stamped `"$schema": "http://json-schema.org/draft-07/schema#"` (zod-to-json-schema's default output) validates with draft-07 semantics, and a 2019-09 stamp (zod-to-json-schema's `2019-09`/`openAi` targets) with 2019-09 semantics, on both the Ajv and Cloudflare Workers providers (one documented engine difference: classic Ajv evaluates keywords alongside `$ref`, which draft-07 says to ignore — see the migration guide). Schemas with no `$schema` still validate as 2020-12, and unknown dialects still produce the typed error (now listing the supported dialects: 2020-12, 2019-09, draft-07, draft-06). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,28 +10,21 @@ | |
|
|
||
| import { Validator } from '@cfworker/json-schema'; | ||
|
|
||
| import { declaredDialect } from './dialects'; | ||
| import type { JsonSchemaType, JsonSchemaValidator, jsonSchemaValidator, JsonSchemaValidatorResult } from './types'; | ||
|
|
||
| /** | ||
| * JSON Schema draft version supported by `@cfworker/json-schema`. | ||
| */ | ||
| export type CfWorkerSchemaDraft = '4' | '7' | '2019-09' | '2020-12'; | ||
|
|
||
| /** | ||
| * Canonical 2020-12 `$schema` URIs (http + https variants, trailing-`#` stripped). When a schema | ||
| * declares anything else and no `{draft}` is forced, the provider throws a plain `Error`. | ||
| */ | ||
| const DRAFT_2020_12_URIS: ReadonlySet<string> = new Set([ | ||
| 'https://json-schema.org/draft/2020-12/schema', | ||
| 'http://json-schema.org/draft/2020-12/schema' | ||
| ]); | ||
|
|
||
| /** | ||
| * `@cfworker/json-schema`-backed JSON Schema validator. See | ||
| * `@modelcontextprotocol/{client,server}/validators/cf-worker` for the customisation entry point. | ||
| * | ||
| * Default validates as **JSON Schema 2020-12** (SEP-1613). Schemas declaring a different | ||
| * `$schema` are rejected with a plain `Error`. Passing an explicit `draft` to the constructor | ||
| * Default dispatches on the schema's declared dialect: no `$schema` or 2020-12 → `'2020-12'` | ||
| * (SEP-1613); 2019-09 → `'2019-09'`; draft-07 or draft-06 → `'7'`. Schemas declaring any other `$schema` are rejected | ||
| * with a plain `Error`. Passing an explicit `draft` to the constructor | ||
| * overrides this — that draft is used for every schema regardless of `$schema`. | ||
| * | ||
| * @example Use with default configuration (2020-12, shortcircuit on) | ||
|
|
@@ -58,14 +51,24 @@ | |
| * @param options - Configuration options | ||
| * @param options.shortcircuit - If `true`, stop validation after first error (default: `true`) | ||
| * @param options.draft - JSON Schema draft version to force for every schema. When set, the | ||
| * `$schema` check is skipped. When omitted, the provider validates as 2020-12 and rejects | ||
| * schemas declaring a different `$schema`. | ||
| * `$schema` dispatch is skipped. When omitted, the provider dispatches on each schema's | ||
| * declared `$schema` (2020-12, 2019-09, draft-07, draft-06; absent means 2020-12) and rejects others. | ||
| */ | ||
| constructor(options?: { shortcircuit?: boolean; draft?: CfWorkerSchemaDraft }) { | ||
| this.shortcircuit = options?.shortcircuit ?? true; | ||
| this.draft = options?.draft; | ||
| } | ||
|
|
||
| /** | ||
| * Pick the engine draft for a schema's declared dialect (a caller-forced `{draft}` bypasses | ||
| * this — do not second-guess by `$schema`). No `$schema` or 2020-12 → `'2020-12'`; 2019-09 → | ||
| * `'2019-09'`; draft-07 or draft-06 → `'7'`; anything else → `Error`. | ||
| */ | ||
| private _draftFor(schema: JsonSchemaType): CfWorkerSchemaDraft { | ||
| const dialect = declaredDialect(schema, 'pass an explicit { draft } to CfWorkerJsonSchemaValidator to validate other dialects.'); | ||
| return dialect === 'draft-7' ? '7' : dialect; | ||
| } | ||
|
|
||
|
Check warning on line 71 in packages/core-internal/src/validators/cfWorkerProvider.ts
|
||
|
Comment on lines
+63
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 A second, undocumented draft-07 engine divergence: @cfworker/json-schema's dereference() doesn't treat draft-07 Extended reasoning...What the bug is. Routing declared draft-07/06 schemas to Step-by-step proof (verified against the pinned const schema = {
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
definitions: { addr: { type: 'string' } },
properties: { type: { type: 'string' }, billing: {} },
// canonical draft-07 idiom: 'when `type` is present, constrain `billing`'
dependencies: { type: { properties: { billing: { $ref: '#/definitions/addr' } } } }
};
// cfworker (browser/Workers default path):
validate({ billing: 'x' }) // → valid: true (dependency not triggered)
validate({ type: 'card', billing: 'x' }) // → THROWS Error('Unresolved $ref "#/definitions/addr"')
// classic Ajv (Node default path, provider's exact options):
// → true for valid data, false for invalid data — correct draft-07 behaviorThe same throw reproduces for entry keys Why existing safeguards don't catch it. The Impact. A spec-valid draft-07 tool with spec-valid Why nit rather than blocking. Nothing that worked pre-PR regresses: before this change, any draft-07-declared schema was rejected pre-wire with the typed error on both platforms (100% failure), so the divergence is newly created surface, not a regression. The trigger intersection is narrow — schema-form How to fix. Match the treatment the PR gave the $ref-siblings divergence: document it in the |
||
| /** | ||
| * Create a validator for the given JSON Schema | ||
| * | ||
|
|
@@ -75,22 +78,7 @@ | |
| * @returns A validator function that validates input data | ||
| */ | ||
| getValidator<T>(schema: JsonSchemaType): JsonSchemaValidator<T> { | ||
| // Caller forced a draft — use it for everything; do not second-guess by `$schema`. | ||
| if ( | ||
| this.draft === undefined && | ||
| '$schema' in schema && | ||
| typeof schema.$schema === 'string' && | ||
| !DRAFT_2020_12_URIS.has(schema.$schema.replace(/#$/, '')) | ||
| ) { | ||
| const declared = schema.$schema.slice(0, 200); | ||
| throw new Error( | ||
| `JSON Schema declares an unsupported dialect ("$schema": "${declared}"). ` + | ||
| `The default validator supports JSON Schema 2020-12 only; pass an explicit ` + | ||
| `{ draft } to CfWorkerJsonSchemaValidator to validate other dialects.` | ||
| ); | ||
| } | ||
|
|
||
| const draft = this.draft ?? '2020-12'; | ||
| const draft = this.draft ?? this._draftFor(schema); | ||
| // Cast to the cfworker Schema type - our JsonSchemaType is structurally compatible | ||
| const validator = new Validator(schema as ConstructorParameters<typeof Validator>[0], draft, this.shortcircuit); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this still a default?