diff --git a/json_schemas/_info.schema.yaml b/json_schemas/_info.schema.yaml new file mode 100644 index 00000000..77a2257b --- /dev/null +++ b/json_schemas/_info.schema.yaml @@ -0,0 +1,44 @@ +$schema: http://json-schema.org/draft-07/schema# + +type: object +properties: + title: + type: string + summary: + type: string + description: + type: string + termsOfService: + type: string + format: uri + contact: + $comment: https://spec.openapis.org/oas/v3.1.0#contact-object + type: object + properties: + name: + type: string + url: + type: string + format: uri + email: + type: string + format: email + license: + $comment: https://spec.openapis.org/oas/v3.1.0#license-object + type: object + properties: + name: + type: string + identifier: + type: string + url: + type: string + format: uri + required: + - name + version: + type: string +required: + - title + - version + - $schema \ No newline at end of file diff --git a/json_schemas/_superseded_operations.yaml b/json_schemas/_superseded_operations.schema.yaml similarity index 99% rename from json_schemas/_superseded_operations.yaml rename to json_schemas/_superseded_operations.schema.yaml index ed298d7f..a49f6671 100644 --- a/json_schemas/_superseded_operations.yaml +++ b/json_schemas/_superseded_operations.schema.yaml @@ -1,4 +1,5 @@ $schema: http://json-schema.org/draft-07/schema# + type: object patternProperties: ^\$schema$: diff --git a/spec/_global_parameters.yaml b/spec/_global_parameters.yaml index 9a2cd00e..e1f1b5ee 100644 --- a/spec/_global_parameters.yaml +++ b/spec/_global_parameters.yaml @@ -4,34 +4,34 @@ info: version: '' components: parameters: - _global::query.pretty: + pretty: name: pretty in: query description: Whether to pretty format the returned JSON response. schema: type: boolean default: false - _global::query.human: + human: name: human in: query description: Whether to return human readable values for statistics. schema: type: boolean default: true - _global::query.error_trace: + error_trace: name: error_trace in: query description: Whether to include the stack trace of returned errors. schema: type: boolean default: false - _global::query.source: + source: name: source in: query description: The URL-encoded request definition. Useful for libraries that do not accept a request body for non-POST requests. schema: type: string - _global::query.filter_path: + filter_path: name: filter_path in: query description: Comma-separated list of filters used to reduce the response. diff --git a/spec/_info.yaml b/spec/_info.yaml index 9da26bfa..f71d35dd 100644 --- a/spec/_info.yaml +++ b/spec/_info.yaml @@ -1,3 +1,4 @@ -title: OpenSearch API -description: OpenSearch API +$schema: ../json_schemas/_info.schema.yaml + +title: OpenSearch API Specification version: 1.0.0 \ No newline at end of file diff --git a/spec/_superseded_operations.yaml b/spec/_superseded_operations.yaml index a3d4faa0..70f9e2bf 100644 --- a/spec/_superseded_operations.yaml +++ b/spec/_superseded_operations.yaml @@ -1,4 +1,4 @@ -$schema: ../json_schemas/_superseded_operations.yaml +$schema: ../json_schemas/_superseded_operations.schema.yaml /_opendistro/_alerting/destinations: superseded_by: /_plugins/_alerting/destinations diff --git a/spec/namespaces/notifications.yaml b/spec/namespaces/notifications.yaml index c34ad915..37973508 100644 --- a/spec/namespaces/notifications.yaml +++ b/spec/namespaces/notifications.yaml @@ -254,18 +254,7 @@ components: channel_list: type: array items: - type: object - properties: - config_id: - type: string - name: - type: string - description: - type: string - config_type: - $ref: '../schemas/notifications._common.yaml#/components/schemas/NotificationConfigType' - is_enabled: - type: boolean + $ref: '../schemas/notifications._common.yaml#/components/schemas/NotificationChannel' notifications.list_features@200: description: '' content: diff --git a/spec/schemas/notifications._common.yaml b/spec/schemas/notifications._common.yaml index e85bee4c..2ddcfc6a 100644 --- a/spec/schemas/notifications._common.yaml +++ b/spec/schemas/notifications._common.yaml @@ -291,3 +291,16 @@ components: type: object additionalProperties: type: string + NotificationChannel: + type: object + properties: + config_id: + type: string + name: + type: string + description: + type: string + config_type: + $ref: '#/components/schemas/NotificationConfigType' + is_enabled: + type: boolean diff --git a/tools/eslint.config.mjs b/tools/eslint.config.mjs index 1a674024..9e13e3c1 100644 --- a/tools/eslint.config.mjs +++ b/tools/eslint.config.mjs @@ -19,7 +19,7 @@ export default [ '@typescript-eslint/dot-notation': 'error', '@typescript-eslint/explicit-function-return-type': 'error', '@typescript-eslint/naming-convention': ['error', - { selector: 'classProperty', modifiers: ['readonly'], format: ['UPPER_CASE'], leadingUnderscore: 'allow' }, + { selector: 'classProperty', modifiers: ['static', 'readonly'], format: ['UPPER_CASE'], leadingUnderscore: 'allow' }, { selector: 'memberLike', modifiers: ['public'], format: ['snake_case'], leadingUnderscore: 'forbid' }, { selector: 'memberLike', modifiers: ['private', 'protected'], format: ['snake_case'], leadingUnderscore: 'require' }, { selector: 'variableLike', format: ['snake_case', 'UPPER_CASE'], leadingUnderscore: 'allow' }, diff --git a/tools/helpers.ts b/tools/helpers.ts index 09e6fcb6..cdbb5af5 100644 --- a/tools/helpers.ts +++ b/tools/helpers.ts @@ -43,8 +43,10 @@ export function sort_by_keys (obj: Record, priorities: string[] = [ }) } -export function read_yaml (file_path: string): Record { - return YAML.parse(fs.readFileSync(file_path, 'utf8')) +export function read_yaml (file_path: string, exclude_schema: boolean = false): Record { + const doc = YAML.parse(fs.readFileSync(file_path, 'utf8')) + if (exclude_schema) delete doc.$schema + return doc } export function write_yaml (file_path: string, content: Record): void { diff --git a/tools/linter/InlineObjectSchemaValidator.ts b/tools/linter/InlineObjectSchemaValidator.ts new file mode 100644 index 00000000..7acf5eb4 --- /dev/null +++ b/tools/linter/InlineObjectSchemaValidator.ts @@ -0,0 +1,44 @@ +import type NamespacesFolder from './components/NamespacesFolder' +import type SchemasFolder from './components/SchemasFolder' +import { type ValidationError } from '../types' +import { SchemaVisitor } from './utils/SpecificationVisitor' +import { is_ref, type MaybeRef, SpecificationContext } from './utils' +import { type OpenAPIV3 } from 'openapi-types' + +export default class InlineObjectSchemaValidator { + private readonly _namespaces_folder: NamespacesFolder + private readonly _schemas_folder: SchemasFolder + + constructor (namespaces_folder: NamespacesFolder, schemas_folder: SchemasFolder) { + this._namespaces_folder = namespaces_folder + this._schemas_folder = schemas_folder + } + + validate (): ValidationError[] { + const errors: ValidationError[] = [] + + const visitor = new SchemaVisitor((ctx, schema) => { + this.#validate_schema(ctx, schema, errors) + }); + + [ + ...this._namespaces_folder.files, + ...this._schemas_folder.files + ].forEach(f => { visitor.visit_specification(new SpecificationContext(f.file), f.spec()) }) + + return errors + } + + #validate_schema (ctx: SpecificationContext, schema: MaybeRef, errors: ValidationError[]): void { + if (is_ref(schema) || schema.type !== 'object' || schema.properties === undefined) { + return + } + + const this_key = ctx.key + const parent_key = ctx.parent().key + + if (parent_key === 'properties' || this_key === 'additionalProperties' || this_key === 'items') { + errors.push(ctx.error('object schemas should be defined out-of-line via a $ref')) + } + } +} diff --git a/tools/linter/SpecValidator.ts b/tools/linter/SpecValidator.ts index a9ea98f1..50f489e1 100644 --- a/tools/linter/SpecValidator.ts +++ b/tools/linter/SpecValidator.ts @@ -3,18 +3,24 @@ import NamespacesFolder from './components/NamespacesFolder' import { type ValidationError } from '../types' import SchemaRefsValidator from './SchemaRefsValidator' import SupersededOperationsFile from './components/SupersededOperationsFile' +import InfoFile from './components/InfoFile' +import InlineObjectSchemaValidator from './InlineObjectSchemaValidator' export default class SpecValidator { - superseded_ops_files: SupersededOperationsFile + superseded_ops_file: SupersededOperationsFile + info_file: InfoFile namespaces_folder: NamespacesFolder schemas_folder: SchemasFolder schema_refs_validator: SchemaRefsValidator + inline_object_schema_validator: InlineObjectSchemaValidator constructor (root_folder: string) { - this.superseded_ops_files = new SupersededOperationsFile(`${root_folder}/_superseded_operations.yaml`) + this.superseded_ops_file = new SupersededOperationsFile(`${root_folder}/_superseded_operations.yaml`) + this.info_file = new InfoFile(`${root_folder}/_info.yaml`) this.namespaces_folder = new NamespacesFolder(`${root_folder}/namespaces`) this.schemas_folder = new SchemasFolder(`${root_folder}/schemas`) this.schema_refs_validator = new SchemaRefsValidator(this.namespaces_folder, this.schemas_folder) + this.inline_object_schema_validator = new InlineObjectSchemaValidator(this.namespaces_folder, this.schemas_folder) } validate (): ValidationError[] { @@ -26,7 +32,9 @@ export default class SpecValidator { return [ ...this.schema_refs_validator.validate(), - ...this.superseded_ops_files.validate() + ...this.superseded_ops_file.validate(), + ...this.info_file.validate(), + ...this.inline_object_schema_validator.validate() ] } } diff --git a/tools/linter/components/InfoFile.ts b/tools/linter/components/InfoFile.ts new file mode 100644 index 00000000..791beda4 --- /dev/null +++ b/tools/linter/components/InfoFile.ts @@ -0,0 +1,5 @@ +import FileValidator from './base/FileValidator' + +export default class InfoFile extends FileValidator { + has_json_schema = true +} diff --git a/tools/linter/components/OperationGroup.ts b/tools/linter/components/OperationGroup.ts index db4a0654..68156387 100644 --- a/tools/linter/components/OperationGroup.ts +++ b/tools/linter/components/OperationGroup.ts @@ -3,7 +3,7 @@ import { type ValidationError } from '../../types' import ValidatorBase from './base/ValidatorBase' export default class OperationGroup extends ValidatorBase { - readonly OP_PRIORITY = ['operationId', 'x-operation-group', 'x-ignorable', 'deprecated', + static readonly OP_PRIORITY = ['operationId', 'x-operation-group', 'x-ignorable', 'deprecated', 'x-deprecation-message', 'x-version-added', 'x-version-deprecated', 'x-version-removed', 'description', 'externalDocs', 'parameters', 'requestBody', 'responses'] diff --git a/tools/linter/components/SupersededOperationsFile.ts b/tools/linter/components/SupersededOperationsFile.ts index 335a897f..3591ede8 100644 --- a/tools/linter/components/SupersededOperationsFile.ts +++ b/tools/linter/components/SupersededOperationsFile.ts @@ -1,22 +1,5 @@ import FileValidator from './base/FileValidator' -import ajv from 'ajv' -import { type ValidationError } from '../../types' -import { read_yaml } from '../../helpers' export default class SupersededOperationsFile extends FileValidator { - readonly JSON_SCHEMA_PATH = '../json_schemas/_superseded_operations.yaml' - - validate (): ValidationError[] { - return [ - this.validate_json_schema() - ].filter(e => e) as ValidationError[] - } - - validate_json_schema (): ValidationError | undefined { - const schema = read_yaml(this.JSON_SCHEMA_PATH) - const validator = (new ajv()).compile(schema) - if (!validator(this.spec())) { - return this.error(`File content does not match JSON schema found in '${this.JSON_SCHEMA_PATH}':\n ${JSON.stringify(validator.errors, null, 2)}`) - } - } + has_json_schema = true } diff --git a/tools/linter/components/base/FileValidator.ts b/tools/linter/components/base/FileValidator.ts index f2d77721..a0e0fa7c 100644 --- a/tools/linter/components/base/FileValidator.ts +++ b/tools/linter/components/base/FileValidator.ts @@ -2,9 +2,12 @@ import ValidatorBase from './ValidatorBase' import { type ValidationError } from '../../../types' import { type OpenAPIV3 } from 'openapi-types' import { read_yaml } from '../../../helpers' +import AJV from 'ajv' +import addFormats from 'ajv-formats' export default class FileValidator extends ValidatorBase { file_path: string + has_json_schema: boolean = false protected _spec: OpenAPIV3.Document | undefined constructor (file_path: string) { @@ -23,11 +26,13 @@ export default class FileValidator extends ValidatorBase { if (extension_error) return [extension_error] const yaml_error = this.validate_yaml() if (yaml_error) return [yaml_error] + const json_schema_error = this.validate_json_schema() + if (json_schema_error) return [json_schema_error] return this.validate_file() } validate_file (): ValidationError[] { - throw new Error('Method not implemented.') + return [] } validate_extension (): ValidationError | undefined { @@ -41,4 +46,17 @@ export default class FileValidator extends ValidatorBase { return this.error('Unable to read or parse YAML.', 'File Content') } } + + validate_json_schema (): ValidationError | undefined { + if (!this.has_json_schema) return + const json_schema_path: string = (this.spec() as any).$schema ?? '' + if (json_schema_path === '') return this.error('JSON Schema is required but not found in this file.', '$schema') + const schema = read_yaml(json_schema_path) + const ajv = new AJV({ schemaId: 'id' }) + addFormats(ajv) + const validator = ajv.compile(schema) + if (!validator(this.spec())) { + return this.error(`File content does not match JSON schema found in '${json_schema_path}':\n ${JSON.stringify(validator.errors, null, 2)}`) + } + } } diff --git a/tools/linter/utils/SpecificationVisitor.ts b/tools/linter/utils/SpecificationVisitor.ts new file mode 100644 index 00000000..d94731a7 --- /dev/null +++ b/tools/linter/utils/SpecificationVisitor.ts @@ -0,0 +1,117 @@ +import { is_array_schema, is_ref, type KeysMatching, type MaybeRef, type SpecificationContext } from './index' +import { OpenAPIV3 } from 'openapi-types' + +type VisitorCallback = (ctx: SpecificationContext, o: NonNullable) => void +type SchemaVisitorCallback = VisitorCallback> + +function visit ( + ctx: SpecificationContext, + parent: Parent, + key: Key, + visitor: VisitorCallback +): void { + const child = parent[key] + if (child == null) return + visitor(ctx.child(key as string), child) +} + +type EnumerableKeys = KeysMatching | undefined> | KeysMatching | undefined> +type ElementOf = T extends Record ? V : T extends ArrayLike ? V : never + +function visit_each> ( + ctx: SpecificationContext, + parent: Parent, + key: Key, + visitor: VisitorCallback> +): void { + const children = parent[key] + if (children == null) return + ctx = ctx.child(key as string) + Object.entries>(children).forEach(([key, child]) => { + if (child == null) return + visitor(ctx.child(key), child) + }) +} + +export class SpecificationVisitor { + visit_specification (ctx: SpecificationContext, specification: OpenAPIV3.Document): void { + visit_each(ctx, specification, 'paths', this.visit_path.bind(this)) + visit(ctx, specification, 'components', this.visit_components.bind(this)) + } + + visit_path (ctx: SpecificationContext, path: OpenAPIV3.PathItemObject): void { + visit_each(ctx, path, 'parameters', this.visit_parameter.bind(this)) + + for (const method of Object.values(OpenAPIV3.HttpMethods)) { + visit(ctx, path, method, this.visit_operation.bind(this)) + } + } + + visit_operation (ctx: SpecificationContext, operation: OpenAPIV3.OperationObject): void { + visit_each(ctx, operation, 'parameters', this.visit_parameter.bind(this)) + visit(ctx, operation, 'requestBody', this.visit_request_body.bind(this)) + visit_each(ctx, operation, 'responses', this.visit_response.bind(this)) + } + + visit_components (ctx: SpecificationContext, components: OpenAPIV3.ComponentsObject): void { + visit_each(ctx, components, 'parameters', this.visit_parameter.bind(this)) + visit_each(ctx, components, 'requestBodies', this.visit_request_body.bind(this)) + visit_each(ctx, components, 'responses', this.visit_response.bind(this)) + visit_each(ctx, components, 'schemas', this.visit_schema.bind(this)) + } + + visit_parameter (ctx: SpecificationContext, parameter: MaybeRef): void { + if (is_ref(parameter)) return + + visit(ctx, parameter, 'schema', this.visit_schema.bind(this)) + } + + visit_request_body (ctx: SpecificationContext, request_body: MaybeRef): void { + if (is_ref(request_body)) return + + visit_each(ctx, request_body, 'content', this.visit_media_type.bind(this)) + } + + visit_response (ctx: SpecificationContext, response: MaybeRef): void { + if (is_ref(response)) return + + visit_each(ctx, response, 'content', this.visit_media_type.bind(this)) + } + + visit_media_type (ctx: SpecificationContext, media_type: OpenAPIV3.MediaTypeObject): void { + visit(ctx, media_type, 'schema', this.visit_schema.bind(this)) + } + + visit_schema (ctx: SpecificationContext, schema: MaybeRef): void { + if (is_ref(schema)) return + + if (is_array_schema(schema)) { + visit(ctx, schema, 'items', this.visit_schema.bind(this)) + } + + visit(ctx, schema, 'additionalProperties', (ctx, v) => { + if (typeof v !== 'object') return + this.visit_schema(ctx, v) + }) + + visit_each(ctx, schema, 'properties', this.visit_schema.bind(this)) + visit_each(ctx, schema, 'allOf', this.visit_schema.bind(this)) + visit_each(ctx, schema, 'anyOf', this.visit_schema.bind(this)) + visit_each(ctx, schema, 'oneOf', this.visit_schema.bind(this)) + visit(ctx, schema, 'not', this.visit_schema.bind(this)) + } +} + +export class SchemaVisitor extends SpecificationVisitor { + private readonly _callback: SchemaVisitorCallback + + constructor (callback: SchemaVisitorCallback) { + super() + this._callback = callback + } + + visit_schema (ctx: SpecificationContext, schema: MaybeRef): void { + super.visit_schema(ctx, schema) + this._callback(ctx, schema) + } +} diff --git a/tools/linter/utils/index.ts b/tools/linter/utils/index.ts new file mode 100644 index 00000000..d20fbf5d --- /dev/null +++ b/tools/linter/utils/index.ts @@ -0,0 +1,62 @@ +import { type OpenAPIV3 } from 'openapi-types' +import { type ValidationError } from '../../types' + +export function is_ref (o: MaybeRef): o is OpenAPIV3.ReferenceObject { + return '$ref' in o +} + +export function is_array_schema (schema: OpenAPIV3.SchemaObject): schema is OpenAPIV3.ArraySchemaObject { + return schema.type === 'array' +} + +export function is_primitive_schema (schema: OpenAPIV3.SchemaObject): boolean { + return schema.type === 'boolean' || + schema.type === 'integer' || + schema.type === 'number' || + schema.type === 'string' +} + +export class SpecificationContext { + private readonly _file: string + private readonly _location: string[] + + constructor (file: string, location?: string[]) { + this._file = file + this._location = location ?? ['#'] + } + + parent (): SpecificationContext { + if (this._location.length <= 1) return this + return new SpecificationContext(this._file, this._location.slice(0, -1)) + } + + child (child: string): SpecificationContext { + return new SpecificationContext(this._file, [...this._location, child]) + } + + error (message: string): ValidationError { + return { file: this._file, location: this.location, message } + } + + get file (): string { + return this._file + } + + get location (): string { + return this._location + .map(k => k + .replaceAll('~', '~0') + .replaceAll('/', '~1')) + .join('/') + } + + get key (): string { + return this._location[this._location.length - 1] + } +} + +export type MaybeRef = O | OpenAPIV3.ReferenceObject + +export type KeysMatching = { + [K in keyof T]-?: T[K] extends V ? K : never +}[keyof T] diff --git a/tools/merger/GlobalParamsGenerator.ts b/tools/merger/GlobalParamsGenerator.ts index df84ff8c..612c18fa 100644 --- a/tools/merger/GlobalParamsGenerator.ts +++ b/tools/merger/GlobalParamsGenerator.ts @@ -27,9 +27,9 @@ export default class GlobalParamsGenerator { const params = (spec.components?.parameters ?? {}) as Record _.entries(params).forEach(([original_key, param]) => { const global_key = `_global::${param.in}.${param.name}` - params[global_key] = param _.set(param, 'x-global', true) _.unset(params, original_key) + params[global_key] = param }) return params } diff --git a/tools/merger/OpenApiMerger.ts b/tools/merger/OpenApiMerger.ts index 7054eac1..0bb0be43 100644 --- a/tools/merger/OpenApiMerger.ts +++ b/tools/merger/OpenApiMerger.ts @@ -17,7 +17,7 @@ export default class OpenApiMerger { this.root_folder = fs.realpathSync(root_folder) this.spec = { openapi: '3.1.0', - info: read_yaml(`${this.root_folder}/_info.yaml`), + info: read_yaml(`${this.root_folder}/_info.yaml`, true), paths: {}, components: { parameters: {}, diff --git a/tools/package-lock.json b/tools/package-lock.json index baf75ba2..20ab5a4d 100644 --- a/tools/package-lock.json +++ b/tools/package-lock.json @@ -13,6 +13,7 @@ "@types/lodash": "^4.14.202", "@types/node": "^20.10.3", "ajv": "^8.13.0", + "ajv-formats": "^3.0.1", "lodash": "^4.17.21", "ts-node": "^10.9.1", "typescript": "^5.4.5", @@ -1831,6 +1832,22 @@ } } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", diff --git a/tools/package.json b/tools/package.json index 0e7bbbfd..23bafa58 100644 --- a/tools/package.json +++ b/tools/package.json @@ -15,6 +15,7 @@ "@types/lodash": "^4.14.202", "@types/node": "^20.10.3", "ajv": "^8.13.0", + "ajv-formats": "^3.0.1", "lodash": "^4.17.21", "ts-node": "^10.9.1", "typescript": "^5.4.5", diff --git a/tools/test/linter/InfoFile.test.ts b/tools/test/linter/InfoFile.test.ts new file mode 100644 index 00000000..a0a7652b --- /dev/null +++ b/tools/test/linter/InfoFile.test.ts @@ -0,0 +1,12 @@ +import InfoFile from '../../linter/components/InfoFile' + +test('validate()', () => { + const validator = new InfoFile('./test/linter/fixtures/_info.yaml') + expect(validator.validate()).toEqual([ + { + file: 'fixtures/_info.yaml', + location: '$schema', + message: 'JSON Schema is required but not found in this file.' + } + ]) +}) diff --git a/tools/test/linter/SupersededOperationsFile.test.ts b/tools/test/linter/SupersededOperationsFile.test.ts index be93ea7a..f5c6a7e3 100644 --- a/tools/test/linter/SupersededOperationsFile.test.ts +++ b/tools/test/linter/SupersededOperationsFile.test.ts @@ -5,7 +5,7 @@ test('validate()', () => { expect(validator.validate()).toEqual([ { file: 'fixtures/_superseded_operations.yaml', - message: "File content does not match JSON schema found in '../json_schemas/_superseded_operations.yaml':\n [\n {\n \"instancePath\": \"/~1hello~1world/operations/1\",\n \"schemaPath\": \"#/patternProperties/%5E~1/properties/operations/items/enum\",\n \"keyword\": \"enum\",\n \"params\": {\n \"allowedValues\": [\n \"GET\",\n \"POST\",\n \"PUT\",\n \"DELETE\",\n \"HEAD\",\n \"OPTIONS\",\n \"PATCH\"\n ]\n },\n \"message\": \"must be equal to one of the allowed values\"\n }\n]" + message: "File content does not match JSON schema found in '../json_schemas/_superseded_operations.schema.yaml':\n [\n {\n \"instancePath\": \"/~1hello~1world/operations/1\",\n \"schemaPath\": \"#/patternProperties/%5E~1/properties/operations/items/enum\",\n \"keyword\": \"enum\",\n \"params\": {\n \"allowedValues\": [\n \"GET\",\n \"POST\",\n \"PUT\",\n \"DELETE\",\n \"HEAD\",\n \"OPTIONS\",\n \"PATCH\"\n ]\n },\n \"message\": \"must be equal to one of the allowed values\"\n }\n]" } ]) }) diff --git a/tools/test/linter/fixtures/_info.yaml b/tools/test/linter/fixtures/_info.yaml new file mode 100644 index 00000000..29b411f5 --- /dev/null +++ b/tools/test/linter/fixtures/_info.yaml @@ -0,0 +1,2 @@ +title: OpenSearch API Specification +version: 1.0.0 \ No newline at end of file diff --git a/tools/test/linter/fixtures/_superseded_operations.yaml b/tools/test/linter/fixtures/_superseded_operations.yaml index d264c068..20c92eec 100644 --- a/tools/test/linter/fixtures/_superseded_operations.yaml +++ b/tools/test/linter/fixtures/_superseded_operations.yaml @@ -1,4 +1,4 @@ -$schema: ../../../../json_schemas/_superseded_operations.yaml +$schema: ../json_schemas/_superseded_operations.schema.yaml /hello/world: superseded_by: /goodbye/world diff --git a/tools/test/linter/fixtures/empty/_info.yaml b/tools/test/linter/fixtures/empty/_info.yaml new file mode 100644 index 00000000..66d1923d --- /dev/null +++ b/tools/test/linter/fixtures/empty/_info.yaml @@ -0,0 +1,4 @@ +$schema: ../json_schemas/_info.schema.yaml + +title: '' +version: '' \ No newline at end of file diff --git a/tools/test/linter/fixtures/empty/_superseded_operations.yaml b/tools/test/linter/fixtures/empty/_superseded_operations.yaml index ac2175ea..6f143f79 100644 --- a/tools/test/linter/fixtures/empty/_superseded_operations.yaml +++ b/tools/test/linter/fixtures/empty/_superseded_operations.yaml @@ -1 +1 @@ -$schema: ../../../../../json_schemas/_superseded_operations.yaml \ No newline at end of file +$schema: ../json_schemas/_superseded_operations.schema.yaml \ No newline at end of file diff --git a/tools/test/merger/fixtures/spec/_info.yaml b/tools/test/merger/fixtures/spec/_info.yaml index 9da26bfa..721aed8c 100644 --- a/tools/test/merger/fixtures/spec/_info.yaml +++ b/tools/test/merger/fixtures/spec/_info.yaml @@ -1,3 +1,5 @@ +$schema: should-be-ignored + title: OpenSearch API description: OpenSearch API version: 1.0.0 \ No newline at end of file