-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(converter): add complete code infrastructure
- Loading branch information
Showing
36 changed files
with
518 additions
and
128 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { ApiDOMError } from '@swagger-api/apidom-error'; | ||
|
||
class ConvertError extends ApiDOMError {} | ||
|
||
export default ConvertError; |
5 changes: 5 additions & 0 deletions
5
packages/apidom-converter/src/errors/UnmatchedConvertStrategyError.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import ConvertError from './ConvertError'; | ||
|
||
class UnmatchedConvertStrategyError extends ConvertError {} | ||
|
||
export default UnmatchedConvertStrategyError; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,36 @@ | ||
import { parse } from '@swagger-api/apidom-parser-adapter-yaml-1-2'; | ||
|
||
import getOpenAPIRefractor from './get-refractor'; | ||
import getPluginsBySpec from './plugins/get-plugins-by-spec'; | ||
|
||
const convert = async (yaml: string, from: string) => { | ||
const apiDOM = await parse(yaml); | ||
const refractor = getOpenAPIRefractor(from); | ||
const openApiElement = refractor.refract(apiDOM.result, { | ||
plugins: [...getPluginsBySpec(from)], | ||
}) as unknown as typeof refractor; | ||
return openApiElement; | ||
import { ParseResultElement } from '@swagger-api/apidom-core'; | ||
import { mergeOptions, bundle, File } from '@swagger-api/apidom-reference'; | ||
|
||
import defaultOptions, { ConverterOptions } from './options'; | ||
import ConvertError from './errors/ConvertError'; | ||
import UnmatchedConvertStrategyError from './errors/UnmatchedConvertStrategyError'; | ||
|
||
export { ConvertError, UnmatchedConvertStrategyError }; | ||
|
||
/** | ||
* `convertApiDOM` already assumes that the ApiDOM is bundled. | ||
*/ | ||
export const convertApiDOM = async (element: ParseResultElement, options = {}) => { | ||
const mergedOptions = mergeOptions(defaultOptions, options || {}) as ConverterOptions; | ||
const file = File({ | ||
uri: mergedOptions.resolve.baseURI, | ||
parseResult: element, | ||
mediaType: mergedOptions.convert.sourceMediaType || mergedOptions.parse.mediaType, | ||
}); | ||
const strategy = mergedOptions.convert.strategies.find((s) => s.canConvert(file, mergedOptions)); | ||
|
||
if (typeof strategy === 'undefined') { | ||
throw new UnmatchedConvertStrategyError(file.uri); | ||
} | ||
|
||
return strategy.convert(file, mergedOptions); | ||
}; | ||
|
||
const convert = async (uri: string, options = {}) => { | ||
const mergedOptions = mergeOptions(defaultOptions, options || {}) as ConverterOptions; | ||
const parseResult = await bundle(uri, mergedOptions); | ||
|
||
return convertApiDOM(parseResult, mergedOptions); | ||
}; | ||
|
||
export default convert; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { options as referenceOptions } from '@swagger-api/apidom-reference'; | ||
|
||
import ConvertStrategy from '../strategies/ConvertStrategy'; | ||
import OpenAPI31ToOpenAPI30ConvertStrategy from '../strategies/openapi-3-1-to-openapi-3-0-3'; | ||
|
||
type ReferenceOptions = typeof referenceOptions; | ||
|
||
interface ConvertOptions { | ||
strategies: Array<ConvertStrategy>; | ||
sourceMediaType: string; | ||
targetMediaType: string; | ||
} | ||
|
||
export interface ConverterOptions extends ReferenceOptions { | ||
readonly convert: ConvertOptions; | ||
} | ||
|
||
const defaultOptions: ConverterOptions = { | ||
...referenceOptions, | ||
convert: { | ||
/** | ||
* Determines strategies how ApiDOM is bundled. | ||
* Strategy is determined by media type or by inspecting ApiDOM to be bundled. | ||
* | ||
* You can add additional bundle strategies of your own, replace an existing one with | ||
* your own implementation, or remove any bundle strategy by removing it from the list. | ||
*/ | ||
strategies: [new OpenAPI31ToOpenAPI30ConvertStrategy()], | ||
/** | ||
* Media type of source API definition. | ||
*/ | ||
sourceMediaType: 'text/plain', | ||
/** | ||
* Media type of target API definition. | ||
*/ | ||
targetMediaType: 'text/plain', | ||
}, | ||
}; | ||
|
||
export default defaultOptions; |
16 changes: 0 additions & 16 deletions
16
packages/apidom-converter/src/plugins/get-plugins-by-spec.ts
This file was deleted.
Oops, something went wrong.
13 changes: 0 additions & 13 deletions
13
packages/apidom-converter/src/plugins/openapi3_1/spec-downgrade.ts
This file was deleted.
Oops, something went wrong.
26 changes: 26 additions & 0 deletions
26
packages/apidom-converter/src/strategies/ConvertStrategy.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import stampit from 'stampit'; | ||
import { ParseResultElement } from '@swagger-api/apidom-core'; | ||
import { File } from '@swagger-api/apidom-reference'; | ||
|
||
import type { ConverterOptions } from '../options'; | ||
|
||
type ExtractGenericType<T> = T extends stampit.Stamp<infer U> ? U : never; | ||
export type IFile = ExtractGenericType<typeof File>; | ||
|
||
export interface ConvertStrategyOptions { | ||
readonly name: string; | ||
} | ||
|
||
abstract class ConvertStrategy { | ||
public readonly name: string; | ||
|
||
protected constructor({ name }: ConvertStrategyOptions) { | ||
this.name = name; | ||
} | ||
|
||
abstract canConvert(file: IFile, options: ConverterOptions): boolean; | ||
|
||
abstract convert(file: IFile, options: ConverterOptions): Promise<ParseResultElement>; | ||
} | ||
|
||
export default ConvertStrategy; |
76 changes: 76 additions & 0 deletions
76
packages/apidom-converter/src/strategies/openapi-3-1-to-openapi-3-0-3/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { | ||
OpenApi3_0Element, | ||
mediaTypes as openAPI3_0MediaTypes, | ||
} from '@swagger-api/apidom-ns-openapi-3-0'; | ||
import { | ||
isOpenApi3_1Element, | ||
mediaTypes as openAPI3_1MediaTypes, | ||
createToolbox, | ||
keyMap, | ||
getNodeType, | ||
} from '@swagger-api/apidom-ns-openapi-3-1'; | ||
import { | ||
ParseResultElement, | ||
dispatchRefractorPlugins, | ||
AnnotationElement, | ||
cloneShallow, | ||
} from '@swagger-api/apidom-core'; | ||
|
||
import ConvertStrategy, { IFile } from '../ConvertStrategy'; | ||
import openAPIVersionRefractorPlugin from './refractor-plugins/openapi-version'; | ||
import webhooksRefractorPlugin from './refractor-plugins/webhooks'; | ||
import type { ConverterOptions } from '../../options'; | ||
|
||
// eslint-disable-next-line @typescript-eslint/naming-convention | ||
const openAPI3_0_3MediaTypes = [ | ||
openAPI3_0MediaTypes.findBy('3.0.3', 'generic'), | ||
openAPI3_0MediaTypes.findBy('3.0.3', 'json'), | ||
openAPI3_0MediaTypes.findBy('3.0.3', 'yaml'), | ||
]; | ||
|
||
/* eslint-disable class-methods-use-this */ | ||
class OpenAPI31ToOpenAPI30ConvertStrategy extends ConvertStrategy { | ||
constructor() { | ||
super({ name: 'openapi-3-1-to-openapi-3-0-3' }); | ||
} | ||
|
||
canConvert(file: IFile, options: ConverterOptions): boolean { | ||
let hasRecognizedSourceMediaType = false; | ||
const hasRecognizedTargetMediaType = openAPI3_0_3MediaTypes.includes( | ||
options.convert.targetMediaType, | ||
); | ||
|
||
// source detection | ||
if (openAPI3_1MediaTypes.includes(options.convert.sourceMediaType)) { | ||
hasRecognizedSourceMediaType = true; | ||
} else if (file.mediaType !== 'text/plain') { | ||
hasRecognizedSourceMediaType = openAPI3_1MediaTypes.includes(file.mediaType); | ||
} else if (isOpenApi3_1Element(file.parseResult?.result)) { | ||
hasRecognizedSourceMediaType = true; | ||
} | ||
|
||
return hasRecognizedSourceMediaType && hasRecognizedTargetMediaType; | ||
} | ||
|
||
async convert(file: IFile): Promise<ParseResultElement> { | ||
const parseResultElement = file.parseResult; | ||
const annotations: AnnotationElement[] = []; | ||
const converted = dispatchRefractorPlugins( | ||
parseResultElement, | ||
[openAPIVersionRefractorPlugin(), webhooksRefractorPlugin({ annotations })], | ||
{ | ||
toolboxCreator: createToolbox, | ||
visitorOptions: { keyMap, nodeTypeGetter: getNodeType }, | ||
}, | ||
); | ||
|
||
const annotated = cloneShallow(converted); | ||
annotations.forEach((a) => annotated.push(a)); | ||
annotated.replaceResult(OpenApi3_0Element.refract(converted.api)); | ||
|
||
return annotated; | ||
} | ||
} | ||
/* eslint-enable class-methods-use-this */ | ||
|
||
export default OpenAPI31ToOpenAPI30ConvertStrategy; |
11 changes: 11 additions & 0 deletions
11
...onverter/src/strategies/openapi-3-1-to-openapi-3-0-3/refractor-plugins/openapi-version.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { OpenapiElement as Openapi30Element } from '@swagger-api/apidom-ns-openapi-3-0'; | ||
|
||
const openAPIVersionRefractorPlugin = () => () => ({ | ||
visitor: { | ||
OpenapiElement() { | ||
return new Openapi30Element('3.0.3'); | ||
}, | ||
}, | ||
}); | ||
|
||
export default openAPIVersionRefractorPlugin; |
30 changes: 30 additions & 0 deletions
30
...pidom-converter/src/strategies/openapi-3-1-to-openapi-3-0-3/refractor-plugins/webhooks.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { OpenApi3_1Element } from '@swagger-api/apidom-ns-openapi-3-1'; | ||
import { AnnotationElement, cloneShallow } from '@swagger-api/apidom-core'; | ||
|
||
type WebhooksRefractorPluginOptions = { | ||
annotations: AnnotationElement[]; | ||
}; | ||
|
||
const webhooksRefractorPlugin = | ||
({ annotations }: WebhooksRefractorPluginOptions) => | ||
() => ({ | ||
visitor: { | ||
OpenApi3_1Element(element: OpenApi3_1Element) { | ||
if (!element.hasKey('webhooks')) return undefined; | ||
|
||
const copy = cloneShallow(element); | ||
const annotation = new AnnotationElement( | ||
'Webhooks are not supported in OpenAPI 3.0.3. They will be removed from the converted document.', | ||
{ classes: ['warning'] }, | ||
{ code: 'webhooks' }, | ||
); | ||
|
||
annotations.push(annotation); | ||
copy.remove('webhooks'); | ||
|
||
return copy; | ||
}, | ||
}, | ||
}); | ||
|
||
export default webhooksRefractorPlugin; |
13 changes: 13 additions & 0 deletions
13
packages/apidom-converter/test/__snapshots__/index.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`apidom-converter convert given URI should convert 1`] = ` | ||
{ | ||
"openapi": "3.0.3" | ||
} | ||
`; | ||
|
||
exports[`apidom-converter convertApiDOM given ApiDOM data should convert 1`] = ` | ||
{ | ||
"openapi": "3.0.3" | ||
} | ||
`; |
Oops, something went wrong.