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

feat: throw on error #138

Merged
merged 7 commits into from
Jun 21, 2024
Merged
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
5 changes: 5 additions & 0 deletions .changeset/thick-pumpkins-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@scalar/openapi-parser': patch
---

feat: validate and dereference, throwOnError option
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,26 @@ const result = openapi()
.get()
```

### Then/Catch syntax

If you’re more the then/catch type of guy, that’s fine:

```ts
import { validate } from '@scalar/openapi-parser'

const specification = …

validate(specification, {
throwOnError: true,
})
.then(result => {
// Success
})
.catch(error => {
// Failure
})
```

### Advanced: URL and file references

You can reference other files, too. To do that, the parser needs to know what files are available.
Expand Down
4 changes: 2 additions & 2 deletions packages/openapi-parser/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export const OpenApiVersions = Object.keys(
* List of error messages used in the Validator
*/
export const ERRORS = {
EMPTY_OR_INVALID: 'Cannot find JSON, YAML or filename in data',
EMPTY_OR_INVALID: 'Can’t find JSON, YAML or filename in data',
// URI_MUST_BE_STRING: 'uri parameter or $id attribute must be a string',
OPENAPI_VERSION_NOT_SUPPORTED:
'Cannot find supported Swagger/OpenAPI version in specification, version must be a string.',
'Can’t find supported Swagger/OpenAPI version in specification, version must be a string.',
INVALID_REFERENCE: 'Can’t resolve reference: %s',
EXTERNAL_REFERENCE_NOT_FOUND: 'Can’t resolve external reference: %s',
FILE_DOES_NOT_EXIST: 'File does not exist: %s',
Expand Down
22 changes: 19 additions & 3 deletions packages/openapi-parser/src/lib/Validator/Validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import {
type OpenApiVersion,
OpenApiVersions,
} from '../../configuration'
import type { AnyObject, Filesystem, ValidateResult } from '../../types'
import type {
AnyObject,
Filesystem,
ThrowOnErrorOption,
ValidateResult,
} from '../../types'
import { details as getOpenApiVersion } from '../../utils/details'
import { resolveReferences } from '../../utils/resolveReferences'
import { transformErrors } from '../../utils/transformErrors'
Expand Down Expand Up @@ -45,7 +50,10 @@ export class Validator {
/**
* Checks whether a specification is valid and all references can be resolved.
*/
async validate(filesystem: Filesystem): Promise<ValidateResult> {
async validate(
filesystem: Filesystem,
options?: ThrowOnErrorOption,
): Promise<ValidateResult> {
const entrypoint = filesystem.find((file) => file.isEntrypoint)
const specification = entrypoint?.specification

Expand All @@ -61,6 +69,10 @@ export class Validator {
try {
// AnyObject is empty or invalid
if (specification === undefined || specification === null) {
if (options?.throwOnError) {
throw new Error(ERRORS.EMPTY_OR_INVALID)
}

return {
valid: false,
errors: transformErrors(entrypoint, ERRORS.EMPTY_OR_INVALID),
Expand Down Expand Up @@ -101,7 +113,7 @@ export class Validator {
}

// Check if the references are valid
const resolvedReferences = resolveReferences(filesystem)
const resolvedReferences = resolveReferences(filesystem, options)

return {
valid: schemaResult && resolvedReferences.valid,
Expand All @@ -110,6 +122,10 @@ export class Validator {
}
} catch (error) {
// Something went horribly wrong!
if (options?.throwOnError) {
throw error
}

return {
valid: false,
errors: transformErrors(entrypoint, error.message ?? error),
Expand Down
139 changes: 139 additions & 0 deletions packages/openapi-parser/src/pipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,143 @@ describe('pipeline', () => {
expect(result.errors).toStrictEqual([])
expect(result.schema.info.title).toBe('Hello World')
})

it('throws an error when dereference fails (global)', async () => {
expect(async () => {
await openapi({
throwOnError: true,
})
.load({
openapi: '3.1.0',
info: {},
paths: {
'/foobar': {
post: {
requestBody: {
$ref: '#/components/requestBodies/DoesNotExist',
},
},
},
},
})
.dereference()
.get()
}).rejects.toThrowError(
'Can’t resolve reference: #/components/requestBodies/DoesNotExist',
)
})

it('throws an error when dereference fails (only dereference)', async () => {
expect(async () => {
await openapi()
.load({
openapi: '3.1.0',
info: {},
paths: {
'/foobar': {
post: {
requestBody: {
$ref: '#/components/requestBodies/DoesNotExist',
},
},
},
},
})
.dereference({
throwOnError: true,
})
.get()
}).rejects.toThrowError(
'Can’t resolve reference: #/components/requestBodies/DoesNotExist',
)
})

it('throws an error when validate fails (global)', async () => {
expect(async () => {
await openapi({
throwOnError: true,
})
.load({
openapi: '3.1.0',
info: {
title: 'Hello World',
},
paths: {
'/foobar': {
post: {
requestBody: {
$ref: '#/components/requestBodies/DoesNotExist',
},
},
},
},
})
.validate()
.get()
}).rejects.toThrowError(
'Can’t resolve reference: #/components/requestBodies/DoesNotExist',
)
})

it('throws an error when validate fails (only validate)', async () => {
expect(async () => {
await openapi()
.load({
openapi: '3.1.0',
info: {
title: 'Hello World',
},
paths: {
'/foobar': {
post: {
requestBody: {
$ref: '#/components/requestBodies/DoesNotExist',
},
},
},
},
})
.validate({
throwOnError: true,
})
.get()
}).rejects.toThrowError(
'Can’t resolve reference: #/components/requestBodies/DoesNotExist',
)
})

it('works with then & catch', async () => {
return new Promise((resolve, reject) => {
openapi()
.load({
openapi: '3.1.0',
info: {
title: 'Hello World',
},
paths: {
'/foobar': {
post: {
requestBody: {
$ref: '#/components/requestBodies/DoesNotExist',
},
},
},
},
})
.validate({
throwOnError: true,
})
.get()
.then(() => {
reject()
})
.catch((error) => {
expect(error.message).toBe(
'Can’t resolve reference: #/components/requestBodies/DoesNotExist',
)

resolve(null)
})
})
})
})
Loading