yaml-to-json-schema
Version:
Generate json schema from yaml (swagger, openapi, asyncapi)
151 lines (117 loc) • 3.1 kB
text/typescript
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)
}