Skip to content
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1f9b49e
feat: Added pathItems
makeev-pavel Aug 20, 2025
050f7ae
feat: Added work with '$ref' in rest operation
makeev-pavel Aug 22, 2025
5661ae0
feat: Fixed merge pathItems operations
makeev-pavel Aug 25, 2025
df04f59
feat: Added tests
makeev-pavel Aug 25, 2025
37369ef
feat: refactoring
makeev-pavel Aug 26, 2025
63690cf
Merge branch 'develop' into feature/pathItems
makeev-pavel Aug 26, 2025
0c2cb21
feat: Added tests and review
makeev-pavel Aug 29, 2025
45225cf
feat: Added tests and review
makeev-pavel Sep 1, 2025
e3cbfa9
Merge branch 'develop' into feature/pathItems
makeev-pavel Sep 1, 2025
1caef30
feat: Refactoring
makeev-pavel Sep 3, 2025
30097c2
feat: Refactoring
makeev-pavel Sep 5, 2025
700ba7e
feat: Update tests
makeev-pavel Sep 5, 2025
225d22a
feat: Update dep
makeev-pavel Sep 5, 2025
bd3ad86
feat: Refactoring
makeev-pavel Sep 8, 2025
aa6f46a
feat: Added hash tests
makeev-pavel Sep 9, 2025
26cc1fd
feat: Fixed servers prefix for operations and added tests
makeev-pavel Sep 10, 2025
136d5ce
feat: Refactoring
makeev-pavel Sep 10, 2025
ffdc348
feat: Refactoring
makeev-pavel Sep 12, 2025
11f601d
feat: Refactoring
makeev-pavel Sep 12, 2025
793e0bc
feat: POC ref pathItems support
makeev-pavel Sep 12, 2025
c00bce3
feat: refactoring
makeev-pavel Sep 15, 2025
6203ae9
feat: typing
makeev-pavel Sep 15, 2025
665fec8
feat: Added cleaning PathItemObject in components
makeev-pavel Sep 15, 2025
66af9d8
feat: Cleaning
makeev-pavel Sep 15, 2025
f38895a
feat: Cleaning
makeev-pavel Sep 15, 2025
9dacc6a
Merge pull request #40 from Netcracker/feature/pathItems-test
makeev-pavel Sep 15, 2025
0a44196
feat: Update tests
makeev-pavel Sep 16, 2025
14ad3fa
feat: Update tests
makeev-pavel Sep 16, 2025
c4db313
Merge remote-tracking branch 'origin/feature/pathItems' into feature/…
makeev-pavel Sep 16, 2025
5b1b4e5
feat: Review
makeev-pavel Sep 17, 2025
21672c6
feat: Cleaning
makeev-pavel Sep 17, 2025
1d5b5d5
Merge branch 'develop' into feature/pathItems
makeev-pavel Sep 17, 2025
cacf54c
feat: Added info in yaml tests
makeev-pavel Sep 17, 2025
2d5d97f
feat: Fix linter
makeev-pavel Sep 17, 2025
d4b058e
feat: Linter
makeev-pavel Sep 17, 2025
9bf11d7
feat: Linter
makeev-pavel Sep 17, 2025
bbefe7e
feat: Review
makeev-pavel Sep 18, 2025
1ae91d7
feat: Review
makeev-pavel Sep 18, 2025
1c4e293
feat: Review
makeev-pavel Sep 18, 2025
a12e958
feat: Linter
makeev-pavel Sep 18, 2025
cfeeb6d
Merge branch 'develop' into feature/pathItems
makeev-pavel Sep 18, 2025
7f67976
feat: Review
makeev-pavel Sep 19, 2025
c70196e
feat: Review
makeev-pavel Sep 19, 2025
0f1f89d
feat: Review
makeev-pavel Sep 19, 2025
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
"update-lock-file": "update-lock-file @netcracker"
},
"dependencies": {
"@netcracker/qubership-apihub-api-diff": "2.2.0",
"@netcracker/qubership-apihub-api-unifier": "2.3.0",
"@netcracker/qubership-apihub-api-diff": "dev",
"@netcracker/qubership-apihub-api-unifier": "dev",
"@netcracker/qubership-apihub-graphapi": "1.0.8",
"@netcracker/qubership-apihub-json-crawl": "1.0.4",
"adm-zip": "0.5.10",
Expand Down
119 changes: 110 additions & 9 deletions src/apitypes/rest/rest.operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ import { OpenAPIV3 } from 'openapi-types'
import { REST_API_TYPE, REST_KIND_KEY } from './rest.consts'
import { operationRules } from './rest.rules'
import type * as TYPE from './rest.types'
import type {
import { RestOperationData } from './rest.types'
import {
BuildConfig,
CrawlRule,
DeprecateItem,
NotificationMessage,
OperationCrawlState,
OperationId,
SearchScopes,
} from '../../types'
import {
Expand All @@ -33,15 +35,18 @@ import {
getKeyValue,
getSplittedVersionKey,
isDeprecatedOperationItem,
isObject,
isOperationDeprecated,
normalizePath,
rawToApiKind,
removeFirstSlash,
setValueByPath,
slugify,
takeIf,
takeIfDefined,
} from '../../utils'
import { API_KIND, INLINE_REFS_FLAG, ORIGINS_SYMBOL, VERSION_STATUS } from '../../consts'
import { getCustomTags, resolveApiAudience } from './rest.utils'
import { getCustomTags, getOperationBasePath, resolveApiAudience } from './rest.utils'
import { DebugPerformanceContext, syncDebugPerformance } from '../../utils/logs'
import {
calculateDeprecatedItems,
Expand All @@ -59,6 +64,7 @@ import {
} from '@netcracker/qubership-apihub-api-unifier'
import { calculateObjectHash } from '../../utils/hashes'
import { calculateTolerantHash } from '../../components/deprecated'
import { getValueByPath } from '../../utils/path'

export const buildRestOperation = (
operationId: string,
Expand Down Expand Up @@ -143,7 +149,7 @@ export const buildRestOperation = (
security,
components?.securitySchemes,
)
calculateSpecRefs(document.data, refsOnlySingleOperationSpec, specWithSingleOperation, models, componentsHashMap)
calculateSpecRefs(document.data, refsOnlySingleOperationSpec, specWithSingleOperation, [operationId], models, componentsHashMap)
const dataHash = calculateObjectHash(specWithSingleOperation)
return [specWithSingleOperation, dataHash]
}, debugCtx)
Expand Down Expand Up @@ -180,7 +186,14 @@ export const buildRestOperation = (
}
}

export const calculateSpecRefs = (sourceDocument: unknown, normalizedSpec: unknown, resultSpec: unknown, models?: Record<string, string>, componentsHashMap?: Map<string, string>): void => {
export const calculateSpecRefs = (
sourceDocument: TYPE.RestOperationData,
normalizedSpec: TYPE.RestOperationData,
resultSpec: TYPE.RestOperationData,
operations: OperationId[],
models?: Record<string, string>,
componentsHashMap?: Map<string, string>,
): void => {
const handledObjects = new Set<unknown>()
const inlineRefs = new Set<string>()
syncCrawl(
Expand Down Expand Up @@ -212,10 +225,13 @@ export const calculateSpecRefs = (sourceDocument: unknown, normalizedSpec: unkno
return
}
const componentName = matchResult.grepValues[grepKey].toString()
const component = getKeyValue(sourceDocument, ...matchResult.path)
let component = getKeyValue(sourceDocument, ...matchResult.path) as Record<string, unknown>
if (!component) {
return
}
if (isObject(component)) {
component = { ...component }
}
if (models && !models[componentName] && isComponentsSchemaRef(matchResult.path)) {
let componentHash = componentsHashMap?.get(componentName)
if (componentHash) {
Expand All @@ -226,8 +242,70 @@ export const calculateSpecRefs = (sourceDocument: unknown, normalizedSpec: unkno
models[componentName] = componentHash
}
}

setValueByPath(resultSpec, matchResult.path, component)
})

if (operations?.length) {
resolveComponentsPathItemOperationSpec(resultSpec, normalizedSpec, operations)
}
}

export function resolveComponentsPathItemOperationSpec(
sourceDocument: RestOperationData,
normalizedDocument: RestOperationData,
operations: OperationId[],
): void {
const { paths } = normalizedDocument

for (const path of Object.keys(paths)) {
const sourcePathItem = paths[path] as OpenAPIV3.PathItemObject
if (!isNonNullObject(sourcePathItem)) {
continue
}
const refs: string[] = hasInlineRefsFlag(sourcePathItem) ? sourcePathItem[INLINE_REFS_FLAG] : []
if (refs.length === 0) {
continue
}
const { jsonPath } = parseRef(refs[0])
if (!jsonPath) {
continue
}

const valueByPath = getValueByPath(sourceDocument, jsonPath) as OpenAPIV3.PathItemObject

const operationIds: OpenAPIV3.HttpMethods[] = (Object.keys(valueByPath) as OpenAPIV3.HttpMethods[])
.filter((httpMethod) => isValidHttpMethod(httpMethod))
.filter(httpMethod => {
const methodData = sourcePathItem[httpMethod as OpenAPIV3.HttpMethods]
if (!methodData) return false
const basePath = getOperationBasePath(
methodData?.servers ||
sourcePathItem?.servers ||
[],
)
const operationId = getOperationId(basePath, httpMethod, path)
return operations.includes(operationId)
})

if (operationIds?.length) {
const pathItemObject = {
...extractCommonPathItemProperties(valueByPath),
...operationIds.reduce<OpenAPIV3.PathItemObject>((pathItemObject: OpenAPIV3.PathItemObject, operationId: OpenAPIV3.HttpMethods) => {
const operationData = valueByPath[operationId]
if (operationData) {
pathItemObject[operationId] = { ...operationData }
}
return pathItemObject
}, {}),
}
setValueByPath(sourceDocument, jsonPath, pathItemObject)
}
}
}

function isNonNullObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}

export const isComponentsSchemaRef = (path: JsonPath): boolean => {
Expand All @@ -236,13 +314,18 @@ export const isComponentsSchemaRef = (path: JsonPath): boolean => {
[[OPEN_API_PROPERTY_COMPONENTS, OPEN_API_PROPERTY_SCHEMAS, PREDICATE_UNCLOSED_END]],
)
}

const isOperationPaths = (paths: JsonPath[]): boolean => {
return !!matchPaths(
paths,
[[OPEN_API_PROPERTY_PATHS, PREDICATE_ANY_VALUE, PREDICATE_ANY_VALUE, PREDICATE_ANY_VALUE]],
)
}

function hasInlineRefsFlag(obj: unknown): obj is { [INLINE_REFS_FLAG]: string[] } {
return typeof obj === 'object' && obj !== null && INLINE_REFS_FLAG in obj
}

// todo output of this method disrupts document normalization.
// origin symbols are not being transferred to the resulting spec.
// DO NOT pass output of this method to apiDiff
Expand All @@ -257,15 +340,20 @@ const createSingleOperationSpec = (
): TYPE.RestOperationData => {
const pathData = document.paths[path] as OpenAPIV3.PathItemObject

const isContainsRef = !!pathData.$ref
const refFlag = hasInlineRefsFlag(pathData) ? pathData[INLINE_REFS_FLAG] : false
return {
openapi: openapi ?? '3.0.0',
...takeIfDefined({ servers }),
...takeIfDefined({ security }), // TODO: remove duplicates in security
paths: {
[path]: {
...extractCommonPathItemProperties(pathData),
[method]: { ...pathData[method] },
},
[path]: isContainsRef
? pathData
: {
...extractCommonPathItemProperties(pathData),
[method]: { ...pathData[method] },
...(refFlag ? { [INLINE_REFS_FLAG]: refFlag } : {}),
},
},
components: {
...takeIfDefined({ securitySchemes }),
Expand All @@ -281,3 +369,16 @@ export const extractCommonPathItemProperties = (
...takeIfDefined({ servers: pathData?.servers }),
...takeIfDefined({ parameters: pathData?.parameters }),
})

function isValidHttpMethod(method: string): method is OpenAPIV3.HttpMethods {
return (Object.values(OpenAPIV3.HttpMethods) as string[]).includes(method)
}

export function getOperationId(
basePath: string,
key: string,
path: string,
): string {
const operationPath = basePath + path
return slugify(`${removeFirstSlash(operationPath)}-${key}`)
}
8 changes: 3 additions & 5 deletions src/apitypes/rest/rest.operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@

import { OpenAPIV3 } from 'openapi-types'

import { buildRestOperation } from './rest.operation'
import { buildRestOperation, getOperationId } from './rest.operation'
import { OperationIdNormalizer, OperationsBuilder } from '../../types'
import {
createBundlingErrorHandler,
IGNORE_PATH_PARAM_UNIFIED_PLACEHOLDER,
removeComponents,
removeFirstSlash,
slugify,
} from '../../utils'
import { getOperationBasePath } from './rest.utils'
Expand Down Expand Up @@ -61,7 +60,7 @@ export const buildRestOperations: OperationsBuilder<OpenAPIV3.Document> = async
debugCtx,
)

const { paths, servers } = document.data
const { paths, servers } = effectiveDocument

const operations: TYPE.VersionRestOperation[] = []
if (!paths) { return [] }
Expand All @@ -77,9 +76,8 @@ export const buildRestOperations: OperationsBuilder<OpenAPIV3.Document> = async
await asyncFunction(() => {
const methodData = pathData[key as OpenAPIV3.HttpMethods]
const basePath = getOperationBasePath(methodData?.servers || pathData?.servers || servers || [])
const operationPath = basePath + path

const operationId = slugify(`${removeFirstSlash(operationPath)}-${key}`)
const operationId = getOperationId(basePath, key, path)

if (ctx.operationResolver(operationId)) {
ctx.notifications.push({
Expand Down
Loading
Loading