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

Commit

Permalink
feat: support references to other files (#76)
Browse files Browse the repository at this point in the history
* test: add a failing multifile test

* test: improve multi file tests

* feat: compose two files

* feat: resolve parts of other files

* feat: find files with relative paths in external files

* fix: single specification breaks resolveReference

* chore: don’t throw an error when a reference is invalid

* chore: clean up

* docs: update README

* refactor: move tests around

* chore: clean up

* chore: enable failing test

* chore: enable reference example again

* test: use correct helper method for externalPathItemRef test

* chore: improve error message when URIs can’t be found

* refactor: move uri not found error message to global errors

* chore: remove resolveReferences from Validator class, not needed anymore

* chore: clean up

* chore: add TODO

* fix: slashes in uris are not escaped properly

* fix: broken import

* refactor: make resolveReferences return an object

* feat: properly return all errors

* fix: TS issues

* fix: tests

* refactor: improve errors

* docs: add a section about file references

* docs(changeset): feat: file references

* fix: validate function can’t find external references

* chore: clean up

* chore: enable one more test

* refactor: merge lists of open api versions and specifications

* refactor: simplify error message

* refactor: simplify validate function

* refactor: move all function to the utils folder

* fix: demo doesn’t return any result
  • Loading branch information
hanspagel authored May 3, 2024
1 parent 36639d7 commit 56e7033
Show file tree
Hide file tree
Showing 96 changed files with 831 additions and 817 deletions.
5 changes: 5 additions & 0 deletions .changeset/spotty-chairs-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@scalar/openapi-parser": patch
---

feat: file references
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ Modern OpenAPI parser written in TypeScript with support for OpenAPI 3.1, OpenAP

References are hard and the following features aren’t implemented yet (but will be in the future):

- references inside inside referenced files (recursion, yay)
- circular references in referenced files (recursion inside recursion, yay)
- URLs (low priority though)
- URLs

## Installation

Expand Down Expand Up @@ -130,6 +128,22 @@ const result = openapi()
.get()
```

## Advanced: File references

You can reference other files, too. To do that, the parser needs to know what files are available.

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

// load a file and all referenced files
const filesystem = loadFiles('./openapi.yaml')
// instead of just passing a single specification, pass the whole “filesystem”
const result = await resolve(filesystem)
```

You don’t have to use `loadFiles`, though. You just need to stick to the format. That enables you store the files
wherever you want (maybe in a database?) or to use the package in a browser environment.

## Community

We are API nerds. You too? Let’s chat on Discord: <https://discord.gg/scalar>
Expand Down
25 changes: 17 additions & 8 deletions packages/openapi-parser/src/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import Ajv04 from 'ajv-draft-04'
import Ajv2020 from 'ajv/dist/2020'

import Swagger20 from '../schemas/v2.0/schema.json'
import OpenApi30 from '../schemas/v3.0/schema.json'
import OpenApi31 from '../schemas/v3.1/schema.json'

/**
* A list of the supported OpenAPI versions
* A list of the supported OpenAPI specifications
*/
export const supportedVersions = ['2.0', '3.0', '3.1'] as const
export const OpenApiSpecifications = {
'2.0': Swagger20,
'3.0': OpenApi30,
'3.1': OpenApi31,
}

export type SupportedVersion = (typeof supportedVersions)[number]
export type OpenApiVersion = keyof typeof OpenApiSpecifications

export const OpenApiVersions = Object.keys(
OpenApiSpecifications,
) as OpenApiVersion[]

/**
* Configure available JSON Schema versions
Expand All @@ -24,11 +36,8 @@ export const ERRORS = {
// 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.',
INVALID_REFERENCE: 'Can’t resolve URI: %s',
EXTERNAL_REFERENCE_NOT_SUPPORTED:
'External references are not supported yet: %s',
INVALID_REFERENCE: 'Can’t resolve reference: %s',
EXTERNAL_REFERENCE_NOT_FOUND: 'Can’t resolve external reference: %s',
} as const

export type VALIDATOR_ERROR = keyof typeof ERRORS

export const inlinedRefs = 'x-inlined-refs'
61 changes: 24 additions & 37 deletions packages/openapi-parser/src/lib/Validator/Validator.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
import addFormats from 'ajv-formats'

import Swagger20 from '../../../schemas/v2.0/schema.json'
import OpenApi30 from '../../../schemas/v3.0/schema.json'
import OpenApi31 from '../../../schemas/v3.1/schema.json'
import {
ERRORS,
type SupportedVersion,
inlinedRefs,
OpenApiSpecifications,
type OpenApiVersion,
OpenApiVersions,
jsonSchemaVersions,
supportedVersions,
} from '../../configuration'
import type { AnyObject, Filesystem, ValidateResult } from '../../types'
import { details as getOpenApiVersion } from '../../utils'
import { checkReferences } from './checkReferences'
import { resolveReferences } from './resolveReferences'
import { transformErrors } from './transformErrors'

// All available schemas
const schemas = {
'2.0': Swagger20,
'3.0': OpenApi30,
'3.1': OpenApi31,
}
import { resolveReferences } from '../../utils/resolveReferences'
import { transformErrors } from '../../utils/transformErrors'

export class Validator {
public version: string

public static supportedVersions = supportedVersions
public static supportedVersions = OpenApiVersions

// Object with function *or* object { errors: string }
protected ajvValidators: Record<
Expand All @@ -36,8 +25,6 @@ export class Validator {
}
> = {}

protected externalRefs: Record<string, AnyObject> = {}

protected errors: string

protected specificationVersion: string
Expand All @@ -46,12 +33,6 @@ export class Validator {

public specification: AnyObject

resolveReferences(filesystem?: Filesystem) {
return resolveReferences(
filesystem.find((file) => file.isEntrypoint === true).specification,
)
}

/**
* Checks whether a specification is valid and all references can be resolved.
*/
Expand All @@ -77,11 +58,6 @@ export class Validator {
}
}

// TODO: Do we want to keep external references in the spec?
if (Object.keys(this.externalRefs).length > 0) {
specification[inlinedRefs] = this.externalRefs
}

// Meta data about the specification
const { version, specificationType, specificationVersion } =
getOpenApiVersion(specification)
Expand All @@ -105,11 +81,6 @@ export class Validator {
const validateSchema = await this.getAjvValidator(version)
const schemaResult = validateSchema(specification)

// Check if the references are valid
if (schemaResult) {
return checkReferences(entrypoint.specification)
}

// Error handling
if (validateSchema.errors) {
if (validateSchema.errors.length > 0) {
Expand All @@ -120,6 +91,22 @@ export class Validator {
}
}

// Check if the references are valid
if (schemaResult) {
const resolvedReferences = resolveReferences(filesystem)

if (resolvedReferences.errors.length > 0) {
return {
valid: false,
errors: resolvedReferences.errors,
}
} else {
return {
...resolvedReferences,
}
}
}

// Whoops … no errors? Actually, that should never happen.
return {
valid: false,
Expand All @@ -136,14 +123,14 @@ export class Validator {
/**
* Ajv JSON schema validator
*/
async getAjvValidator(version: SupportedVersion) {
async getAjvValidator(version: OpenApiVersion) {
// Schema loaded already
if (this.ajvValidators[version]) {
return this.ajvValidators[version]
}

// Load OpenAPI Schema
const schema = schemas[version]
const schema = OpenApiSpecifications[version]

// Load JSON Schema
const AjvClass = jsonSchemaVersions[schema.$schema]
Expand Down

This file was deleted.

Loading

0 comments on commit 56e7033

Please sign in to comment.