From 89b2e3225bc40b5f48d5046a9b949fd9f4662c6f Mon Sep 17 00:00:00 2001 From: dblock Date: Tue, 2 Jul 2024 15:54:19 -0400 Subject: [PATCH] Fixed `/_cat/pit_segments/{pit_id}`, `/_cat/cluster_manager`, `/_cat/allocation`, `/_cat/shards`, and `/_cat/thread_pool`. Signed-off-by: dblock --- .github/opensearch-cluster/docker-compose.yml | 3 +- CHANGELOG.md | 1 + spec/namespaces/cat.yaml | 10 ++- spec/schemas/cat.allocation.yaml | 14 ++-- spec/schemas/cat.cluster_manager.yaml | 23 ++++++ spec/schemas/cat.shards.yaml | 8 +- spec/schemas/cat.templates.yaml | 2 +- spec/schemas/cat.thread_pool.yaml | 8 +- tests/_core/aliases.yaml | 75 +++++++++++++++++++ tests/cat/aliases.yaml | 18 ++++- tests/cat/allocation.yaml | 11 +++ tests/cat/cluster_manager.yaml | 11 +++ tests/cat/count.yaml | 11 +++ tests/cat/fielddata.yaml | 13 ++++ tests/cat/nodeattrs.yaml | 14 ++++ tests/cat/nodes.yaml | 13 ++++ tests/cat/pending_tasks.yaml | 11 +++ tests/cat/pit_segments.yaml | 49 ++++++++++++ tests/cat/plugins.yaml | 13 ++++ tests/cat/recovery.yaml | 11 +++ tests/cat/repositories.yaml | 30 ++++++++ tests/cat/segment_replication.yaml | 11 +++ tests/cat/segments.yaml | 11 +++ tests/cat/shards.yaml | 29 +++++++ tests/cat/snapshots.yaml | 28 +++++++ tests/cat/tasks.yaml | 11 +++ tests/cat/templates.yaml | 39 ++++++++++ tests/cat/thread_pool.yaml | 21 ++++++ tests/indices/index.yaml | 2 +- tools/src/tester/SchemaValidator.ts | 10 ++- 30 files changed, 490 insertions(+), 21 deletions(-) create mode 100644 spec/schemas/cat.cluster_manager.yaml create mode 100644 tests/_core/aliases.yaml create mode 100644 tests/cat/allocation.yaml create mode 100644 tests/cat/cluster_manager.yaml create mode 100644 tests/cat/count.yaml create mode 100644 tests/cat/fielddata.yaml create mode 100644 tests/cat/nodeattrs.yaml create mode 100644 tests/cat/nodes.yaml create mode 100644 tests/cat/pending_tasks.yaml create mode 100644 tests/cat/pit_segments.yaml create mode 100644 tests/cat/plugins.yaml create mode 100644 tests/cat/recovery.yaml create mode 100644 tests/cat/repositories.yaml create mode 100644 tests/cat/segment_replication.yaml create mode 100644 tests/cat/segments.yaml create mode 100644 tests/cat/shards.yaml create mode 100644 tests/cat/snapshots.yaml create mode 100644 tests/cat/tasks.yaml create mode 100644 tests/cat/templates.yaml create mode 100644 tests/cat/thread_pool.yaml diff --git a/.github/opensearch-cluster/docker-compose.yml b/.github/opensearch-cluster/docker-compose.yml index 2f1e4363..f9c215ce 100644 --- a/.github/opensearch-cluster/docker-compose.yml +++ b/.github/opensearch-cluster/docker-compose.yml @@ -8,4 +8,5 @@ services: - "9600:9600" environment: - discovery.type=single-node - - "OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_PASSWORD:-myStrongPassword123!}" \ No newline at end of file + - "OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_PASSWORD:-myStrongPassword123!}" + - "path.repo=/tmp/opensearch/repo" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0077fb52..372522a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixed `pages_processed` in `/_plugins/_rollup` ([#341](https://github.com/opensearch-project/opensearch-api-specification/pull/341)) - Fixed `_bulk` spec request and response types ([#355](https://github.com/opensearch-project/opensearch-api-specification/pull/355)) - Fixed `text/plain` response in `/_cat` ([#357](https://github.com/opensearch-project/opensearch-api-specification/pull/357)) +- Fixed `/_cat/cluster_manager`, `/_cat/allocation`, `/_cat/shards`, and `/_cat/thread_pool` ([#373](https://github.com/opensearch-project/opensearch-api-specification/pull/373)) ### Security diff --git a/spec/namespaces/cat.yaml b/spec/namespaces/cat.yaml index 4f12c0e7..1ff7bf78 100644 --- a/spec/namespaces/cat.yaml +++ b/spec/namespaces/cat.yaml @@ -818,6 +818,14 @@ components: $ref: '../schemas/cat.allocation.yaml#/components/schemas/AllocationRecord' cat.cluster_manager@200: description: '' + content: + text/plain: + type: string + application/json: + schema: + type: array + items: + $ref: '../schemas/cat.cluster_manager.yaml#/components/schemas/ClusterManagerRecord' cat.count@200: description: '' content: @@ -1768,7 +1776,7 @@ components: name: full_id description: If `true`, return the full node ID. If `false`, return the shortened node ID. schema: - oneOf: + anyOf: - type: boolean - type: string default: false diff --git a/spec/schemas/cat.allocation.yaml b/spec/schemas/cat.allocation.yaml index 8c8913a4..a2318323 100644 --- a/spec/schemas/cat.allocation.yaml +++ b/spec/schemas/cat.allocation.yaml @@ -16,7 +16,7 @@ components: description: |- Disk space used by the node’s shards. Does not include disk space for the translog or unassigned shards. IMPORTANT: This metric double-counts disk space for hard-linked files, such as those created when shrinking, splitting, or cloning an index. - oneOf: + anyOf: - $ref: '_common.yaml#/components/schemas/ByteSize' - nullable: true type: string @@ -26,7 +26,7 @@ components: OpenSearch retrieves this metric from the node’s operating system (OS). The metric includes disk space for: OpenSearch, including the translog and unassigned shards; the node’s operating system; any other applications or files on the node. Unlike `disk.indices`, this metric does not double-count disk space for hard-linked files. - oneOf: + anyOf: - $ref: '_common.yaml#/components/schemas/ByteSize' - nullable: true type: string @@ -35,31 +35,31 @@ components: Free disk space available to OpenSearch. OpenSearch retrieves this metric from the node’s operating system. Disk-based shard allocation uses this metric to assign shards to nodes based on available disk space. - oneOf: + anyOf: - $ref: '_common.yaml#/components/schemas/ByteSize' - nullable: true type: string disk.total: description: Total disk space for the node, including in-use and available space. - oneOf: + anyOf: - $ref: '_common.yaml#/components/schemas/ByteSize' - nullable: true type: string disk.percent: description: Total percentage of disk space in use. Calculated as `disk.used / disk.total`. - oneOf: + anyOf: - $ref: '_common.yaml#/components/schemas/Percentage' - nullable: true type: string host: description: Network host for the node. Set using the `network.host` setting. - oneOf: + anyOf: - $ref: '_common.yaml#/components/schemas/Host' - nullable: true type: string ip: description: IP address and port for the node. - oneOf: + anyOf: - $ref: '_common.yaml#/components/schemas/Ip' - nullable: true type: string diff --git a/spec/schemas/cat.cluster_manager.yaml b/spec/schemas/cat.cluster_manager.yaml new file mode 100644 index 00000000..5b04fc47 --- /dev/null +++ b/spec/schemas/cat.cluster_manager.yaml @@ -0,0 +1,23 @@ +openapi: 3.1.0 +info: + title: Schemas of cat.cluster_manager category + description: Schemas of cat.cluster_manager category + version: 1.0.0 +paths: {} +components: + schemas: + ClusterManagerRecord: + type: object + properties: + id: + description: node id + type: string + host: + description: host name + type: string + ip: + description: ip address + type: string + node: + description: node name + type: string diff --git a/spec/schemas/cat.shards.yaml b/spec/schemas/cat.shards.yaml index f3621e6f..649780d8 100644 --- a/spec/schemas/cat.shards.yaml +++ b/spec/schemas/cat.shards.yaml @@ -29,19 +29,19 @@ components: type: string docs: description: The number of documents in the shard. - oneOf: + anyOf: - type: string - nullable: true type: string store: description: The disk space used by the shard. - oneOf: + anyOf: - type: string - nullable: true type: string ip: description: The IP address of the node. - oneOf: + anyOf: - type: string - nullable: true type: string @@ -50,7 +50,7 @@ components: type: string node: description: The name of node. - oneOf: + anyOf: - type: string - nullable: true type: string diff --git a/spec/schemas/cat.templates.yaml b/spec/schemas/cat.templates.yaml index aab5d128..81c04895 100644 --- a/spec/schemas/cat.templates.yaml +++ b/spec/schemas/cat.templates.yaml @@ -19,7 +19,7 @@ components: type: string version: description: The template version. - oneOf: + anyOf: - $ref: '_common.yaml#/components/schemas/VersionString' - nullable: true type: string diff --git a/spec/schemas/cat.thread_pool.yaml b/spec/schemas/cat.thread_pool.yaml index 759ecf7b..bc91a875 100644 --- a/spec/schemas/cat.thread_pool.yaml +++ b/spec/schemas/cat.thread_pool.yaml @@ -60,25 +60,25 @@ components: type: string core: description: The core number of active threads allowed in a scaling thread pool. - oneOf: + anyOf: - type: string - nullable: true type: string max: description: The maximum number of active threads allowed in a scaling thread pool. - oneOf: + anyOf: - type: string - nullable: true type: string size: description: The number of active threads allowed in a fixed thread pool. - oneOf: + anyOf: - type: string - nullable: true type: string keep_alive: description: The thread keep alive time. - oneOf: + anyOf: - type: string - nullable: true type: string diff --git a/tests/_core/aliases.yaml b/tests/_core/aliases.yaml new file mode 100644 index 00000000..f9d7dc48 --- /dev/null +++ b/tests/_core/aliases.yaml @@ -0,0 +1,75 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test aliases endpoints. +epilogues: + - path: /games + method: DELETE + status: [200, 404] +prologues: + - path: /{index} + method: PUT + parameters: + index: games +chapters: + - synopsis: Create an alias. + path: /{index}/_aliases/{name} + method: PUT + parameters: + index: games + name: jeux + - synopsis: Get an alias from index. + method: GET + path: /{index}/_alias/{name} + parameters: + index: games + name: jeux + response: + status: 200 + content_type: application/json + payload: + games: + aliases: + jeux: {} + - synopsis: Get an alias from _aliases. + method: GET + path: /_alias/{name} + parameters: + name: jeux + response: + status: 200 + content_type: application/json + payload: + games: + aliases: + jeux: {} + - synopsis: Multiple alias operations. + path: /_aliases + method: POST + request_body: + payload: + actions: + - remove: + index: games + alias: jeux + - add: + index: games + alias: plays1 + - add: + index: games + alias: plays2 + response: + status: 200 + payload: + acknowledged: true + - synopsis: Delete an alias from _aliases. + path: /{index}/_aliases/{name} + method: DELETE + parameters: + index: games + name: plays1 + - synopsis: Delete an alias from _alias. + path: /{index}/_alias/{name} + method: DELETE + parameters: + index: games + name: plays2 diff --git a/tests/cat/aliases.yaml b/tests/cat/aliases.yaml index d589354a..5b50563b 100644 --- a/tests/cat/aliases.yaml +++ b/tests/cat/aliases.yaml @@ -1,6 +1,20 @@ $schema: ../../json_schemas/test_story.schema.yaml description: Test cat/aliases endpoints. +epilogues: + - path: /games + method: DELETE + status: [200, 404] +prologues: + - path: /{index} + method: PUT + parameters: + index: games + - path: /{index}/_aliases/{name} + method: PUT + parameters: + index: games + name: jeux chapters: - synopsis: Cat with a text response. path: /_cat/aliases @@ -16,4 +30,6 @@ chapters: response: status: 200 content_type: application/json - payload: [] + payload: + - alias: jeux + index: games diff --git a/tests/cat/allocation.yaml b/tests/cat/allocation.yaml new file mode 100644 index 00000000..e5d22d0a --- /dev/null +++ b/tests/cat/allocation.yaml @@ -0,0 +1,11 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/allocation endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/allocation + method: GET + parameters: + format: json + response: + status: 200 diff --git a/tests/cat/cluster_manager.yaml b/tests/cat/cluster_manager.yaml new file mode 100644 index 00000000..ab27df45 --- /dev/null +++ b/tests/cat/cluster_manager.yaml @@ -0,0 +1,11 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/cluster_manager endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/cluster_manager + method: GET + parameters: + format: json + response: + status: 200 diff --git a/tests/cat/count.yaml b/tests/cat/count.yaml new file mode 100644 index 00000000..e5bc876a --- /dev/null +++ b/tests/cat/count.yaml @@ -0,0 +1,11 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/count endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/count + method: GET + parameters: + format: json + response: + status: 200 diff --git a/tests/cat/fielddata.yaml b/tests/cat/fielddata.yaml new file mode 100644 index 00000000..0eaae692 --- /dev/null +++ b/tests/cat/fielddata.yaml @@ -0,0 +1,13 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/fielddata endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/fielddata + method: GET + parameters: + format: json + response: + status: 200 + payload: + - field: log_types # from security-analytics diff --git a/tests/cat/nodeattrs.yaml b/tests/cat/nodeattrs.yaml new file mode 100644 index 00000000..98e1f8b2 --- /dev/null +++ b/tests/cat/nodeattrs.yaml @@ -0,0 +1,14 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/nodeattrs endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/nodeattrs + method: GET + parameters: + format: json + response: + status: 200 + payload: + - attr: shard_indexing_pressure_enabled + value: 'true' diff --git a/tests/cat/nodes.yaml b/tests/cat/nodes.yaml new file mode 100644 index 00000000..ab484e29 --- /dev/null +++ b/tests/cat/nodes.yaml @@ -0,0 +1,13 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/nodes endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/nodes + method: GET + parameters: + format: json + response: + status: 200 + payload: + - cluster_manager: '*' diff --git a/tests/cat/pending_tasks.yaml b/tests/cat/pending_tasks.yaml new file mode 100644 index 00000000..e76ad332 --- /dev/null +++ b/tests/cat/pending_tasks.yaml @@ -0,0 +1,11 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/pending_tasks endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/pending_tasks + method: GET + parameters: + format: json + response: + status: 200 diff --git a/tests/cat/pit_segments.yaml b/tests/cat/pit_segments.yaml new file mode 100644 index 00000000..478a39ef --- /dev/null +++ b/tests/cat/pit_segments.yaml @@ -0,0 +1,49 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/pit_segments endpoints. +epilogues: + - path: /games + method: DELETE + status: [200, 404] +prologues: + - path: /games/_doc + method: POST + parameters: + refresh: true + request_body: + payload: + title: Monopoly + status: [201] +chapters: + - synopsis: Create a PIT. + id: create_pit + path: /{index}/_search/point_in_time + method: POST + parameters: + index: + - games + keep_alive: 1m + output: + pit_id: "payload.pit_id" + - synopsis: Cat _all with a json response. + path: /_cat/pit_segments/_all + method: GET + parameters: + format: json + response: + status: 200 + payload: + - index: games + - synopsis: Cat pit_segments/pit_id with a json response. + path: /_cat/pit_segments + method: GET + parameters: + format: json + request_body: + payload: + pit_id: + - ${create_pit.pit_id} + response: + status: 200 + payload: + - index: games diff --git a/tests/cat/plugins.yaml b/tests/cat/plugins.yaml new file mode 100644 index 00000000..e2d66551 --- /dev/null +++ b/tests/cat/plugins.yaml @@ -0,0 +1,13 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/plugins endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/plugins + method: GET + parameters: + format: json + response: + status: 200 + payload: + - component: opensearch-alerting diff --git a/tests/cat/recovery.yaml b/tests/cat/recovery.yaml new file mode 100644 index 00000000..ab4a9647 --- /dev/null +++ b/tests/cat/recovery.yaml @@ -0,0 +1,11 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/recovery endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/recovery + method: GET + parameters: + format: json + response: + status: 200 diff --git a/tests/cat/repositories.yaml b/tests/cat/repositories.yaml new file mode 100644 index 00000000..d2817b9e --- /dev/null +++ b/tests/cat/repositories.yaml @@ -0,0 +1,30 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/repositories endpoints. +epilogues: + - path: /_snapshot/{repository} + method: DELETE + status: [200, 404] + parameters: + repository: my-fs-repository +prologues: + - path: /_snapshot/{repository} + method: PUT + parameters: + repository: my-fs-repository + request_body: + payload: + type: fs + settings: + location: "/tmp/opensearch/repo" +chapters: + - synopsis: Cat with a json response. + path: /_cat/repositories + method: GET + parameters: + format: json + response: + status: 200 + payload: + - id: my-fs-repository + type: fs diff --git a/tests/cat/segment_replication.yaml b/tests/cat/segment_replication.yaml new file mode 100644 index 00000000..685deee0 --- /dev/null +++ b/tests/cat/segment_replication.yaml @@ -0,0 +1,11 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/segment_replication endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/segment_replication + method: GET + parameters: + format: json + response: + status: 200 diff --git a/tests/cat/segments.yaml b/tests/cat/segments.yaml new file mode 100644 index 00000000..8d46443e --- /dev/null +++ b/tests/cat/segments.yaml @@ -0,0 +1,11 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/segments endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/segments + method: GET + parameters: + format: json + response: + status: 200 diff --git a/tests/cat/shards.yaml b/tests/cat/shards.yaml new file mode 100644 index 00000000..9bf960b2 --- /dev/null +++ b/tests/cat/shards.yaml @@ -0,0 +1,29 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/shards endpoints. +epilogues: + - path: /games + method: DELETE + status: [200, 404] +prologues: + - path: /{index} + method: PUT + parameters: + index: games +chapters: + - synopsis: Cat all with a json response. + path: /_cat/shards + method: GET + parameters: + format: json + - synopsis: Cat index shards with a json response. + path: /_cat/shards/{index} + method: GET + parameters: + format: json + index: games + response: + status: 200 + payload: + - index: games + diff --git a/tests/cat/snapshots.yaml b/tests/cat/snapshots.yaml new file mode 100644 index 00000000..5c70685e --- /dev/null +++ b/tests/cat/snapshots.yaml @@ -0,0 +1,28 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/snapshots endpoints. +epilogues: + - path: /_snapshot/{repository} + method: DELETE + status: [200, 404] + parameters: + repository: my-fs-repository +chapters: + - synopsis: Create a snapshot repository. + path: /_snapshot/{repository} + method: PUT + parameters: + repository: my-fs-repository + request_body: + payload: + type: fs + settings: + location: "/tmp/opensearch/repo" + - synopsis: Cat with a json response. + path: /_cat/snapshots/{repository} + method: GET + parameters: + format: json + repository: my-fs-repository + response: + status: 200 diff --git a/tests/cat/tasks.yaml b/tests/cat/tasks.yaml new file mode 100644 index 00000000..dbde807f --- /dev/null +++ b/tests/cat/tasks.yaml @@ -0,0 +1,11 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/tasks endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/tasks + method: GET + parameters: + format: json + response: + status: 200 diff --git a/tests/cat/templates.yaml b/tests/cat/templates.yaml new file mode 100644 index 00000000..44c128fb --- /dev/null +++ b/tests/cat/templates.yaml @@ -0,0 +1,39 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/templates endpoints. +epilogues: + - path: /_index_template/daily_logs + method: DELETE + status: [200, 404] +prologues: + - path: /_index_template/daily_logs + method: PUT + request_body: + payload: + index_patterns: + - "logs*" + priority: 0 + template: + settings: + number_of_shards: 2 + number_of_replicas: 2 +chapters: + - synopsis: Cat with a json response. + path: /_cat/templates + method: GET + parameters: + format: json + response: + status: 200 + payload: + - name: daily_logs + - synopsis: Cat a specific template with a json response. + path: /_cat/templates/{name} + method: GET + parameters: + format: json + name: daily_logs + response: + status: 200 + payload: + - name: daily_logs diff --git a/tests/cat/thread_pool.yaml b/tests/cat/thread_pool.yaml new file mode 100644 index 00000000..54928665 --- /dev/null +++ b/tests/cat/thread_pool.yaml @@ -0,0 +1,21 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/thread_pool endpoints. +chapters: + - synopsis: Cat with a json response. + path: /_cat/thread_pool + method: GET + parameters: + format: json + response: + status: 200 + - synopsis: Cat of a specific thread pool with a json response. + path: /_cat/thread_pool/{thread_pool_patterns} + method: GET + parameters: + format: json + thread_pool_patterns: generic + response: + status: 200 + payload: + - name: generic diff --git a/tests/indices/index.yaml b/tests/indices/index.yaml index 456078f4..3ccd60ae 100644 --- a/tests/indices/index.yaml +++ b/tests/indices/index.yaml @@ -28,7 +28,7 @@ chapters: response: status: 200 - - synopsis: Create an index named `games` with default settings, + - synopsis: Create an index named `games` with default settings. path: /{index} method: PUT parameters: diff --git a/tools/src/tester/SchemaValidator.ts b/tools/src/tester/SchemaValidator.ts index c8f13e09..9fdc0dfb 100644 --- a/tools/src/tester/SchemaValidator.ts +++ b/tools/src/tester/SchemaValidator.ts @@ -15,6 +15,14 @@ import { type Evaluation, Result } from './types/eval.types' import { Logger } from 'Logger' import { to_json } from '../helpers' +const ADDITIONAL_KEYWORDS = [ + 'discriminator', + 'x-version-added', + 'x-version-deprecated', + 'x-version-removed', + 'x-deprecation-message' +] + export default class SchemaValidator { private readonly ajv: AJV private readonly logger: Logger @@ -23,8 +31,8 @@ export default class SchemaValidator { this.logger = logger this.ajv = new AJV({ allErrors: true, strict: true }) addFormats(this.ajv) + for (const keyword of ADDITIONAL_KEYWORDS) this.ajv.addKeyword(keyword) ajv_errors(this.ajv, { singleError: true }) - this.ajv.addKeyword('discriminator') const schemas = spec.components?.schemas ?? {} for (const key in schemas) this.ajv.addSchema(schemas[key], `#/components/schemas/${key}`) }