Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into security-api-spec
Browse files Browse the repository at this point in the history
  • Loading branch information
DarshitChanpura committed May 7, 2024
2 parents 939654a + 909b02d commit 8d4ecdf
Show file tree
Hide file tree
Showing 28 changed files with 376 additions and 51 deletions.
44 changes: 44 additions & 0 deletions json_schemas/_info.schema.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
$schema: http://json-schema.org/draft-07/schema#

type: object
patternProperties:
^\$schema$:
Expand Down
10 changes: 5 additions & 5 deletions spec/_global_parameters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions spec/_info.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
title: OpenSearch API
description: OpenSearch API
$schema: ../json_schemas/_info.schema.yaml

title: OpenSearch API Specification
version: 1.0.0
2 changes: 1 addition & 1 deletion spec/_superseded_operations.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
13 changes: 1 addition & 12 deletions spec/namespaces/notifications.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
13 changes: 13 additions & 0 deletions spec/schemas/notifications._common.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion tools/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
6 changes: 4 additions & 2 deletions tools/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ export function sort_by_keys (obj: Record<string, any>, priorities: string[] = [
})
}

export function read_yaml (file_path: string): Record<string, any> {
return YAML.parse(fs.readFileSync(file_path, 'utf8'))
export function read_yaml (file_path: string, exclude_schema: boolean = false): Record<string, any> {
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<string, any>): void {
Expand Down
44 changes: 44 additions & 0 deletions tools/linter/InlineObjectSchemaValidator.ts
Original file line number Diff line number Diff line change
@@ -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<OpenAPIV3.SchemaObject>, 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'))
}
}
}
14 changes: 11 additions & 3 deletions tools/linter/SpecValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] {
Expand All @@ -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()
]
}
}
5 changes: 5 additions & 0 deletions tools/linter/components/InfoFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import FileValidator from './base/FileValidator'

export default class InfoFile extends FileValidator {
has_json_schema = true
}
2 changes: 1 addition & 1 deletion tools/linter/components/OperationGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']

Expand Down
19 changes: 1 addition & 18 deletions tools/linter/components/SupersededOperationsFile.ts
Original file line number Diff line number Diff line change
@@ -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
}
20 changes: 19 additions & 1 deletion tools/linter/components/base/FileValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 {
Expand All @@ -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)}`)
}
}
}
Loading

0 comments on commit 8d4ecdf

Please sign in to comment.