close
Skip to content
Open
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
4 changes: 4 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ If the keyword is validating data type that is different from the type(s) in its

See [User defined keywords](./keywords.md) for more details.

### ajv.allowCustomAnnotations(): Ajv

Allows unknown schema keywords prefixed with `x-` when `strictSchema` is enabled. These keywords are treated as annotations and ignored by validation.

### ajv.getKeyword(keyword: string): object | boolean

Returns keyword definition, `false` if the keyword is unknown.
Expand Down
6 changes: 6 additions & 0 deletions docs/strict-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ or use the convenience method `addVocabulary` for multiple keywords
ajv.addVocabulary(["allowed1", "allowed2"]) // simply calls addKeyword multiple times
```

To allow all [custom annotation keywords](https://json-schema.org/blog/posts/custom-annotations-will-continue) prefixed with `x-`, use `allowCustomAnnotations`:

```javascript
ajv.allowCustomAnnotations()
```

#### Ignored "additionalItems" keyword

JSON Schema section [9.3.1.2](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2) requires to ignore "additionalItems" keyword if "items" keyword is absent or if it is not an array of items. This is inconsistent with the interaction of "additionalProperties" and "properties", and may cause unexpected results.
Expand Down
1 change: 1 addition & 0 deletions lib/compile/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface ValidationRules {
post: RuleGroup
all: {[Key in string]?: boolean | Rule} // rules that have to be validated
keywords: {[Key in string]?: boolean} // all known keywords (superset of "all")
allowCustomAnnotations?: boolean // allow ignored x-* annotation keywords
types: ValidationTypes
}

Expand Down
9 changes: 8 additions & 1 deletion lib/compile/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,17 @@ export function checkUnknownRules(it: SchemaCxt, schema: AnySchema = it.schema):
if (typeof schema === "boolean") return
const rules = self.RULES.keywords
for (const key in schema) {
if (!rules[key]) checkStrictMode(it, `unknown keyword: "${key}"`)
if (!rules[key] && !allowedCustomAnnotation(self.RULES, key)) {
checkStrictMode(it, `unknown keyword: "${key}"`)
}
}
}

// https://json-schema.org/blog/posts/custom-annotations-will-continue
function allowedCustomAnnotation(RULES: ValidationRules, keyword: string): boolean {
return RULES.allowCustomAnnotations === true && keyword.startsWith("x-")
}

export function schemaHasRules(
schema: AnySchema,
rules: {[Key in string]?: boolean | Rule}
Expand Down
5 changes: 5 additions & 0 deletions lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,11 @@ export default class Ajv {
return this
}

allowCustomAnnotations(): Ajv {
this.RULES.allowCustomAnnotations = true
return this
}

addKeyword(
kwdOrDef: string | KeywordDefinition,
def?: KeywordDefinition // deprecated
Expand Down
5 changes: 5 additions & 0 deletions lib/standalone/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ export default class AjvPack {
this.ajv.addKeyword.call(this.ajv, ...args)
return this
}

allowCustomAnnotations(): AjvPack {
this.ajv.allowCustomAnnotations.call(this.ajv)
return this
}
}
50 changes: 50 additions & 0 deletions spec/options/strictKeywords.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,56 @@ describe("strict option with keywords (replaced strictKeywords)", () => {
})
})

describe("custom annotations", () => {
it('should throw an error for "x-" prefixed keywords by default', () => {
const ajv = new _Ajv()
should.throw(() => ajv.compile({"x-annotation": 1}), /unknown keyword: "x-annotation"/)
})

it('should allow ignored "x-" prefixed keywords when enabled', () => {
const ajv = new _Ajv()
const result = ajv.allowCustomAnnotations()
result.should.equal(ajv)

const validate = ajv.compile({
type: "object",
"x-root": 1,
properties: {
foo: {
type: "string",
"x-property": {description: "ignored"},
},
},
anyOf: [{"x-sub-schema": true}],
})

validate({foo: "bar"}).should.equal(true)
})

it("should still throw an error for other unknown keywords", () => {
const ajv = new _Ajv()
ajv.allowCustomAnnotations()

should.throw(
() => ajv.compile({type: "object", "x-annotation": 1, unknownKeyword: 1}),
/unknown keyword: "unknownKeyword"/
)
})

it('should not log a warning for "x-" prefixed keywords when enabled', () => {
const output: any = {}
const ajv = new _Ajv({
strict: "log",
logger: getLogger(output),
})
ajv.allowCustomAnnotations()

ajv.compile({"x-annotation": 1})

should.not.exist(output.warning)
})
})

function getLogger(output) {
return {
log() {
Expand Down
Loading