strong-tie/inbound-calls
0
1import type Ajv from "ajv"2import type {3 Plugin,4 CodeKeywordDefinition,5 KeywordErrorDefinition,6 Code,7 Name,8 ErrorObject,9} from "ajv"10import type {AddedFormat} from "ajv/dist/types"11import type {Rule} from "ajv/dist/compile/rules"12import {KeywordCxt} from "ajv"13import {_, str, or, getProperty, operators} from "ajv/dist/compile/codegen"14 15type Kwd = "formatMaximum" | "formatMinimum" | "formatExclusiveMaximum" | "formatExclusiveMinimum"16 17type Comparison = "<=" | ">=" | "<" | ">"18 19const ops = operators20 21const KWDs: {[K in Kwd]: {okStr: Comparison; ok: Code; fail: Code}} = {22 formatMaximum: {okStr: "<=", ok: ops.LTE, fail: ops.GT},23 formatMinimum: {okStr: ">=", ok: ops.GTE, fail: ops.LT},24 formatExclusiveMaximum: {okStr: "<", ok: ops.LT, fail: ops.GTE},25 formatExclusiveMinimum: {okStr: ">", ok: ops.GT, fail: ops.LTE},26}27 28export type LimitFormatError = ErrorObject<Kwd, {limit: string; comparison: Comparison}>29 30const error: KeywordErrorDefinition = {31 message: ({keyword, schemaCode}) => str`should be ${KWDs[keyword as Kwd].okStr} ${schemaCode}`,32 params: ({keyword, schemaCode}) =>33 _`{comparison: ${KWDs[keyword as Kwd].okStr}, limit: ${schemaCode}}`,34}35 36export const formatLimitDefinition: CodeKeywordDefinition = {37 keyword: Object.keys(KWDs),38 type: "string",39 schemaType: "string",40 $data: true,41 error,42 code(cxt) {43 const {gen, data, schemaCode, keyword, it} = cxt44 const {opts, self} = it45 if (!opts.validateFormats) return46 47 const fCxt = new KeywordCxt(it, (self.RULES.all.format as Rule).definition, "format")48 if (fCxt.$data) validate$DataFormat()49 else validateFormat()50 51 function validate$DataFormat(): void {52 const fmts = gen.scopeValue("formats", {53 ref: self.formats,54 code: opts.code.formats,55 })56 const fmt = gen.const("fmt", _`${fmts}[${fCxt.schemaCode}]`)57 cxt.fail$data(58 or(59 _`typeof ${fmt} != "object"`,60 _`${fmt} instanceof RegExp`,61 _`typeof ${fmt}.compare != "function"`,62 compareCode(fmt)63 )64 )65 }66 67 function validateFormat(): void {68 const format = fCxt.schema as string69 const fmtDef: AddedFormat | undefined = self.formats[format]70 if (!fmtDef || fmtDef === true) return71 if (72 typeof fmtDef != "object" ||73 fmtDef instanceof RegExp ||74 typeof fmtDef.compare != "function"75 ) {76 throw new Error(`"${keyword}": format "${format}" does not define "compare" function`)77 }78 const fmt = gen.scopeValue("formats", {79 key: format,80 ref: fmtDef,81 code: opts.code.formats ? _`${opts.code.formats}${getProperty(format)}` : undefined,82 })83 84 cxt.fail$data(compareCode(fmt))85 }86 87 function compareCode(fmt: Name): Code {88 return _`${fmt}.compare(${data}, ${schemaCode}) ${KWDs[keyword as Kwd].fail} 0`89 }90 },91 dependencies: ["format"],92}93 94const formatLimitPlugin: Plugin<undefined> = (ajv: Ajv): Ajv => {95 ajv.addKeyword(formatLimitDefinition)96 return ajv97}98 99export default formatLimitPlugin100 