Skip to content

Commit 217c4b7

Browse files
committed
fix: do not fail publish on description override in OAS 3.0.x
1 parent afa6b66 commit 217c4b7

File tree

7 files changed

+103
-39
lines changed

7 files changed

+103
-39
lines changed

package-lock.json

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@
2828
"operation:test": "node ./test/operation.js",
2929
"profile": "node --inspect-brk ./test/profile.js",
3030
"test:coverage": "jest --verbose --coverage",
31-
"update-lock-file": "update-lock-file @netcracker"
31+
"update-lock-file": "update-lock-file @netcracker"
3232
},
3333
"dependencies": {
3434
"@netcracker/qubership-apihub-api-diff": "dev",
35-
"@netcracker/qubership-apihub-api-unifier": "1.0.4",
35+
"@netcracker/qubership-apihub-api-unifier": "dev",
3636
"@netcracker/qubership-apihub-json-crawl": "1.0.4",
3737
"@netcracker/qubership-apihub-graphapi": "1.0.8",
3838
"adm-zip": "0.5.10",
@@ -80,4 +80,4 @@
8080
"engines": {
8181
"node": ">=18.0.0"
8282
}
83-
}
83+
}

src/apitypes/rest/rest.operations.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import type * as TYPE from './rest.types'
2424
import { HASH_FLAG, INLINE_REFS_FLAG, MESSAGE_SEVERITY, NORMALIZE_OPTIONS, ORIGINS_SYMBOL } from '../../consts'
2525
import { asyncFunction } from '../../utils/async'
2626
import { logLongBuild, syncDebugPerformance } from '../../utils/logs'
27-
import { normalize } from '@netcracker/qubership-apihub-api-unifier'
27+
import { normalize, RefErrorType } from '@netcracker/qubership-apihub-api-unifier'
2828

2929
export const buildRestOperations: OperationsBuilder<OpenAPIV3.Document> = async (document, ctx, debugCtx) => {
3030
const documentWithoutComponents = removeComponents(document.data)
@@ -38,8 +38,8 @@ export const buildRestOperations: OperationsBuilder<OpenAPIV3.Document> = async
3838
originsFlag: ORIGINS_SYMBOL,
3939
hashFlag: HASH_FLAG,
4040
source: document.data,
41-
onRefResolveError: (_: string, __: PropertyKey[], ref: string) =>
42-
bundlingErrorHandler([`The $ref "${ref}" references an invalid location in the document.`]),
41+
onRefResolveError: (message: string, _path: PropertyKey[], _ref: string, errorType: RefErrorType) =>
42+
bundlingErrorHandler([{ message, errorType }]),
4343
},
4444
) as OpenAPIV3.Document
4545
const refsOnlyDocument = normalize(

src/utils/document.ts

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ import {
2929
PackageDocument,
3030
ResolvedDocument,
3131
VALIDATION_RULES_SEVERITY_LEVEL_ERROR,
32-
VALIDATION_RULES_SEVERITY_LEVEL_WARNING,
3332
VersionDocument,
3433
YAML_EXPORT_GROUP_FORMAT,
3534
} from '../types'
3635
import { bundle, Resolver } from 'api-ref-bundler'
3736
import { FILE_FORMAT_JSON, FILE_FORMAT_YAML, MESSAGE_SEVERITY } from '../consts'
3837
import { isNotEmpty } from './arrays'
38+
import { RefErrorType, RefErrorTypes } from '@netcracker/qubership-apihub-api-unifier'
3939

4040
export const EXPORT_FORMAT_TO_FILE_FORMAT = new Map<OperationsGroupExportFormat, FileFormat>([
4141
[YAML_EXPORT_GROUP_FORMAT, FILE_FORMAT_YAML],
@@ -121,42 +121,60 @@ export const getDocumentTitle = (fileId: string): string => {
121121
return fileId.substring(cutDot).split('/').pop()!.replace(/\.[^/.]+$/, '')
122122
}
123123

124-
export const createBundlingErrorHandler = (ctx: BuilderContext, fileId: FileId) => (messages: string[]): void => {
125-
switch (ctx.config.validationRulesSeverity?.brokenRefs) {
126-
case VALIDATION_RULES_SEVERITY_LEVEL_ERROR:
127-
throw new Error(messages[0])
128-
case VALIDATION_RULES_SEVERITY_LEVEL_WARNING:
129-
default:
130-
for (const message of messages) {
131-
ctx.notifications.push({
132-
severity: MESSAGE_SEVERITY.Error,
133-
message: message,
134-
fileId: fileId,
135-
})
136-
}
124+
export interface BundlingError {
125+
message: string
126+
errorType: RefErrorType
127+
}
128+
129+
export const createBundlingErrorHandler = (ctx: BuilderContext, fileId: FileId) => (errors: BundlingError[]): void => {
130+
// Only throw if severity is ERROR and there's at least one critical error
131+
if (ctx.config.validationRulesSeverity?.brokenRefs === VALIDATION_RULES_SEVERITY_LEVEL_ERROR) {
132+
const criticalError = errors.find(error =>
133+
error.errorType === RefErrorTypes.REF_NOT_FOUND ||
134+
error.errorType === RefErrorTypes.REF_NOT_VALID_FORMAT,
135+
)
136+
137+
if (criticalError) {
138+
throw new Error(criticalError.message)
139+
}
140+
}
141+
142+
// In other cases push all errors to notifications
143+
for (const error of errors) {
144+
ctx.notifications.push({
145+
severity: MESSAGE_SEVERITY.Error,
146+
message: error.message,
147+
fileId: fileId,
148+
})
137149
}
138150
}
139151

140152
export const getBundledFileDataWithDependencies = async (
141153
fileId: FileId,
142154
parsedFileResolver: _ParsedFileResolver,
143-
onError: (messages: string[]) => void,
155+
onError: (errors: BundlingError[]) => void,
144156
): Promise<{ data: any; dependencies: string[] }> => {
145157
const dependencies: string[] = []
146-
const errorMessages: string[] = []
158+
const errors: BundlingError[] = []
147159

148160
const resolver: Resolver = async (filepath: string) => {
149161
const data = await parsedFileResolver(filepath)
150162

151163
if (data === null) {
152164
// can't throw the error here because it will be suppressed: https://github.com/udamir/api-ref-bundler/blob/0.4.0/src/resolver.ts#L33
153-
errorMessages.push(`Unable to resolve the file "${filepath}" because it does not exist.`)
165+
errors.push({
166+
message: `Unable to resolve the file "${filepath}" because it does not exist.`,
167+
errorType: RefErrorTypes.REF_NOT_FOUND,
168+
})
154169
return {}
155170
}
156171

157172
if (data.kind !== FILE_KIND.TEXT) {
158173
// can't throw the error here because it will be suppressed: https://github.com/udamir/api-ref-bundler/blob/0.4.0/src/resolver.ts#L33
159-
errorMessages.push(`Unable to resolve the file "${filepath}" because it is not a valid text file.`)
174+
errors.push({
175+
message: `Unable to resolve the file "${filepath}" because it is not a valid text file.`,
176+
errorType: RefErrorTypes.REF_NOT_VALID_FORMAT,
177+
})
160178
return {}
161179
}
162180

@@ -169,8 +187,8 @@ export const getBundledFileDataWithDependencies = async (
169187

170188
const bundledFileData = await bundle(fileId, resolver)
171189

172-
if (isNotEmpty(errorMessages)) {
173-
onError(errorMessages)
190+
if (isNotEmpty(errors)) {
191+
onError(errors)
174192
}
175193

176194
return { data: bundledFileData, dependencies: dependencies }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"packageId": "reference-bundling/description-override",
3+
"apiType": "rest",
4+
"version": "v1",
5+
"files": [
6+
{
7+
"fileId": "openapi.yaml",
8+
"publish": true,
9+
"labels": [],
10+
"commitId": "6c778b1f44200bd19944a6a8eac10a4e5a21a1xd"
11+
}
12+
]
13+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
openapi: 3.0.3
2+
info:
3+
title: test
4+
version: 0.1.0
5+
paths:
6+
/path1:
7+
get:
8+
responses:
9+
'200':
10+
description: OK
11+
content:
12+
application/json:
13+
schema:
14+
$ref: '#/components/schemas/Test'
15+
description: Description override
16+
components:
17+
schemas:
18+
Test:
19+
type: object

test/reference-bundling.test.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('Reference bundling test', () => {
2222
const result = await pkg.publish(pkg.packageId)
2323

2424
expect(result).toEqual(notificationsMatcher([
25-
errorNotificationMatcher('references an invalid location'),
25+
errorNotificationMatcher('can\'t be resolved'),
2626
errorNotificationMatcher('does not exist'),
2727
]))
2828
expect(result.operations.size).toBe(1)
@@ -52,7 +52,7 @@ describe('Reference bundling test', () => {
5252
const result = await pkg.publish(pkg.packageId)
5353

5454
expect(result).toEqual(notificationsMatcher([
55-
errorNotificationMatcher('references an invalid location'),
55+
errorNotificationMatcher('can\'t be resolved'),
5656
errorNotificationMatcher('does not exist'),
5757
]))
5858
expect(result.operations.size).toBe(1)
@@ -64,14 +64,14 @@ describe('Reference bundling test', () => {
6464

6565
await expect(
6666
pkg.publish(pkg.packageId, { validationRulesSeverity: { brokenRefs: VALIDATION_RULES_SEVERITY_LEVEL_ERROR } }),
67-
).rejects.toThrow(/references an invalid location/)
67+
).rejects.toThrow(/can't be resolved/)
6868
})
6969

7070
test('should collect missing internal reference notification if severity level is not configured', async () => {
7171
const pkg = LocalRegistry.openPackage('reference-bundling/case5')
7272
const result = await pkg.publish(pkg.packageId)
7373

74-
expect(result).toEqual(notificationsMatcher([errorNotificationMatcher('references an invalid location')]))
74+
expect(result).toEqual(notificationsMatcher([errorNotificationMatcher('can\'t be resolved')]))
7575
expect(result.operations.size).toBe(1)
7676
})
7777

@@ -81,4 +81,18 @@ describe('Reference bundling test', () => {
8181
pkg.publish(pkg.packageId, { validationRulesSeverity: { brokenRefs: VALIDATION_RULES_SEVERITY_LEVEL_ERROR } }),
8282
).rejects.toThrow(/not a valid text file/)
8383
})
84+
85+
test('should collect notifications on publishing specification with incorrect description override', async () => {
86+
const pkg = LocalRegistry.openPackage('reference-bundling/description-override')
87+
const result = await pkg.publish(pkg.packageId, {
88+
validationRulesSeverity: {
89+
brokenRefs: VALIDATION_RULES_SEVERITY_LEVEL_ERROR,
90+
},
91+
})
92+
93+
expect(result).toEqual(notificationsMatcher([
94+
errorNotificationMatcher('can\'t have siblings in this specification version'),
95+
]))
96+
expect(result.operations.size).toBe(1)
97+
})
8498
})

0 commit comments

Comments
 (0)