Tool to merge multiple OpenAPI or AsyncAPI documents that use JSON Reference links (`$ref`) to reference API definition elements across source files.
npm install @apiture/api-ref-resolverapi-ref-resolver resolves multi-file API definition documents by replacing
external {$ref: "uri"} JSON Reference
objects with the object referenced at the uri.
The uri may be a file-path or a URL with an optional# JSON Pointer fragment.
For example, if components.yaml contains:
``yaml`
paths:
'/health':
get:
operationId: apiHealth
description: Return API Health
tags:
- Health
responses:
'200':
description: OK. The API is alive and active.
content:
application/json:
schema:
$ref: '#/components/schemas/health'
components:
parameters:
idempotencyKeyHeaderParam:
name: Idempotency-Key
description: Idempotency Key to guarantee client requests and not processed multiple times.
in: header
schema:
type: string
schemas:
health:
title: API Health
description: API Health response
type: object
properties:
status:
description: The API status.
type: string
enum:
- pass
- fail
- warn
and api.yaml contains
`yaml`
paths:
/health:
get:
$ref: 'components.yaml#/paths/~1health/get'
/thing:
parameters:
- $ref: 'components.yaml#/components/parameters/idempotencyKeyHeaderParam'
then running
`bash`
api-ref-resolver -i api.yaml -o resolved-api.yaml
will yield the following in resolved-api.yaml:
`yaml
paths:
/health:
get:
operationId: apiHealth
description: Return API Health
tags:
- Health
responses:
'200':
description: OK. The API is alive and active.
content:
application/json:
schema:
$ref: '#/components/schemas/health'
x-resolved-from: >-
components.yaml#/paths/~1health/get
/thing:
parameters:
- $ref: '#/components/parameters/idempotencyKeyHeaderParam'
components:
parameters:
idempotencyKeyHeaderParam:
name: Idempotency-Key
description: >-
Idempotency Key to guarantee client requests and not processed multiple
times.
in: header
schema:
type: string
x-resolved-from: >-
components.yaml#/components/parameters/idempotencyKeyHeaderParam
schemas:
health:
title: API Health
description: API Health response
type: object
properties:
status:
description: The API status.
type: string
enum:
- pass
- fail
- warn
x-resolved-from: >-
components.yaml#/components/schemas/health
x-resolved-from: >-
api.yaml
x-resolved-at: '2022-03-11T16:27:59.365Z'
`
The tool handles chains of JSON references (i.e. a.yaml references components from b.yaml which references components from c.yaml) asA
well as direct or indirect cycles (component references component B which references component A).
Unlike other generic $ref resolvers (1, 2, 3),api-ref-resolver treats components references specially.components/section/componentName
It understands reusable objects at the top-level of an API definition, such as #/components/schemas/schemaName, and attempts to
maintain those component structures; see Notes below.
Otherwise, it is specification agnostic and works with either
OpenAPI specification or AsyncAPI specification.
This tool does _not_ enforce JSON Reference strictness; that is, the $ref member may have siblings, as used in OpenAPI 3.1 Reference Objects.
`bash`
api-ref-resolver --input api.yaml --output resolved-api.yamlarr is also defined a shortcut command for api-ref-resolver
arr --input api.yaml --output resolved-api.yaml
arr -i api.yaml | some-other-pipeline >| resolved-api.yaml
Command line options:
`text
Usage: api-ref-resolver [options]
Options:
-V, --version output the version number
-i, --input
-n, --no-markers Do not add x-resolved-from and x-resolved-at markers
-o, --output
-f, --format [yaml|json] Output format for stdout if no --output option is used; default to yaml
-v, --verbose Verbose output
-h, --help display help for command
`
`javascript
import { ApiRefResolver } from '@apiture/api-ref-resolver';
import * as fs from 'fs';
import * as yaml from 'js-yaml';
const sourceFileName = 'api.yaml'
const outputFileName = 'resolved-api.yaml'
const resolver = new ApiRefResolver(sourceFileName);
const options: ApiRefOptions = {
verbose: false,
conflictStrategy: 'error', // 'error' | 'rename' | 'ignore';
outputFormat: 'yaml' // 'yaml' | 'json'
};
options.verbose = opts.verbose;
resolver
.resolve(options)
.then((resolved) => {
fs.writeFileSync(outputFileName, yaml.dump(resolved.api), 'utf8');
})
.catch((ex) => {
console.error(ex.message);
process.exit(1);
});
`
or with async/await:
`javascript`
// ..initialize as above, but inside an async function:
try {
const resolved = await resolve(options);
fs.writeFileSync(outputFileName, yaml.dump(resolved.api), 'utf8');
} catch (e) {
// handle error e
}
Below, a _normalized path_ is defined as the simplified
version of a file-path or URL, i.e. with ../ path elements collapsed.../a/b/c/../../d/e
The normalized path for is ../a/d/e.
Local references that begin with #, such as { $ref: "#/path/to/element" },
are left as-is.
There are three types of replacements:
Component Replacements,
Full resource replacements,
and Other embedded objects.
_Component replacements_ are of the form
{ $ref: "uri#/components/section/componentName" } (section may be schemas,parameters, response, or any other item in components).
Component replacements are only done for three-level JSON Pointers; for longer JSON pointers, see #4 below.
If the containing $ref object is at /components/section/componentName0, it does not contain any other keys, andcomponentName0 equals componentName, the entire referenced object is inserted$ref
in place of the original object and the mapping uri#/components/section/componentName ⇒ #/components/section/componentName
is remembered.
This is useful to reuse security schemes in OpenAPI 3.1, which are reference by names instead of a $ref.common.yaml
For example, if contains the definition of the apiKey security schema:
`yaml`
components:
securitySchemes:
apiKey:
type: apiKey
name: API-Key
in: header
description: 'API Key based client identification.'
then other API source files can reference this via
`yaml`
paths:
'/some/path':
get:
security:
apiKey: []
components:
securitySchemes:
apiKey:
$ref: '../common.yaml#/components/securitySchemes/apiKey'
This tool will replace the $ref definition of apiKeycommon.yaml
with the one from :
`yaml`
paths:
'/some/path':
get:
security:
apiKey: []
components:
securitySchemes:
type: apiKey
name: API-Key
in: header
description: 'API Key based client identification.'
x-resolved-from: common.yaml#/components/securitySchemes/apiKey
In a more complicated case (where the $ref contains other properties,$ref
preventing a simple replacement),
the content at the external URI is read and the new named component is
inserted into the target document's components object. The non-local ( ../common.yaml#/components/responses/404 in this case) replaced by{ $ref: "#/components/responses/404" }
a local ref, such as .
For example, if an API has several operations that can return a 404 when a thing
is not found, it may define the reusable component response
with a clean description of the problem:
`yaml
paths:
/thing/{thingId}:
get:
...
responses:
'404':
$ref: '#/components/responses/404Thing'
put:
...
responses:
'404':
$ref: '#/components/responses/404Thing'
patch:
...
responses:
'404':
$ref: '#/components/responses/404Thing'
components:
responses:
'404Thing':
description: Thing not found at /thing/{thingId}.
$ref: 'common.yaml#/components/responses/404'
`
The tool will inline the 404 response from common.yaml as a component,$ref
then replace the remote inside thr 404Thing response with a reference to the local, inlined 404:
`yaml`
components:
responses:
'404':
description: Not found. There is no such resource at the request URL.
content:
application/json:
schema:
$ref: '#/components/schemas/problemResponse'
x-resolved-from: common.yaml#/components/responses/404
404Thing:
description: Thing not found at /thing/{thingId}.
$ref: '#/components/responses/404'
The ApiRefOptions.conflictPolicy determines what to do if the componentName
already exists in the target document:
* it is either renamed with a unique numeric suffix (rename);error
* it is an error and the entire process fails ()ignore
* the conflict is ignored ().
Note: The OpenAPI Specification requires that these paths be relative to the
path in the
servers object, but this tool simply uses relative references
from the source URI.
_Full resource replacements_ are of the form
{ $ref: "uri" } with no # fragment. If not yet seen, the entire external file$ref
is inserted, replacing the object. The location is{ $ref: #/location/of/resolved/resource }
remembered so that any duplicate references to the normalized
path are replaced with a local .$ref
This is _only_ done if the is the _only_ key in the object.
When referencing non-component objects, such as
{ $ref: "components.yaml#/paths/~1health/get" } to include the get operation at/health
the OpenAPI path the operation object in components.yaml.
After embedding an external object from uri, the tool will also rewrite any$ref objects within it, relative to the path that the object was read from.{ $ref: "#/..."}
Any objects are converted to { $ref: "normalized-path#/..."}.
This tool does not yet merge non-$ref content from API files. For example, if$ref
one file has a to an operation in another file, this tooltags
does not pull in API elements from the referenced file, such as the and security` requirements of the referenced operation.