import type {CodeKeywordDefinition, AnySchemaObject, KeywordCxt, Code, Name} from "ajv" import {_, stringify, getProperty} from "ajv/dist/compile/codegen" type TransformName = | "trimStart" | "trimEnd" | "trimLeft" | "trimRight" | "trim" | "toLowerCase" | "toUpperCase" | "toEnumCase" interface TransformConfig { hash: Record } type Transform = (s: string, cfg?: TransformConfig) => string const transform: {[key in TransformName]: Transform} = { trimStart: (s) => s.trimStart(), trimEnd: (s) => s.trimEnd(), trimLeft: (s) => s.trimStart(), trimRight: (s) => s.trimEnd(), trim: (s) => s.trim(), toLowerCase: (s) => s.toLowerCase(), toUpperCase: (s) => s.toUpperCase(), toEnumCase: (s, cfg) => cfg?.hash[configKey(s)] || s, } const getDef: (() => CodeKeywordDefinition) & { transform: typeof transform } = Object.assign(_getDef, {transform}) function _getDef(): CodeKeywordDefinition { return { keyword: "transform", schemaType: "array", before: "enum", code(cxt: KeywordCxt) { const {gen, data, schema, parentSchema, it} = cxt const {parentData, parentDataProperty} = it const tNames: string[] = schema if (!tNames.length) return let cfg: Name | undefined if (tNames.includes("toEnumCase")) { const config = getEnumCaseCfg(parentSchema) cfg = gen.scopeValue("obj", {ref: config, code: stringify(config)}) } gen.if(_`typeof ${data} == "string" && ${parentData} !== undefined`, () => { gen.assign(data, transformExpr(tNames.slice())) gen.assign(_`${parentData}[${parentDataProperty}]`, data) }) function transformExpr(ts: string[]): Code { if (!ts.length) return data const t = ts.pop() as string if (!(t in transform)) throw new Error(`transform: unknown transformation ${t}`) const func = gen.scopeValue("func", { ref: transform[t as TransformName], code: _`require("ajv-keywords/dist/definitions/transform").transform${getProperty(t)}`, }) const arg = transformExpr(ts) return cfg && t === "toEnumCase" ? _`${func}(${arg}, ${cfg})` : _`${func}(${arg})` } }, metaSchema: { type: "array", items: {type: "string", enum: Object.keys(transform)}, }, } } function getEnumCaseCfg(parentSchema: AnySchemaObject): TransformConfig { // build hash table to enum values const cfg: TransformConfig = {hash: {}} // requires `enum` in the same schema as transform if (!parentSchema.enum) throw new Error('transform: "toEnumCase" requires "enum"') for (const v of parentSchema.enum) { if (typeof v !== "string") continue const k = configKey(v) // requires all `enum` values have unique keys if (cfg.hash[k]) { throw new Error('transform: "toEnumCase" requires all lowercased "enum" values to be unique') } cfg.hash[k] = v } return cfg } function configKey(s: string): string { return s.toLowerCase() } export default getDef module.exports = getDef