Implemented server routes
This commit is contained in:
113
src/validation/index.ts
Normal file
113
src/validation/index.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user