dynamicDefaults.ts 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import type {FuncKeywordDefinition, SchemaCxt} from "ajv"
  2. const sequences: Record<string, number | undefined> = {}
  3. export type DynamicDefaultFunc = (args?: Record<string, any>) => () => any
  4. const DEFAULTS: Record<string, DynamicDefaultFunc | undefined> = {
  5. timestamp: () => () => Date.now(),
  6. datetime: () => () => new Date().toISOString(),
  7. date: () => () => new Date().toISOString().slice(0, 10),
  8. time: () => () => new Date().toISOString().slice(11),
  9. random: () => () => Math.random(),
  10. randomint: (args?: {max?: number}) => {
  11. const max = args?.max ?? 2
  12. return () => Math.floor(Math.random() * max)
  13. },
  14. seq: (args?: {name?: string}) => {
  15. const name = args?.name ?? ""
  16. sequences[name] ||= 0
  17. return () => (sequences[name] as number)++
  18. },
  19. }
  20. interface PropertyDefaultSchema {
  21. func: string
  22. args: Record<string, any>
  23. }
  24. type DefaultSchema = Record<string, string | PropertyDefaultSchema | undefined>
  25. const getDef: (() => FuncKeywordDefinition) & {
  26. DEFAULTS: typeof DEFAULTS
  27. } = Object.assign(_getDef, {DEFAULTS})
  28. function _getDef(): FuncKeywordDefinition {
  29. return {
  30. keyword: "dynamicDefaults",
  31. type: "object",
  32. schemaType: ["string", "object"],
  33. modifying: true,
  34. valid: true,
  35. compile(schema: DefaultSchema, _parentSchema, it: SchemaCxt) {
  36. if (!it.opts.useDefaults || it.compositeRule) return () => true
  37. const fs: Record<string, () => any> = {}
  38. for (const key in schema) fs[key] = getDefault(schema[key])
  39. const empty = it.opts.useDefaults === "empty"
  40. return (data: Record<string, any>) => {
  41. for (const prop in schema) {
  42. if (data[prop] === undefined || (empty && (data[prop] === null || data[prop] === ""))) {
  43. data[prop] = fs[prop]()
  44. }
  45. }
  46. return true
  47. }
  48. },
  49. metaSchema: {
  50. type: "object",
  51. additionalProperties: {
  52. anyOf: [
  53. {type: "string"},
  54. {
  55. type: "object",
  56. additionalProperties: false,
  57. required: ["func", "args"],
  58. properties: {
  59. func: {type: "string"},
  60. args: {type: "object"},
  61. },
  62. },
  63. ],
  64. },
  65. },
  66. }
  67. }
  68. function getDefault(d: string | PropertyDefaultSchema | undefined): () => any {
  69. return typeof d == "object" ? getObjDefault(d) : getStrDefault(d)
  70. }
  71. function getObjDefault({func, args}: PropertyDefaultSchema): () => any {
  72. const def = DEFAULTS[func]
  73. assertDefined(func, def)
  74. return def(args)
  75. }
  76. function getStrDefault(d = ""): () => any {
  77. const def = DEFAULTS[d]
  78. assertDefined(d, def)
  79. return def()
  80. }
  81. function assertDefined(name: string, def?: DynamicDefaultFunc): asserts def is DynamicDefaultFunc {
  82. if (!def) throw new Error(`invalid "dynamicDefaults" keyword property value: ${name}`)
  83. }
  84. export default getDef
  85. module.exports = getDef