Skip to content
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
370 changes: 370 additions & 0 deletions packages/apidom-ns-openapi-3-1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ InfoElement.refract(objectElement, { plugins: [plugin] }); // => InfoElement({ t

You can define as many plugins as needed to enhance the resulting namespaced ApiDOM structure.
If multiple plugins with the same visitor method are defined, they run in parallel (just like in Babel).
All the plugins available in `@swagger-api/apidom-ns-openapi-3-1` are idempotent and the normalization state is stored in the root `OpenApi3_1Element` in `<storageField>`. `<storageField>` can be customized in the plugin configuration (default: `x-normalized`).

#### Replace Empty Element plugin

Expand Down Expand Up @@ -431,6 +432,375 @@ toValue(openApiElement);
// }
// }
```
#### Normalize Parameter examples plugin

`parameter.examples` and `parameter.example` override `parameter.schema.examples` and `parameter.schema.example` fields. The plugin overrides only the existing `parameter.schema.examples` and `parameter.schema.example`.

- Does not apply to parameters defined under `components`.
- `parameter.examples` has precedence over deprecated `parameter.example`.

```js
import { toValue } from '@swagger-api/apidom-core';
import { OpenApi3_1Element, refractorPluginNormalizeParameterExamples } from '@swagger-api/apidom-ns-openapi-3-1';
import { parse } from '@swagger-api/apidom-parser-adapter-yaml-1-2';

const yamlDefinition = `
openapi: 3.1.0
paths:
/:
get:
parameters:
- in: query
name: idempotent
schema:
type: number
examples: [1]
examples:
example1:
value: 2`

const apiDOM = await parse(yamlDefinition);

//default
const openApiElement = OpenApi3_1Element.refract(apiDOM.result, {
plugins: [refractorPluginNormalizeParameterExamples()],
});
toValue(openApiElement);
// =>
// {
// openapi: '3.1.0',
// paths: {
// '/': {
// get: {
// parameters: [
// {
// in: 'query',
// name: 'idempotent',
// schema: { type: 'number', examples: [ 2 ] },
// examples: { example1: { value: 2 } }
// }
// ]
// }
// }
// },
// 'x-normalized': { 'parameter-examples': [ '/paths/~1/get/parameters/0' ] }
// }

// custom storage field name
const openApiElementWithCustomField = OpenApi3_1Element.refract(apiDOM.result, {
plugins: [refractorPluginNormalizeParameterExamples({ storageField: '$$my-normalized' })],
});
toValue(openApiElementWithCustomField);
// =>
// {
// openapi: '3.1.0',
// paths: {
// '/': {
// get: {
// parameters: [
// {
// in: 'query',
// name: 'idempotent',
// schema: { type: 'number', examples: [2] },
// examples: { example1: { value: 2 } }
// }
// ]
// }
// }
// },
// '$$my-normalized': { 'parameter-examples': [ '/paths/~1/get/parameters/0' ] }
// }
```

#### Normalize Header examples plugin

`Header.examples` and `header.example` override `header.schema.examples` and `header.schema.example` fields. The plugin overrides only the existing `header.schema.examples` and `header.schema.example`.

- Does not apply to headers defined under `components`.
- `header.examples` has precedence over deprecated `header.example`.

```js
import { toValue } from '@swagger-api/apidom-core';
import { OpenApi3_1Element, refractorPluginNormalizeHeaderExamples } from '@swagger-api/apidom-ns-openapi-3-1';
import { parse } from '@swagger-api/apidom-parser-adapter-yaml-1-2';

const yamlDefinition = `
openapi: 3.1.0
paths:
/:
get:
responses:
"200":
headers:
content-type:
schema:
type: number
example: 1
examples:
example1:
value: 2
`
const apiDOM = await parse(yamlDefinition);

// default
const openApiElement = OpenApi3_1Element.refract(apiDOM.result, {
plugins: [refractorPluginNormalizeHeaderExamples()],
});
toValue(openApiElement);
// =>
// {
// openapi: '3.1.0',
// paths: {
// '/': {
// get: {
// responses: {
// '200': {
// headers: {
// 'content-type': {
// schema: { type: 'number', example: 2 },
// examples: { example1: { value: 2 } }
// }
// }
// }
// }
// }
// }
// },
// 'x-normalized': {
// 'header-examples': [ '/paths/~1/get/responses/200/headers/content-type' ]
// }
// }

// custom storage field name

const openApiElementWithCustomField = OpenApi3_1Element.refract(apiDOM.result, {
plugins: [refractorPluginNormalizeHeaderExamples({ storageField: '$$normalized' })],
});
toValue(openApiElementWithCustomField);
// =>
// {
// openapi: '3.1.0',
// paths: {
// '/': {
// get: {
// responses: {
// '200': {
// headers: {
// 'content-type': {
// schema: { type: 'number', example: 2 },
// examples: { example1: { value: 2 } }
// }
// }
// }
// }
// }
// }
// },
// '$$normalized': {
// 'header-examples': [ '/paths/~1/get/responses/200/headers/content-type' ]
// }
// }
```

#### Normalize Discriminator mapping plugin
This plugin normalizes the `discriminator.mapping` field in a Schema Object by:

- Converting mapping values into inline Schema Objects when possible.
- Adding missing mapping entries based on the schema's `oneOf`, `anyOf`, or a prepared `allOf` mapping. The `allOf` mapping
is created based on the schemas defined in `components.schemas` during dereferencing.

The `discriminator.mapping` field is not modified by the plugin.

This plugin is intended to run on dereferenced OpenAPI 3.1 documents. During dereferencing Schema Objects are annotated with meta properties and the `allOf` mapping is created for Schema Objects defined in `components.schemas`.

```json

// fixture-example.json
{
"openapi": "3.1.0",
"components": {
"schemas": {
"MyResponse": {
"type": "object",
"oneOf": [
{
"$ref": "#/components/schemas/Cat"
},
{
"$ref": "#/components/schemas/Dog"
}
],
"discriminator": {
"propertyName": "petType"
}
},
"Pet": {
"type": "object",
"properties": {
"petType": {
"type": "string"
}
}
},
"Cat": {
"allOf": [
{
"$ref": "#/components/schemas/Pet"
},
{
"type": "object",
"properties": {
"meows": {
"type": "boolean"
}
}
}
]
},
"Dog": {
"allOf": [
{
"$ref": "#/components/schemas/Pet"
},
{
"type": "object",
"properties": {
"barks": {
"type": "boolean"
}
}
}
]
}
}
}
}
```

```js
import { toValue, dispatchRefractorPlugins } from '@swagger-api/apidom-core';
import { dereference } from '@swagger-api/apidom-reference';
import FileResolver from '@swagger-api/apidom-reference/resolve/resolvers/file';
import {
createToolbox,
refractorPluginNormalizeDiscriminatorMapping,
keyMap,
getNodeType,
mediaTypes,
} from '@swagger-api/apidom-ns-openapi-3-1';
const uri = 'path/to/fixture-example.json'; // the arbitrary file name shown above

// 1) dereference the document to annotate schemas with required metadata
const dereferenced = await dereference(uri, {
parse: { mediaType: mediaTypes.latest('json') },
resolve: {
baseURI: uri,
resolvers: [ new FileResolver({ fileAllowList: [/\.json$/] }) ],
},
dereference: { strategyOpts: { 'openapi-3-1': { dereferenceDiscriminatorMapping: true } } },
});

// 2) dispatch the plugin and pass the same baseURI
const normalized = dispatchRefractorPlugins(
dereferenced.result,
[refractorPluginNormalizeDiscriminatorMapping({ baseURI: uri })],
{ toolboxCreator: createToolbox, visitorOptions: { keyMap, nodeTypeGetter: getNodeType } }
);
toValue(normalized)

// =>
// {
// openapi: '3.1.0',
// components: {
// schemas: {
// MyResponse: {
// type: 'object',
// oneOf: [
// {
// allOf: [
// {
// type: 'object',
// properties: { petType: { type: 'string' } }
// },
// {
// type: 'object',
// properties: { meows: { type: 'boolean' } }
// }
// ]
// },
// {
// allOf: [
// {
// type: 'object',
// properties: { petType: { type: 'string' } }
// },
// {
// type: 'object',
// properties: { barks: { type: 'boolean' } }
// }
// ]
// }
// ],
// discriminator: {
// propertyName: 'petType',
// 'x-normalized-mapping': {
// Cat: {
// allOf: [
// {
// type: 'object',
// properties: { petType: { type: 'string' } }
// },
// {
// type: 'object',
// properties: { meows: { type: 'boolean' } }
// }
// ]
// },
// Dog: {
// allOf: [
// {
// type: 'object',
// properties: { petType: { type: 'string' } }
// },
// {
// type: 'object',
// properties: { barks: { type: 'boolean' } }
// }
// ]
// }
// }
// }
// },
// Pet: { type: 'object', properties: { petType: { type: 'string' } } },
// Cat: {
// allOf: [
// {
// type: 'object',
// properties: { petType: { type: 'string' } }
// },
// {
// type: 'object',
// properties: { meows: { type: 'boolean' } }
// }
// ]
// },
// Dog: {
// allOf: [
// {
// type: 'object',
// properties: { petType: { type: 'string' } }
// },
// {
// type: 'object',
// properties: { barks: { type: 'boolean' } }
// }
// ]
// }
// }
// },
// 'x-normalized': { 'discriminator-mapping': [ '/components/schemas/MyResponse' ] }
// }
```

## Implementation progress

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('refractor', function () {
example: 1
examples:
example1:
value: 2
value: 2
`;
const apiDOM = await parse(yamlDefinition);
const openApiElement = OpenApi3_1Element.refract(apiDOM.result, {
Expand Down