1 | import type {CodeKeywordDefinition, AnySchemaObject, KeywordCxt, Code, Name} from "ajv"
|
---|
2 | import {_, stringify, getProperty} from "ajv/dist/compile/codegen"
|
---|
3 |
|
---|
4 | type TransformName =
|
---|
5 | | "trimStart"
|
---|
6 | | "trimEnd"
|
---|
7 | | "trimLeft"
|
---|
8 | | "trimRight"
|
---|
9 | | "trim"
|
---|
10 | | "toLowerCase"
|
---|
11 | | "toUpperCase"
|
---|
12 | | "toEnumCase"
|
---|
13 |
|
---|
14 | interface TransformConfig {
|
---|
15 | hash: Record<string, string | undefined>
|
---|
16 | }
|
---|
17 |
|
---|
18 | type Transform = (s: string, cfg?: TransformConfig) => string
|
---|
19 |
|
---|
20 | const transform: {[key in TransformName]: Transform} = {
|
---|
21 | trimStart: (s) => s.trimStart(),
|
---|
22 | trimEnd: (s) => s.trimEnd(),
|
---|
23 | trimLeft: (s) => s.trimStart(),
|
---|
24 | trimRight: (s) => s.trimEnd(),
|
---|
25 | trim: (s) => s.trim(),
|
---|
26 | toLowerCase: (s) => s.toLowerCase(),
|
---|
27 | toUpperCase: (s) => s.toUpperCase(),
|
---|
28 | toEnumCase: (s, cfg) => cfg?.hash[configKey(s)] || s,
|
---|
29 | }
|
---|
30 |
|
---|
31 | const getDef: (() => CodeKeywordDefinition) & {
|
---|
32 | transform: typeof transform
|
---|
33 | } = Object.assign(_getDef, {transform})
|
---|
34 |
|
---|
35 | function _getDef(): CodeKeywordDefinition {
|
---|
36 | return {
|
---|
37 | keyword: "transform",
|
---|
38 | schemaType: "array",
|
---|
39 | before: "enum",
|
---|
40 | code(cxt: KeywordCxt) {
|
---|
41 | const {gen, data, schema, parentSchema, it} = cxt
|
---|
42 | const {parentData, parentDataProperty} = it
|
---|
43 | const tNames: string[] = schema
|
---|
44 | if (!tNames.length) return
|
---|
45 | let cfg: Name | undefined
|
---|
46 | if (tNames.includes("toEnumCase")) {
|
---|
47 | const config = getEnumCaseCfg(parentSchema)
|
---|
48 | cfg = gen.scopeValue("obj", {ref: config, code: stringify(config)})
|
---|
49 | }
|
---|
50 | gen.if(_`typeof ${data} == "string" && ${parentData} !== undefined`, () => {
|
---|
51 | gen.assign(data, transformExpr(tNames.slice()))
|
---|
52 | gen.assign(_`${parentData}[${parentDataProperty}]`, data)
|
---|
53 | })
|
---|
54 |
|
---|
55 | function transformExpr(ts: string[]): Code {
|
---|
56 | if (!ts.length) return data
|
---|
57 | const t = ts.pop() as string
|
---|
58 | if (!(t in transform)) throw new Error(`transform: unknown transformation ${t}`)
|
---|
59 | const func = gen.scopeValue("func", {
|
---|
60 | ref: transform[t as TransformName],
|
---|
61 | code: _`require("ajv-keywords/dist/definitions/transform").transform${getProperty(t)}`,
|
---|
62 | })
|
---|
63 | const arg = transformExpr(ts)
|
---|
64 | return cfg && t === "toEnumCase" ? _`${func}(${arg}, ${cfg})` : _`${func}(${arg})`
|
---|
65 | }
|
---|
66 | },
|
---|
67 | metaSchema: {
|
---|
68 | type: "array",
|
---|
69 | items: {type: "string", enum: Object.keys(transform)},
|
---|
70 | },
|
---|
71 | }
|
---|
72 | }
|
---|
73 |
|
---|
74 | function getEnumCaseCfg(parentSchema: AnySchemaObject): TransformConfig {
|
---|
75 | // build hash table to enum values
|
---|
76 | const cfg: TransformConfig = {hash: {}}
|
---|
77 |
|
---|
78 | // requires `enum` in the same schema as transform
|
---|
79 | if (!parentSchema.enum) throw new Error('transform: "toEnumCase" requires "enum"')
|
---|
80 | for (const v of parentSchema.enum) {
|
---|
81 | if (typeof v !== "string") continue
|
---|
82 | const k = configKey(v)
|
---|
83 | // requires all `enum` values have unique keys
|
---|
84 | if (cfg.hash[k]) {
|
---|
85 | throw new Error('transform: "toEnumCase" requires all lowercased "enum" values to be unique')
|
---|
86 | }
|
---|
87 | cfg.hash[k] = v
|
---|
88 | }
|
---|
89 |
|
---|
90 | return cfg
|
---|
91 | }
|
---|
92 |
|
---|
93 | function configKey(s: string): string {
|
---|
94 | return s.toLowerCase()
|
---|
95 | }
|
---|
96 |
|
---|
97 | export default getDef
|
---|
98 | module.exports = getDef
|
---|