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
10 changes: 9 additions & 1 deletion src/apitypes/rest/rest.changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,13 @@ import { calculateObjectHash } from '../../utils/hashes'
import { REST_API_TYPE } from './rest.consts'
import { OpenAPIV3 } from 'openapi-types'
import {
extractOperationSecurityDiffs,
extractOpenapiVersionDiff,
extractPathParamRenameDiff,
extractRootSecurityDiffs,
extractRootServersDiffs,
extractSecuritySchemesDiffs,
extractSecuritySchemesNames,
validateGroupPrefix,
} from './rest.utils'
import { createOperationChange, getOperationTags, OperationsMap } from '../../components'
Expand Down Expand Up @@ -167,13 +170,18 @@ export const compareDocuments = async (

let operationDiffs: Diff[] = []
if (operationPotentiallyChanged) {
const operationSecurityDiffs = extractOperationSecurityDiffs(methodData as OpenAPIV3.OperationObject)
const shouldTakeRootSecurityDiffs = operationSecurityDiffs.length === 0 && !methodData?.security
const relevantSecuritySchemesNames = shouldTakeRootSecurityDiffs ? extractSecuritySchemesNames(merged.security ?? []) : extractSecuritySchemesNames(methodData?.security ?? [])
operationDiffs = [
...(methodData as WithAggregatedDiffs<OpenAPIV3.OperationObject>)[DIFFS_AGGREGATED_META_KEY] ?? [],
...extractOpenapiVersionDiff(merged),
...extractRootServersDiffs(merged),
...extractRootSecurityDiffs(merged),
...shouldTakeRootSecurityDiffs ? extractRootSecurityDiffs(merged) : [],
...extractSecuritySchemesDiffs(merged.components, relevantSecuritySchemesNames),
...extractPathParamRenameDiff(merged, path),
// parameters, servers, summary, description and extensionKeys are moved from path to method in pathItemsUnification during normalization in apiDiff, so no need to aggregate them here
// note that operation security diffs are not aggregated here, because they are in aggregated diffs for operation object
]
}
if (operationAddedOrRemoved) {
Expand Down
24 changes: 18 additions & 6 deletions src/apitypes/rest/rest.operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import {
takeIfDefined,
} from '../../utils'
import { API_KIND, INLINE_REFS_FLAG, ORIGINS_SYMBOL, VERSION_STATUS } from '../../consts'
import { getCustomTags, resolveApiAudience } from './rest.utils'
import { extractSecuritySchemesNames, getCustomTags, resolveApiAudience } from './rest.utils'
import { DebugPerformanceContext, syncDebugPerformance } from '../../utils/logs'
import {
calculateDeprecatedItems,
Expand Down Expand Up @@ -142,13 +142,15 @@ export const buildRestOperation = (
const models: Record<string, string> = {}
const apiKind = effectiveOperationObject[REST_KIND_KEY] || document.apiKind || API_KIND.BWC
const [specWithSingleOperation] = syncDebugPerformance('[ModelsAndOperationHashing]', () => {
const operationSecurity = effectiveOperationObject.security
const specWithSingleOperation = createSingleOperationSpec(
document.data,
path,
method,
openapi,
servers,
security,
operationSecurity,
components?.securitySchemes,
)
calculateSpecRefs(document.data, refsOnlySingleOperationSpec, specWithSingleOperation, [operationId], models, componentsHashMap)
Expand Down Expand Up @@ -320,22 +322,32 @@ const isOperationPaths = (paths: JsonPath[]): boolean => {
// 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
const createSingleOperationSpec = (
export const createSingleOperationSpec = (
document: OpenAPIV3.Document,
path: string,
method: OpenAPIV3.HttpMethods,
openapi?: string,
servers?: OpenAPIV3.ServerObject[],
security?: OpenAPIV3.SecurityRequirementObject[],
operationSecurity?: OpenAPIV3.SecurityRequirementObject[],
securitySchemes?: { [p: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.SecuritySchemeObject },
): TYPE.RestOperationData => {
const pathData = document.paths[path] as OpenAPIV3.PathItemObject

// Filter security schemes to only include used ones
const effectiveSecurity = operationSecurity ?? security ?? []
const usedSecuritySchemeNames = extractSecuritySchemesNames(effectiveSecurity)
const effectiveSecuritySchemes = securitySchemes && usedSecuritySchemeNames.size > 0
? Object.fromEntries(
Object.entries(securitySchemes).filter(([name]) => usedSecuritySchemeNames.has(name)),
)
: undefined

const isRefPathData = !!pathData.$ref
return {
openapi: openapi ?? '3.0.0',
...takeIfDefined({ servers }),
...takeIfDefined({ security }), // TODO: remove duplicates in security
...!operationSecurity ? takeIfDefined({ security }) : {},// Only add root security if operation security is not explicitly defined
paths: {
[path]: isRefPathData
? pathData
Expand All @@ -345,9 +357,9 @@ const createSingleOperationSpec = (
...extractSymbolProperty(pathData, INLINE_REFS_FLAG),
},
},
components: {
...takeIfDefined({ securitySchemes }),
},
...takeIfDefined({
components: effectiveSecuritySchemes ? { securitySchemes: effectiveSecuritySchemes } : undefined,
}),
}
}

Expand Down
48 changes: 43 additions & 5 deletions src/apitypes/rest/rest.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,55 @@ export const extractRootServersDiffs = (doc: OpenAPIV3.Document): Diff[] => {
]
}

export const extractRootSecurityDiffs = (doc: OpenAPIV3.Document): Diff[] => {
const addOrRemoveSecurityDiff = (doc as WithDiffMetaRecord<OpenAPIV3.Document>)[DIFF_META_KEY]?.security
const securityInternalDiffs = (doc.security as WithAggregatedDiffs<OpenAPIV3.SecurityRequirementObject[]> | undefined)?.[DIFFS_AGGREGATED_META_KEY] ?? []
const componentsSecuritySchemesDiffs = (doc.components?.securitySchemes as WithAggregatedDiffs<Record<string, OpenAPIV3.SecuritySchemeObject>>)[DIFFS_AGGREGATED_META_KEY] ?? []
const extractSecurityDiffs = (source: WithDiffMetaRecord<{ security?: OpenAPIV3.SecurityRequirementObject[] }>): Diff[] => {
const addOrRemoveSecurityDiff = source[DIFF_META_KEY]?.security
const securityInternalDiffs = (source.security as WithAggregatedDiffs<OpenAPIV3.SecurityRequirementObject[]> | undefined)?.[DIFFS_AGGREGATED_META_KEY] ?? []
return [
...(addOrRemoveSecurityDiff ? [addOrRemoveSecurityDiff] : []),
...securityInternalDiffs,
...componentsSecuritySchemesDiffs,
]
}

export const extractRootSecurityDiffs = (doc: OpenAPIV3.Document): Diff[] => {
return extractSecurityDiffs(doc as WithDiffMetaRecord<OpenAPIV3.Document>)
}

export const extractOperationSecurityDiffs = (operation: OpenAPIV3.OperationObject): Diff[] => {
return extractSecurityDiffs(operation as WithDiffMetaRecord<OpenAPIV3.OperationObject>)
}

export const extractSecuritySchemesNames = (security: OpenAPIV3.SecurityRequirementObject[]): Set<string> => {
return new Set(security.flatMap(securityRequirement => Object.keys(securityRequirement)))
}

export const extractSecuritySchemesDiffs = (components: OpenAPIV3.ComponentsObject | undefined, securitySchemesNames: Set<string>): Diff[] => {
if (!components || !components.securitySchemes) {
return []
}
const result: Diff[] = []

const addRemoveSecuritySchemesDiffs = (components.securitySchemes as WithDiffMetaRecord<Record<string, OpenAPIV3.SecuritySchemeObject>>)?.[DIFF_META_KEY]
if (addRemoveSecuritySchemesDiffs) {
for (const schemeName of securitySchemesNames) {
const diff = addRemoveSecuritySchemesDiffs[schemeName]
if (diff) {
result.push(diff)
}
}
}

for (const schemeName of securitySchemesNames) {
const securityScheme = components.securitySchemes[schemeName]
if (securityScheme) {
const aggregatedDiffs = (securityScheme as WithAggregatedDiffs<OpenAPIV3.SecuritySchemeObject>)?.[DIFFS_AGGREGATED_META_KEY] ?? []
result.push(...aggregatedDiffs)
}
}


return result
}

export function validateGroupPrefix(group: unknown, paramName: string): void {
if (group === undefined) {
return
Expand Down
Loading
Loading