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>( instance: Record, schemaName: string, options?: ValidatorOptions ): TransformedValidatorResult { 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 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(schemaName: string, instance: Partial): 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, cb: (key: string, depth: number, value: unknown) => void ) { function iter(o: Record, d: number) { Object.keys(o).forEach(k => { const next = o[k] if (isObject(next)) { iter(next as Record, d + 1) } else { cb(k, d, next) } }) } iter(object, 0) }