Skip to content
Merged
Show file tree
Hide file tree
Changes from 41 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 src/apitypes/rest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import { OpenAPIV3 } from 'openapi-types'
import { buildRestDocument, createRestExportDocument, dumpRestDocument } from './rest.document'
import { REST_API_TYPE, REST_DOCUMENT_TYPE } from './rest.consts'
import { compareRestOperationsData } from './rest.changes'
import { buildRestOperations, createNormalizedOperationId } from './rest.operations'
import { buildRestOperations } from './rest.operations'
import { parseRestFile } from './rest.parser'

import { ApiBuilder } from '../../types'
import { createNormalizedOperationId } from '../../utils'

export * from './rest.consts'

Expand Down
98 changes: 89 additions & 9 deletions src/apitypes/rest/rest.operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,35 @@ 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 {
buildSearchScope,
calculateOperationId,
capitalize,
copySymbolIfDefined,
getKeyValue,
getSplittedVersionKey,
getSymbolValueIfDefined,
isDeprecatedOperationItem,
isOperationDeprecated,
isValidHttpMethod,
normalizePath,
rawToApiKind,
setValueByPath,
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 +65,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 +150,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 +187,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 +226,11 @@ export const calculateSpecRefs = (sourceDocument: unknown, normalizedSpec: unkno
return
}
const componentName = matchResult.grepValues[grepKey].toString()
const component = getKeyValue(sourceDocument, ...matchResult.path)
const component = getKeyValue(sourceDocument, ...matchResult.path) as Record<string, unknown>
if (!component) {
return
}

if (models && !models[componentName] && isComponentsSchemaRef(matchResult.path)) {
let componentHash = componentsHashMap?.get(componentName)
if (componentHash) {
Expand All @@ -226,8 +241,68 @@ export const calculateSpecRefs = (sourceDocument: unknown, normalizedSpec: unkno
models[componentName] = componentHash
}
}

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

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

function reduceComponentPathItemsToOperations(
resultSpec: RestOperationData,
normalizedDocument: RestOperationData,
operations: OperationId[],
): void {
const { paths } = normalizedDocument

for (const path of Object.keys(paths)) {
const sourcePathItem = paths[path] as OpenAPIV3.PathItemObject
const pathItemComponentJsonPath = getPathItemComponentJsonPath(sourcePathItem)
if (!pathItemComponentJsonPath) {
continue
}

const pathItemComponent = getValueByPath(resultSpec, pathItemComponentJsonPath) as OpenAPIV3.PathItemObject

const operationIds: OpenAPIV3.HttpMethods[] = (Object.keys(pathItemComponent) 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 = calculateOperationId(basePath, httpMethod, path)
return operations.includes(operationId)
})

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

const getPathItemComponentJsonPath = (sourcePathItem: OpenAPIV3.PathItemObject): JsonPath | undefined => {
const refs = getSymbolValueIfDefined(sourcePathItem, INLINE_REFS_FLAG) as string[] | undefined
if (!refs || refs.length === 0) {
return undefined
}

return parseRef(refs[0])?.jsonPath
}

export const isComponentsSchemaRef = (path: JsonPath): boolean => {
Expand All @@ -236,6 +311,7 @@ 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,
Expand All @@ -257,15 +333,19 @@ const createSingleOperationSpec = (
): TYPE.RestOperationData => {
const pathData = document.paths[path] as OpenAPIV3.PathItemObject

const isRefPathData = !!pathData.$ref
return {
openapi: openapi ?? '3.0.0',
...takeIfDefined({ servers }),
...takeIfDefined({ security }), // TODO: remove duplicates in security
paths: {
[path]: {
...extractCommonPathItemProperties(pathData),
[method]: { ...pathData[method] },
},
[path]: isRefPathData
? pathData
: {
...extractCommonPathItemProperties(pathData),
[method]: { ...pathData[method] },
...copySymbolIfDefined(pathData, INLINE_REFS_FLAG),
},
},
components: {
...takeIfDefined({ securitySchemes }),
Expand Down
20 changes: 4 additions & 16 deletions src/apitypes/rest/rest.operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,8 @@
import { OpenAPIV3 } from 'openapi-types'

import { buildRestOperation } from './rest.operation'
import { OperationIdNormalizer, OperationsBuilder } from '../../types'
import {
createBundlingErrorHandler,
IGNORE_PATH_PARAM_UNIFIED_PLACEHOLDER,
removeComponents,
removeFirstSlash,
slugify,
} from '../../utils'
import { OperationsBuilder } from '../../types'
import { calculateOperationId, createBundlingErrorHandler, removeComponents } from '../../utils'
import { getOperationBasePath } from './rest.utils'
import type * as TYPE from './rest.types'
import { HASH_FLAG, INLINE_REFS_FLAG, MESSAGE_SEVERITY, NORMALIZE_OPTIONS, ORIGINS_SYMBOL } from '../../consts'
Expand Down Expand Up @@ -61,7 +55,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 +71,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 = calculateOperationId(basePath, key, path)

if (ctx.operationResolver(operationId)) {
ctx.notifications.push({
Expand Down Expand Up @@ -112,8 +105,3 @@ export const buildRestOperations: OperationsBuilder<OpenAPIV3.Document> = async
}
return operations
}

export const createNormalizedOperationId: OperationIdNormalizer = (operation) => {
const { metadata: { path, method } } = operation
return slugify(`${path}-${method}`, [], IGNORE_PATH_PARAM_UNIFIED_PLACEHOLDER)
}
2 changes: 2 additions & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
VALIDATION_RULES_SEVERITY_LEVEL_WARNING,
ValidationRulesSeverity,
} from './types'
import { OpenAPIV3 } from 'openapi-types'

export const DEFAULT_BATCH_SIZE = 32

Expand Down Expand Up @@ -169,3 +170,4 @@ export const EMPTY_CHANGE_SUMMARY_DTO = {
}

export const CUSTOM_PARAMETER_API_AUDIENCE = 'x-api-audience'
export const HTTP_METHODS_SET = new Set(Object.values(OpenAPIV3.HttpMethods) as string[])
Loading
Loading