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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"fast-equals": "4.0.3"
},
"devDependencies": {
"@netcracker/qubership-apihub-graphapi": "1.0.8",
"@netcracker/qubership-apihub-graphapi": "feature-performance-optimization",
"@netcracker/qubership-apihub-npm-gitflow": "3.1.0",
"@types/object-hash": "3.0.6",
"@types/jest": "29.5.2",
Expand Down
34 changes: 29 additions & 5 deletions src/path-matcher.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { isObject, JsonPath } from '@netcracker/qubership-apihub-json-crawl'

export const PREDICATE_ANY_VALUE = Symbol('?')
export const PREDICATE_UNCLOSED_END = Symbol('**')

export interface GrepValuePredicate {
readonly name: string
Expand All @@ -11,7 +9,19 @@ export const grepValue = (name: string) => {
return { name } as GrepValuePredicate
}

export type PathPredicate = (PropertyKey | GrepValuePredicate)[]
export interface ValidateValuePredicate {
readonly validate: (value: PropertyKey) => boolean
}

export const validateValuePredicate = (validate: (value: PropertyKey) => boolean): ValidateValuePredicate => {
return { validate }
}

export const PREDICATE_ANY_VALUE = Symbol('?')
export const PREDICATE_UNCLOSED_END = Symbol('**')
export const PREDICATE_NOT_OAS_EXTENSION = validateValuePredicate((value) => !value.toString().startsWith('x-'))

export type PathPredicate = (PropertyKey | GrepValuePredicate | ValidateValuePredicate)[]

export type GrepValues = Record<GrepValuePredicate['name'], PropertyKey>

Expand All @@ -21,6 +31,14 @@ export type MatchResult = {
grepValues: GrepValues
}

function isGrepValuePredicate(value: unknown): value is GrepValuePredicate {
return isObject(value) && 'name' in (value as object)
}

function isValidateValuePredicate(value: unknown): value is ValidateValuePredicate {
return isObject(value) && 'validate' in (value as object)
}

function matchPath(path: JsonPath, predicates: PathPredicate[]): MatchResult | undefined {
const predicateMap = new Map<number, PathPredicate>(predicates.map((value, index) => [index, value]))
const state = path.reduce((state, pathItem, currentIndex) => {
Expand All @@ -30,10 +48,16 @@ function matchPath(path: JsonPath, predicates: PathPredicate[]): MatchResult | u
}
const predicateCopy = [...predicate]
const currentItemPredicate = predicateCopy.shift()
if (isObject(currentItemPredicate)) {
const name = (currentItemPredicate as GrepValuePredicate).name
if (isGrepValuePredicate(currentItemPredicate)) {
const name = currentItemPredicate.name
state.result[name] = pathItem
map.set(key, predicateCopy)
} else if (isValidateValuePredicate(currentItemPredicate)) {
if (currentItemPredicate.validate(pathItem)) {
map.set(key, predicateCopy)
} else {
map.delete(key)
}
} else {
switch (currentItemPredicate) {
case PREDICATE_ANY_VALUE: {
Expand Down
85 changes: 0 additions & 85 deletions test/oas/declararetion-path-matcher.test.ts

This file was deleted.

169 changes: 169 additions & 0 deletions test/oas/declaration-path-matcher.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import {
grepValue,
matchPaths,
OPEN_API_PROPERTY_COMPONENTS,
OPEN_API_PROPERTY_CONTENT,
OPEN_API_PROPERTY_DESCRIPTION,
OPEN_API_PROPERTY_EXAMPLE,
OPEN_API_PROPERTY_HEADERS,
OPEN_API_PROPERTY_PARAMETERS,
OPEN_API_PROPERTY_PATHS,
OPEN_API_PROPERTY_RESPONSES,
PREDICATE_ANY_VALUE,
PREDICATE_UNCLOSED_END,
PREDICATE_NOT_OAS_EXTENSION,
validateValuePredicate
} from '../../src'

describe('Declaration Path Matcher', () => {
it('Not matched', () => {
const matchResult = matchPaths(
[
[OPEN_API_PROPERTY_PATHS, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_PARAMETERS, 'two', OPEN_API_PROPERTY_DESCRIPTION],
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, 'one', OPEN_API_PROPERTY_DESCRIPTION],
],
[
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_HEADERS, grepValue('parameterName'), OPEN_API_PROPERTY_DESCRIPTION]
]
)
expect(matchResult).toBe(undefined)
})

it('Matched', () => {
const matchResult = matchPaths(
[
[OPEN_API_PROPERTY_PATHS, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_PARAMETERS, 'two', OPEN_API_PROPERTY_DESCRIPTION],
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, 'one', OPEN_API_PROPERTY_DESCRIPTION],
],
[
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, grepValue('parameterName'), OPEN_API_PROPERTY_DESCRIPTION]
]
)
expect(matchResult).toHaveProperty('grepValues.parameterName', 'one')
})

it('Any value matched', () => {
const matchResult = matchPaths(
[
[OPEN_API_PROPERTY_PATHS, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_HEADERS, 'two', OPEN_API_PROPERTY_CONTENT],
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, 'one', OPEN_API_PROPERTY_DESCRIPTION],
],
[
[PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_PARAMETERS, PREDICATE_ANY_VALUE, grepValue('parameterName')]
]
)
expect(matchResult).toHaveProperty('grepValues.parameterName', 'description')
})

it('Many Property Matching', () => {
const matchResult = matchPaths(
[
[OPEN_API_PROPERTY_PATHS, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_RESPONSES, '404', OPEN_API_PROPERTY_CONTENT, 'jsonType', OPEN_API_PROPERTY_EXAMPLE, 'param1'],
],
[
[OPEN_API_PROPERTY_PATHS, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_RESPONSES, PREDICATE_ANY_VALUE, PREDICATE_ANY_VALUE, grepValue('mediaType'), OPEN_API_PROPERTY_EXAMPLE, grepValue('example')],
[OPEN_API_PROPERTY_PATHS, PREDICATE_ANY_VALUE, grepValue('scope')],
]
)
expect(matchResult).toHaveProperty('grepValues.scope', 'responses')
expect(matchResult).toHaveProperty('grepValues.mediaType', 'jsonType')
expect(matchResult).toHaveProperty('grepValues.example', 'param1')
})

it('True predicate after matching', () => {
const suitablePredicate = [OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_EXAMPLE, PREDICATE_UNCLOSED_END]
const matchResult = matchPaths(
[
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_EXAMPLE, PREDICATE_ANY_VALUE, PREDICATE_ANY_VALUE],
],
[
[OPEN_API_PROPERTY_PATHS, OPEN_API_PROPERTY_PARAMETERS, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_EXAMPLE, PREDICATE_ANY_VALUE, PREDICATE_ANY_VALUE],
suitablePredicate,
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_EXAMPLE, PREDICATE_ANY_VALUE, PREDICATE_ANY_VALUE]
]
)
expect(matchResult).toHaveProperty('predicate', suitablePredicate)
})

it('ValidateValue predicate matches non-extension properties', () => {
const matchResult = matchPaths(
[
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, 'myParam', OPEN_API_PROPERTY_DESCRIPTION],
],
[
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, PREDICATE_NOT_OAS_EXTENSION, OPEN_API_PROPERTY_DESCRIPTION]
]
)
expect(matchResult).toBeDefined()
expect(matchResult?.path).toEqual([OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, 'myParam', OPEN_API_PROPERTY_DESCRIPTION])
})

it('ValidateValue predicate filters out OAS extensions', () => {
const matchResult = matchPaths(
[
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, 'x-custom-extension', OPEN_API_PROPERTY_DESCRIPTION],
],
[
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, PREDICATE_NOT_OAS_EXTENSION, OPEN_API_PROPERTY_DESCRIPTION]
]
)
expect(matchResult).toBeUndefined()
})

it('ValidateValue predicate combined with grepValue', () => {
const matchResult = matchPaths(
[
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, 'x-extension', OPEN_API_PROPERTY_DESCRIPTION],
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, 'normalParam', OPEN_API_PROPERTY_DESCRIPTION],
],
[
[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_PARAMETERS, PREDICATE_NOT_OAS_EXTENSION, grepValue('property')]
]
)
expect(matchResult).toBeDefined()
expect(matchResult).toHaveProperty('grepValues.property', OPEN_API_PROPERTY_DESCRIPTION)
})

it('ValidateValue predicate with ANY_VALUE and UNCLOSED_END', () => {
const pathWithoutOASExtension = [OPEN_API_PROPERTY_PATHS, '/api/users', OPEN_API_PROPERTY_RESPONSES, '200', OPEN_API_PROPERTY_CONTENT, 'application/json', 'schema', 'properties', 'id'];
const pathWithOASExtension = [OPEN_API_PROPERTY_PATHS, '/api/users', OPEN_API_PROPERTY_RESPONSES, '200', OPEN_API_PROPERTY_CONTENT, 'x-documentation-extension'];
const matchResult = matchPaths(
[
pathWithOASExtension,
pathWithoutOASExtension,
],
[
[PREDICATE_ANY_VALUE, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_RESPONSES, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_CONTENT, PREDICATE_NOT_OAS_EXTENSION, PREDICATE_UNCLOSED_END]
]
)
expect(matchResult).toBeDefined()
expect(matchResult?.path).toEqual(pathWithoutOASExtension)
})

it('ValidateValue predicate rejects extension in complex path', () => {
const matchResult = matchPaths(
[
[OPEN_API_PROPERTY_PATHS, '/api/users', OPEN_API_PROPERTY_RESPONSES, '200', OPEN_API_PROPERTY_CONTENT, 'x-custom-media', 'schema'],
],
[
[PREDICATE_ANY_VALUE, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_RESPONSES, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_CONTENT, PREDICATE_NOT_OAS_EXTENSION, PREDICATE_UNCLOSED_END]
]
)
expect(matchResult).toBeUndefined()
})

it('Custom ValidateValue predicate with numeric filter', () => {
const numericCodePredicate = validateValuePredicate((value) => /^\d+$/.test(value.toString()))
const matchResult = matchPaths(
[
[OPEN_API_PROPERTY_PATHS, '/api/users', OPEN_API_PROPERTY_RESPONSES, '200', OPEN_API_PROPERTY_CONTENT],
[OPEN_API_PROPERTY_PATHS, '/api/users', OPEN_API_PROPERTY_RESPONSES, 'default', OPEN_API_PROPERTY_CONTENT],
],
[
[PREDICATE_ANY_VALUE, PREDICATE_ANY_VALUE, OPEN_API_PROPERTY_RESPONSES, numericCodePredicate, OPEN_API_PROPERTY_CONTENT]
]
)
expect(matchResult).toBeDefined()
expect(matchResult?.path).toEqual([OPEN_API_PROPERTY_PATHS, '/api/users', OPEN_API_PROPERTY_RESPONSES, '200', OPEN_API_PROPERTY_CONTENT])
})
})
Loading