UNPKG

yaml-to-json-schema

Version:

Generate json schema from yaml (swagger, openapi, asyncapi)

151 lines (117 loc) 3.1 kB
import { resolve, dirname, normalize, relative, basename } from 'path' import { promises as fs } from 'fs' import yaml from 'js-yaml' import { ParseOptions, JsonSchema, File, Key, Keys, Doc, Src, Ref, Prop, Definitions, Properties, Schemas } from './types' let basedir: string const keys: Keys<Src, Key> = {} const setKey = (key: Key, ref: string, src: Src): void => { if (!ref) { return } const file = normalize(ref) keys[file] = key } const getKey = (ref: string, src: Src): string => { if (ref.startsWith('#')) { return basename(ref) } const file: File = resolve(dirname(src), ref) const path = relative(basedir, file) const key: Key = keys[path] if (!key) console.error('WARNING:', key, path) return key } const fixOf = (refs: Prop[], src: Src) => { refs.forEach((item: Prop) => { fixProperty(item, src) }) } const fixAddons = (doc: Prop, src: Src): void => { if (doc.allOf) { fixOf(doc.allOf, src) } else if (doc.oneOf) { fixOf(doc.oneOf, src) } else if (doc.anyOf) { fixOf(doc.anyOf, src) } else if (doc.not) { fixProperty(doc.not, src) } } const fixProperty = (prop: Prop, src: Src): Prop => { if (prop.$ref) { prop.$ref = '#definitions/' + getKey(prop.$ref, src) } else if (prop.type === 'object') { fixRef(prop, src) } else if (prop.type === 'array') { fixProperty(prop.items, src) } fixAddons(prop, src) return prop } const fixRef = (doc: Prop, src: Src): Prop => { fixAddons(doc, src) for(const key in doc.properties) { fixProperty(doc.properties[key], src) } return doc } const loadYamlFile = async (file: File): Promise<any> => { const content = await fs.readFile(file, 'utf8') return yaml.load(content) } const loadPath = async (ref: Ref, src: Src): Promise<Prop> => { const dir: string = dirname(src) const file: string = resolve(dir, ref) const doc: Doc = await loadYamlFile(file) if (!doc) { throw new Error('Empty doc') } if (typeof doc === 'string' || typeof doc === 'number') { throw new Error('Unsupported doc') } // @ts-ignore return fixRef(doc, file) } const loadSchema = async (doc: Prop, src: Src): Promise<Prop> => { if (doc.$ref) { return loadPath(doc.$ref, src) } return fixRef(doc, src) } const prepare = async (doc: Doc, src: Src): Promise<JsonSchema> => { const definitions: Definitions = {} const properties: Properties = {} const { schemas }: { schemas: Schemas } = doc.components ?? {} basedir = dirname(src) for(const key in schemas) { setKey(key, schemas[key].$ref, src) } for(const key in schemas) { definitions[key] = await loadSchema(schemas[key], src) // @ts-ignore // @TODO fix this issue. Error TS2740: Type '{ $ref: string; }' properties[key] = { $ref: '#definitions/' + key } } return { type: 'object', definitions, properties } } export async function parse(options: ParseOptions): Promise<JsonSchema> { const { input } = options const doc: Doc = await loadYamlFile(input) return prepare(doc, input) }