Files
node-factorio-recipes/src/validation/index.ts
2022-08-17 23:27:19 +02:00

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)
}