# schema-keyword-support

Source: https://apigenie.pl/docs/linter/openapi/rules/schema-keyword-support

This rule reports schema keywords that the tools consuming the document are unlikely to support,
and suggests the rewrite where one exists.

## Rule Details

OpenAPI 3.1 schemas are JSON Schema 2020-12, but much of the tooling around them is not. Validators
of older drafts, OpenAPI 3.0 tools and most code generators ignore the keywords they don't know, so
`unevaluatedProperties: false` stops rejecting anything, a `prefixItems` tuple turns into an
untyped array and an `if`/`then` constraint silently disappears from the generated client. The
rule checks each schema against one or more **targets** and reports, once per keyword, the first
target that does not support it:

| Target | Who it stands for |
| ------ | ----------------- |
| `draft4` | Validators stuck at draft-04, e.g. NJsonSchema's default dialect, older PHP and Python libraries |
| `draft6` | Draft-06 validators |
| `draft7` | Validators stopping at draft-07: gojsonschema, everit-org/json-schema, Ajv's default export; also the default schema dialect of AsyncAPI 2.x and 3.0 |
| `draft2019-09` | Validators without draft 2020-12 |
| `openapi30` | The Schema Object of OpenAPI 3.0 and the tools built for it, e.g. NSwag |
| `codegen` | The keywords most code generators ignore or can't express in types (the default) |

What each target lacks, and the suggested rewrite:

| Keyword or form | Not supported by | Suggested rewrite |
| --------------- | ---------------- | ----------------- |
| `unevaluatedProperties` | drafts 4 to 7, `openapi30`, `codegen` | merge the `allOf` members into one schema and use `additionalProperties` |
| `unevaluatedItems` | drafts 4 to 7 | `additionalItems` next to the array form of `items` |
| `unevaluatedItems` | `openapi30`, `codegen` | none |
| `prefixItems` | drafts 4 to 2019-09 | the array form of `items`, `additionalItems` for the rest |
| `prefixItems` | `openapi30`, `codegen` | an object with named properties |
| `$dynamicRef`, `$recursiveRef` | drafts 4 to 7, `openapi30`, `codegen` | `$ref` to the schema it resolves to |
| `$dynamicRef` | `draft2019-09` | `$recursiveRef` |
| `$dynamicAnchor`, `$recursiveAnchor` | drafts 4 to 7, `openapi30`, `codegen` | drop it once the reference to it is a `$ref` |
| `$anchor` | drafts 4 to 7, `openapi30`, `codegen` | reference the schema by a JSON pointer |
| `$vocabulary` | drafts 4 to 7, `openapi30`, `codegen` | drop it |
| `$defs` | drafts 4 to 7 | `definitions` |
| `$defs` | `openapi30` | move the schemas to `components/schemas` |
| `dependentRequired` | drafts 4 to 7 | `dependencies` with an array of property names |
| `dependentSchemas` | drafts 4 to 7 | `dependencies` with a schema |
| `dependentRequired`, `dependentSchemas`, `dependencies` | `openapi30`, `codegen` | none |
| `dependencies` | `draft2019-09` | `dependentRequired` or `dependentSchemas` |
| `if` (with `then`/`else`) | `draft4`, `draft6`, `openapi30`, `codegen` | `oneOf` with one alternative per case |
| `contentSchema`, `minContains`, `maxContains` | drafts 4 to 7, `openapi30`, `codegen` | none |
| `contains` | `draft4`, `openapi30`, `codegen` | none |
| `propertyNames` | `draft4`, `openapi30` | none |
| `const` | `draft4`, `openapi30` | an `enum` with one value |
| `patternProperties` | `openapi30` | `properties` for fixed names, `additionalProperties` for the rest |
| `patternProperties` | `codegen` | `additionalProperties` |
| `not` | `codegen` | none |
| `additionalItems` | `openapi30` | none |
| `$id`, `$schema`, `$comment`, `examples`, `contentEncoding`, `contentMediaType` | `openapi30` | drop `$id` and `$schema`; `description` for `$comment`; `example`; `format: byte` or `format: binary` |
| `$id` | `draft4` | `id` |
| a `type` array | `openapi30` | one `type` with `nullable: true`, or `anyOf` |
| a `type` array of several non-null types | `codegen` | `oneOf` with one schema per type |
| `type: null` | `openapi30` | `nullable: true` |
| a numeric `exclusiveMinimum`/`exclusiveMaximum` | `draft4`, `openapi30` | `minimum`/`maximum` with the boolean form |
| a boolean `exclusiveMinimum`/`exclusiveMaximum` | drafts 6 to 2019-09 | the bound as the value |
| `nullable: true` | drafts 4 to 2019-09 | add `"null"` to `type` |
| `$ref` with keywords that constrain the value | drafts 4 to 7, `openapi30`, `codegen` | wrap the reference in `allOf`; the siblings are ignored otherwise |

Annotations a validator ignores without harm (`description`, `examples`, `readOnly`, `deprecated`,
`$comment`, the content keywords) are reported only for `openapi30`, whose Schema Object allows
no keywords besides its own and `x-` extensions. `then` and `else` are covered by the report on
`if`. A `$ref` next to annotations only (`description`, `summary`, `example`, ...) is not reported.

`apigenie convert --from jsonschema --to draft7|draft4|openapi30` performs most of these rewrites
on a standalone JSON Schema document, including the `allOf` flattening that replaces
`unevaluatedProperties`.

## Configuration

The options are a target name, an array of target names, or an object with:

- `targets`: a target name or an array of target names; `codegen` when omitted.
- `allow`: keywords never reported, for the ones your tools are known to support.

**Configuration Example 1**. Keep a 3.1 document usable by OpenAPI 3.0 tooling.

```yaml
lint:
  rules:
    schema-keyword-support:
      - warning
      - openapi30
```

**Configuration Example 2**. Check against draft-07 validators and the code generators, accepting
`not`.

```yaml
lint:
  rules:
    schema-keyword-support:
      - warning
      - targets: [draft7, codegen]
        allow: [not]
```

## Examples

**Good**. A closed object that every target understands.

```yaml
components:
  schemas:
    Pet:
      type: object
      required: [name]
      properties:
        name:
          type: string
      additionalProperties: false
```

**Bad**. `unevaluatedProperties` is ignored by OpenAPI 3.0 tools, draft-07 validators and most code
generators, so they accept any extra property.

```yaml
components:
  schemas:
    Pet:
      allOf:
        - $ref: '#/components/schemas/Animal'
      properties:
        name:
          type: string
      unevaluatedProperties: false
```

## When Not to Use It

When every consumer of the document is known to support JSON Schema 2020-12 in full, e.g. a
validator such as Ajv 2020, networknt, santhosh-tekuri/jsonschema or Python's jsonschema, and no
code is generated from it.

## Compatibility

This rule is compatible with all OpenAPI 3.x versions. In OpenAPI 3.0 documents it finds keywords
the Schema Object does not have (with the `openapi30` target) and the OpenAPI forms that JSON
Schema validators don't understand (with the draft targets).
