114 lines
3.7 KiB
TypeScript
114 lines
3.7 KiB
TypeScript
import { Validator } from 'jsonschema'
|
|
import { getDefaults, isObject, merge } from './defaults'
|
|
import { TransformedValidatorResult, ValidatorOptions } from './types'
|
|
import { transformInstance } from './transform'
|
|
import { compile, JSONSchema } from 'json-schema-to-typescript'
|
|
import * as fs from 'fs/promises'
|
|
import { join } from 'path'
|
|
import { NetworkError } from '../utils/errors'
|
|
import getConfig from "next/config";
|
|
|
|
const {publicRuntimeConfig: {TENANT_TYPE}} = getConfig()
|
|
|
|
const validatorStorage = new Validator()
|
|
|
|
export async function addSchema(schemas: JSONSchema[]) {
|
|
if (TENANT_TYPE === 'local') {
|
|
const fileName = join('./src/types/ApiSchemas.ts')
|
|
const fileNameFrontend = join('./src/types/ApiSchemasFrontend.ts')
|
|
const style = await fs.readFile('./.prettierrc.json', 'utf8').then(JSON.parse)
|
|
let data = ''
|
|
let dataFrontend = ''
|
|
for (const schema of schemas) {
|
|
if (!schema.id) throw new SyntaxError('Id of schema has to be defined!')
|
|
validatorStorage.addSchema(schema)
|
|
// compile schema to interface
|
|
let entity = await compile(schema, schema.id, {
|
|
style,
|
|
bannerComment: data === '' ? undefined : '\n\n\n'
|
|
})
|
|
entity = entity.replace(/^( {2})+\[k: string]: unknown\n/gm, '')
|
|
dataFrontend += entity
|
|
// remove ? from defaulted properties
|
|
const defaults = getDefaults(schema)
|
|
if (isObject(defaults)) {
|
|
iterObject(defaults, (key, depth) => {
|
|
entity = entity.replace(
|
|
RegExp(` {${2 * depth + 2}}${key}\\?`, 'gm'),
|
|
' '.repeat(2 * depth + 2) + key
|
|
)
|
|
})
|
|
}
|
|
// add interface to others
|
|
data += entity
|
|
}
|
|
const current = await fs.readFile(fileName, 'utf8').catch(() => undefined)
|
|
if (current !== data) {
|
|
await fs.writeFile(fileName, data)
|
|
await fs.writeFile(fileNameFrontend, dataFrontend)
|
|
}
|
|
} else {
|
|
for (const schema of schemas) {
|
|
if (!schema.id) throw new SyntaxError('Id of schema has to be defined!')
|
|
validatorStorage.addSchema(schema)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function validate<T = Record<string, unknown>>(
|
|
instance: Record<string, unknown>,
|
|
schemaName: string,
|
|
options?: ValidatorOptions
|
|
): TransformedValidatorResult<T> {
|
|
const schema = validatorStorage.schemas[schemaName] as JSONSchema | undefined
|
|
if (!schema) throw new SyntaxError(`Schema '${schemaName}' not implemented!`)
|
|
const defaulted =
|
|
options?.applyDefaults !== false ? mergeDefault(getDefaults(schema), instance) : instance
|
|
const transformed =
|
|
options?.transform !== false ? transformInstance(defaulted, schema) : defaulted
|
|
const result = validatorStorage.validate(
|
|
transformed,
|
|
schema,
|
|
options
|
|
) as TransformedValidatorResult<T>
|
|
if (result.errors.length) {
|
|
throw new NetworkError(
|
|
{
|
|
message: 'Validation error',
|
|
validation: result.errors
|
|
},
|
|
undefined,
|
|
400
|
|
)
|
|
}
|
|
result.transformed = transformed as T
|
|
return result
|
|
}
|
|
|
|
export function applyDefaults<T>(schemaName: string, instance: Partial<T>): T {
|
|
const schema = validatorStorage.schemas[schemaName] as JSONSchema | undefined
|
|
if (!schema) throw new SyntaxError(`Schema '${schemaName}' not implemented!`)
|
|
return mergeDefault(getDefaults(schema), instance) as T
|
|
}
|
|
|
|
function mergeDefault(target: unknown, source: unknown) {
|
|
return isObject(target) && isObject(source) ? merge(target, source) : source
|
|
}
|
|
|
|
function iterObject(
|
|
object: Record<string, unknown>,
|
|
cb: (key: string, depth: number, value: unknown) => void
|
|
) {
|
|
function iter(o: Record<string, unknown>, d: number) {
|
|
Object.keys(o).forEach(k => {
|
|
const next = o[k]
|
|
if (isObject(next)) {
|
|
iter(next as Record<string, unknown>, d + 1)
|
|
} else {
|
|
cb(k, d, next)
|
|
}
|
|
})
|
|
}
|
|
iter(object, 0)
|
|
}
|