Skip to content
This repository was archived by the owner on Aug 28, 2024. It is now read-only.

Commit 058d0a8

Browse files
committed
feat: load files, add throwOnError to load
1 parent d10a9b7 commit 058d0a8

File tree

6 files changed

+169
-18
lines changed

6 files changed

+169
-18
lines changed

packages/openapi-parser/src/configuration/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const ERRORS = {
2828
INVALID_REFERENCE: 'Can’t resolve reference: %s',
2929
EXTERNAL_REFERENCE_NOT_FOUND: 'Can’t resolve external reference: %s',
3030
FILE_DOES_NOT_EXIST: 'File does not exist: %s',
31+
NO_CONTENT: 'No content found',
3132
} as const
3233

3334
export type VALIDATOR_ERROR = keyof typeof ERRORS

packages/openapi-parser/src/types/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type AnyObject = Record<string, any>
1212

1313
export type LoadResult = {
1414
filesystem: Filesystem
15+
errors?: ErrorObject[]
1516
}
1617

1718
export type ValidateResult = {

packages/openapi-parser/src/utils/load/load.test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -395,4 +395,26 @@ describe('load', async () => {
395395
},
396396
})
397397
})
398+
399+
it('returns an error', async () => {
400+
const { errors } = await load('INVALID', {
401+
plugins: [readFiles(), fetchUrls()],
402+
})
403+
404+
expect(errors).toMatchObject([
405+
{
406+
code: 'EXTERNAL_REFERENCE_NOT_FOUND',
407+
message: 'Can’t resolve external reference: INVALID',
408+
},
409+
])
410+
})
411+
412+
it('throws an error', async () => {
413+
expect(async () => {
414+
await load('INVALID', {
415+
plugins: [readFiles(), fetchUrls()],
416+
throwOnError: true,
417+
})
418+
}).rejects.toThrowError('Can’t resolve external reference: INVALID')
419+
})
398420
})

packages/openapi-parser/src/utils/load/load.ts

+61-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import type { Filesystem, LoadResult } from '../../types'
1+
import { ERRORS } from '../../configuration'
2+
import type {
3+
AnyObject,
4+
ErrorObject,
5+
Filesystem,
6+
LoadResult,
7+
ThrowOnErrorOption,
8+
} from '../../types'
29
import { getEntrypoint } from '../getEntrypoint'
310
import { getListOfReferences } from '../getListOfReferences'
411
import { makeFilesystem } from '../makeFilesystem'
@@ -18,26 +25,64 @@ export async function load(
1825
plugins?: LoadPlugin[]
1926
filename?: string
2027
filesystem?: Filesystem
21-
},
28+
} & ThrowOnErrorOption,
2229
): Promise<LoadResult> {
30+
const errors: ErrorObject[] = []
31+
2332
// Don’t load a reference twice, check the filesystem before fetching something
2433
if (
2534
options?.filesystem &&
2635
options?.filesystem.find((entry) => entry.filename === value)
2736
) {
2837
return {
2938
filesystem: options.filesystem,
39+
errors,
3040
}
3141
}
3242

3343
// Check whether the value is an URL or file path
3444
const plugin = options?.plugins?.find((plugin) => plugin.check(value))
35-
const content = normalize(plugin ? await plugin.get(value) : value)
45+
46+
let content: AnyObject
47+
48+
if (plugin) {
49+
try {
50+
content = normalize(await plugin.get(value))
51+
} catch (error) {
52+
if (options?.throwOnError) {
53+
throw new Error(
54+
ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', value),
55+
)
56+
}
57+
58+
errors.push({
59+
code: 'EXTERNAL_REFERENCE_NOT_FOUND',
60+
message: ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', value),
61+
})
62+
63+
return {
64+
filesystem: [],
65+
errors,
66+
}
67+
}
68+
} else {
69+
content = normalize(value)
70+
}
3671

3772
// No content
3873
if (content === undefined) {
74+
if (options?.throwOnError) {
75+
throw new Error('No content to load')
76+
}
77+
78+
errors.push({
79+
code: 'NO_CONTENT',
80+
message: ERRORS.NO_CONTENT,
81+
})
82+
3983
return {
4084
filesystem: [],
85+
errors,
4186
}
4287
}
4388

@@ -56,6 +101,7 @@ export async function load(
56101
if (listOfReferences.length === 0) {
57102
return {
58103
filesystem,
104+
errors,
59105
}
60106
}
61107

@@ -79,12 +125,17 @@ export async function load(
79125
continue
80126
}
81127

82-
const { filesystem: referencedFiles } = await load(target, {
83-
...options,
84-
// Make the filename the exact same value as the $ref
85-
// TODO: This leads to problems, if there are multiple references with the same file name but in different folders
86-
filename: reference,
87-
})
128+
const { filesystem: referencedFiles, errors: newErrors } = await load(
129+
target,
130+
{
131+
...options,
132+
// Make the filename the exact same value as the $ref
133+
// TODO: This leads to problems, if there are multiple references with the same file name but in different folders
134+
filename: reference,
135+
},
136+
)
137+
138+
errors.push(...newErrors)
88139

89140
filesystem = [
90141
...filesystem,
@@ -99,5 +150,6 @@ export async function load(
99150

100151
return {
101152
filesystem,
153+
errors,
102154
}
103155
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"openapi": "3.1.0",
3+
"info": {
4+
"title": "Hello World",
5+
"version": "1.0.0"
6+
},
7+
"paths": {
8+
"/foobar": {
9+
"post": {
10+
"requestBody": {
11+
"$ref": "#/components/requestBodies/Foobar"
12+
}
13+
}
14+
}
15+
},
16+
"components": {
17+
"requestBodies": {
18+
"Foobar": {
19+
"content": {}
20+
}
21+
}
22+
}
23+
}

packages/openapi-parser/tests/migration-layer.test.ts

+61-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import OriginalSwaggerParser from '@apidevtools/swagger-parser'
2-
import { describe, expect, it } from 'vitest'
2+
import path from 'node:path'
3+
import { describe, expect, it, vi } from 'vitest'
34

45
import { dereference } from '../src/utils/dereference'
6+
import { load } from '../src/utils/load'
7+
import { fetchUrls } from '../src/utils/load/plugins/fetchUrls'
8+
import { readFiles } from '../src/utils/load/plugins/readFiles'
59
import { validate } from '../src/utils/validate'
610

711
const myAPI = JSON.stringify({
@@ -30,19 +34,33 @@ const myAPI = JSON.stringify({
3034

3135
class SwaggerParser {
3236
static async validate(api: string, callback: (err: any, api: any) => void) {
33-
validate(api, {
34-
throwOnError: true,
35-
})
36-
.then((result) => {
37-
callback(null, result.schema)
37+
try {
38+
const { filesystem } = await load(api, {
39+
plugins: [fetchUrls(), readFiles()],
40+
throwOnError: true,
3841
})
39-
.catch((error) => {
40-
callback(error, null)
42+
43+
validate(filesystem, {
44+
throwOnError: true,
4145
})
46+
.then((result) => {
47+
callback(null, result.schema)
48+
})
49+
.catch((error) => {
50+
callback(error, null)
51+
})
52+
} catch (error) {
53+
callback(error, null)
54+
}
4255
}
4356

4457
static async dereference(api: string) {
45-
return dereference(api).then((result) => result.schema)
58+
const { filesystem } = await load(api, {
59+
plugins: [fetchUrls(), readFiles()],
60+
throwOnError: true,
61+
})
62+
63+
return dereference(filesystem).then((result) => result.schema)
4664
}
4765
}
4866

@@ -85,4 +103,38 @@ describe('dereference', () => {
85103
// so you can easily access any part of the API using simple dot notation
86104
expect(api?.paths?.['/foobar']?.post?.requestBody?.content).toEqual({})
87105
})
106+
107+
it('dereferences URLs', async () => {
108+
global.fetch = async (url: string) =>
109+
({
110+
text: async () => {
111+
if (url === 'http://example.com/specification/openapi.yaml') {
112+
return myAPI
113+
}
114+
115+
throw new Error('Not found')
116+
},
117+
}) as Response
118+
119+
let api = await SwaggerParser.dereference(
120+
'http://example.com/specification/openapi.yaml',
121+
)
122+
123+
// The `api` object is a normal JavaScript object,
124+
// so you can easily access any part of the API using simple dot notation
125+
expect(api?.paths?.['/foobar']?.post?.requestBody?.content).toEqual({})
126+
})
127+
128+
it('dereferences files', async () => {
129+
const EXAMPLE_FILE = path.join(
130+
new URL(import.meta.url).pathname,
131+
'../../tests/migration-layer.json',
132+
)
133+
134+
let api = await SwaggerParser.dereference(EXAMPLE_FILE)
135+
136+
// The `api` object is a normal JavaScript object,
137+
// so you can easily access any part of the API using simple dot notation
138+
expect(api?.paths?.['/foobar']?.post?.requestBody?.content).toEqual({})
139+
})
88140
})

0 commit comments

Comments
 (0)