@tellescope/validation 1.4.0 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/validation.ts CHANGED
@@ -128,6 +128,7 @@ import {
128
128
  WeeklyAvailability,
129
129
  Timezone,
130
130
  TIMEZONES,
131
+ FormType,
131
132
  } from "@tellescope/types-models"
132
133
  import {
133
134
  DatabaseRecord,
@@ -192,8 +193,22 @@ export type EscapeBuilder <R=any> = {
192
193
  }
193
194
  export type ComplexEscapeBuilder <C,R=any> = (customization: C) => EscapeBuilder<R>
194
195
 
196
+ export type ValidatorDefinition <R=any> = {
197
+ validate: EscapeBuilder<R>,
198
+ getType: () => string | object,
199
+ getExample: () => string | number | boolean | object,
200
+ }
201
+ export type ValidatorBuilder <R=any, C={}> = (options: ValidatorOptions & C) => ValidatorDefinition<R>
202
+
203
+ const EXAMPLE_OBJECT_ID = '60398b0231a295e64f084fd9'
204
+ const getTypeString = () => "string"
205
+ const getTypeNumber = () => "number"
206
+ const getExampleString = () => 'example string'
207
+ const getExampleObjectId = () => EXAMPLE_OBJECT_ID
208
+
195
209
  export type InputValues <T> = { [K in keyof T]: JSONType }
196
- export type InputValidation<T> = { [K in keyof T]: EscapeFunction }
210
+ export type InputValidation<T> = { [K in keyof T]: ValidatorDefinition }
211
+ export type InputValidationOld<T> = { [K in keyof T]: EscapeFunction }
197
212
 
198
213
  export const MAX_FILE_SIZE = 25000000 // 25 megabytes in bytes
199
214
  const DEFAULT_MAX_LENGTH = 5000
@@ -282,16 +297,17 @@ export const build_validator: BuildValidator_T = (escapeFunction, options={} as
282
297
  }
283
298
  }
284
299
 
285
- export const fieldsToValidation = <T>(fs: { [K in keyof T]: { validator: EscapeBuilder, required?: boolean } }): InputValidation<T> => {
286
- const validation = {} as InputValidation<T>
300
+ export const fieldsToValidationOld = <T>(fs: { [K in keyof T]: { validator: ValidatorDefinition, required?: boolean } }): InputValidationOld<T> => {
301
+ const validation = {} as InputValidationOld<T>
287
302
 
288
303
  for (const f in fs) {
289
- validation[f] = fs[f].validator({ isOptional: !fs[f].required })
304
+ validation[f] = fs[f].validator.validate({ isOptional: !fs[f].required })
290
305
  }
291
306
 
292
307
  return validation
293
308
  }
294
309
 
310
+
295
311
  /********************************* VALIDATORS *********************************/
296
312
  const optionsWithDefaults = (options={} as ValidatorOptions) => {
297
313
  return {
@@ -319,20 +335,24 @@ export const binaryOrValidator = <A, B>(f1: EscapeFunction<A>, f2: EscapeFunctio
319
335
  },
320
336
  { ...o, listOf: false }
321
337
  )
322
- export const orValidator = <T>(escapeFunctions: { [K in keyof T]: EscapeFunction<T[K]> }): EscapeBuilder<T[keyof T]> => (o={}) => build_validator(
323
- value => {
324
- for (const field in escapeFunctions) {
325
- const escape = escapeFunctions[field]
326
- try {
327
- return escape(value)
328
- } catch(err) {
329
- continue
338
+ export const orValidator = <T>(validators: { [K in keyof T]: ValidatorDefinition<T[K]> }): ValidatorDefinition<T[keyof T]> => ({
339
+ validate: (o={}) => build_validator(
340
+ value => {
341
+ for (const field in validators) {
342
+ const escape = validators[field]
343
+ try {
344
+ return escape.validate()(value)
345
+ } catch(err) {
346
+ continue
347
+ }
330
348
  }
331
- }
332
- throw `Value does not match any of the expected options`
333
- },
334
- { ...o, listOf: false }
335
- )
349
+ throw `Value does not match any of the expected options`
350
+ },
351
+ { ...o, listOf: false }
352
+ ),
353
+ getExample: () => (Object.values(validators)[0] as ValidatorDefinition).getExample(),
354
+ getType: () => [Object.values(validators).map(v => (v as ValidatorDefinition).getType())]
355
+ })
336
356
 
337
357
  export const filterCommandsValidator: EscapeBuilder<FilterType> = (o={}) => build_validator(
338
358
  (value: any) => {
@@ -389,9 +409,11 @@ export const convertCommands = (operators: Indexable<any>) => {
389
409
 
390
410
  interface ObjectOptions {
391
411
  emptyOk?: boolean,
412
+ isOptional?: boolean,
392
413
  throwOnUnrecognizedField?: boolean,
393
414
  }
394
- export const objectValidator = <T extends object>(i: InputValidation<Required<T>>, objectOptions={ emptyOk: true } as ObjectOptions): EscapeBuilder<T> => (o={}) => build_validator(
415
+
416
+ export const objectValidatorOld = <T extends object>(i: InputValidationOld<Required<T>>, objectOptions={ emptyOk: true } as ObjectOptions): EscapeBuilder<T> => (o={}) => build_validator(
395
417
  (object: any) => {
396
418
  const emptyOk = objectOptions.emptyOk ?? true
397
419
  const validated = {} as T
@@ -427,68 +449,140 @@ export const objectValidator = <T extends object>(i: InputValidation<Required<T>
427
449
  return validated
428
450
  }, { ...o, isObject: true, listOf: false }
429
451
  )
430
- export const listOfObjectsValidator = <T extends object>(i: InputValidation<Required<T>>, objectOptions={ emptyOk: true }): EscapeBuilder<T[]> => (o={}) => build_validator(
431
- (object: any) => {
432
- const emptyOk = !!objectOptions.emptyOk || o.emptyListOk
433
- const validated = {} as T
452
+ export const listValidatorOld = <T>(b: EscapeFunction<T>): EscapeBuilder<T[]> => o => build_validator(
453
+ b, { ...o, listOf: true }
454
+ )
434
455
 
435
- if (!is_object(object)) {
436
- throw new Error(`Expected a non-null object by got ${object}`)
437
- }
438
- if (!emptyOk && object_is_empty(object)) {
439
- throw new Error(`Expected a non-empty object`)
440
- }
456
+ const exampleObject = (fields: InputValidation<any>) => {
457
+ const examples = {} as Indexable<string | number | boolean | object>
441
458
 
442
- // don't throw on unrecognized fields, just ignore/don't validate them
443
- // const unrecognizedFields = []
444
- // for (const field in object) {
445
- // if (!(i as Indexable)[field]) {
446
- // unrecognizedFields.push(field)
447
- // }
448
- // }
449
- // if (unrecognizedFields.length > 0) {
450
- // throw new Error(`Got unexpected field(s) [${unrecognizedFields.join(', ')}]`)
451
- // }
459
+ for (const field in fields) {
460
+ examples[field] = fields[field].getExample()
461
+ }
452
462
 
453
- for (const field in i) {
454
- const value = (object as Indexable)[field]
463
+ return examples
464
+ }
465
+ const typeObject = (fields: InputValidation<any>) => {
466
+ const types = {} as Indexable<string | object>
455
467
 
456
- const escaped = i[field](value) // may be required
457
- if (escaped === undefined) continue
468
+ for (const field in fields) {
469
+ types[field] = fields[field].getType()
470
+ }
458
471
 
459
- validated[field] = escaped
460
- }
472
+ return types
473
+ }
461
474
 
462
- return validated
463
- }, { ...o, isObject: true, listOf: true, emptyListOk: !!objectOptions.emptyOk || o.emptyListOk }
464
- )
475
+ export const objectValidator = <T extends object>(i: InputValidation<Required<T>>, objectOptions={ emptyOk: true } as ObjectOptions): ValidatorDefinition<T> => ({
476
+ validate: (o={}) => build_validator(
477
+ (object: any) => {
478
+ const emptyOk = objectOptions.emptyOk ?? true
479
+ const validated = {} as T
465
480
 
466
- export const objectAnyFieldsValidator = <T>(valueValidator?: EscapeFunction<T>): EscapeBuilder<Indexable<T>> => (o={}) => build_validator(
467
- (object: any) => {
468
- if (!is_object(object)) { throw new Error("Expected a non-null object by got ${object}") }
481
+ if (!is_object(object)) {
482
+ throw new Error(`Expected a non-null object by got ${object}`)
483
+ }
484
+ if (!emptyOk && object_is_empty(object)) {
485
+ throw new Error(`Expected a non-empty object`)
486
+ }
469
487
 
470
- const validated = {} as Indexable
488
+ // don't throw on unrecognized fields, just ignore/don't validate them
489
+ if (objectOptions.throwOnUnrecognizedField) {
490
+ const unrecognizedFields = []
491
+ for (const field in object) {
492
+ if (!(i as Indexable)[field]) {
493
+ unrecognizedFields.push(field)
494
+ }
495
+ }
496
+ if (unrecognizedFields.length > 0) {
497
+ throw new Error(`Got unexpected field(s) [${unrecognizedFields.join(', ')}]`)
498
+ }
499
+ }
500
+
501
+ for (const field in i) {
502
+ const value = (object as Indexable)[field]
503
+ const escaped = i[field].validate()(value) // may be required
504
+ if (escaped === undefined) continue
505
+
506
+ validated[field] = escaped
507
+ }
508
+
509
+ return validated
510
+ }, { ...o, isObject: true, listOf: false, isOptional: !!objectOptions.isOptional || o.isOptional }
511
+ ),
512
+ getExample: () => exampleObject(i),
513
+ getType: () => typeObject(i),
514
+ })
515
+
516
+ export const listOfObjectsValidator = <T extends object>(i: InputValidation<Required<T>>, objectOptions={ emptyOk: true }): ValidatorDefinition<T[]> => ({
517
+ validate: (o={}) => build_validator(
518
+ (object: any) => {
519
+ const emptyOk = !!objectOptions.emptyOk || o.emptyListOk
520
+ const validated = {} as T
521
+
522
+ if (!is_object(object)) {
523
+ throw new Error(`Expected a non-null object by got ${object}`)
524
+ }
525
+ if (!emptyOk && object_is_empty(object)) {
526
+ throw new Error(`Expected a non-empty object`)
527
+ }
528
+
529
+ // don't throw on unrecognized fields, just ignore/don't validate them
530
+ // const unrecognizedFields = []
531
+ // for (const field in object) {
532
+ // if (!(i as Indexable)[field]) {
533
+ // unrecognizedFields.push(field)
534
+ // }
535
+ // }
536
+ // if (unrecognizedFields.length > 0) {
537
+ // throw new Error(`Got unexpected field(s) [${unrecognizedFields.join(', ')}]`)
538
+ // }
539
+
540
+ for (const field in i) {
541
+ const value = (object as Indexable)[field]
542
+
543
+ const escaped = i[field].validate()(value) // may be required
544
+ if (escaped === undefined) continue
545
+
546
+ validated[field] = escaped
547
+ }
548
+
549
+ return validated
550
+ }, { ...o, isObject: true, listOf: true, emptyListOk: !!objectOptions.emptyOk || o.emptyListOk }
551
+ ),
552
+ getExample: () => [exampleObject(i)], // don't forget list
553
+ getType: () => [typeObject(i)] // don't forget list
554
+ })
555
+
556
+ export const objectAnyFieldsValidator = <T>(valueValidator?: ValidatorDefinition<T>): ValidatorDefinition<Indexable<T>> => ({
557
+ validate: (o={}) => build_validator(
558
+ (object: any) => {
559
+ if (!is_object(object)) { throw new Error("Expected a non-null object by got ${object}") }
471
560
 
472
- for (const field in object) {
473
- if (valueValidator) {
474
- validated[field] = valueValidator(object[field])
475
- } else if (typeof object[field] === 'number') {
476
- validated[field] = numberValidator(object[field])
477
- } else if (typeof object[field] === 'string') {
478
- validated[field] = stringValidator(object[field])
479
- } else if (object[field] === null) {
480
- validated[field] = null
481
- } else {
561
+ const validated = {} as Indexable
562
+
563
+ for (const field in object) {
482
564
  if (valueValidator) {
483
- throw new Error(`Field ${field} is not a string or number`)
565
+ validated[field] = valueValidator.validate()(object[field])
566
+ } else if (typeof object[field] === 'number') {
567
+ validated[field] = numberValidator.validate()(object[field])
568
+ } else if (typeof object[field] === 'string') {
569
+ validated[field] = stringValidator.validate()(object[field])
570
+ } else if (object[field] === null) {
571
+ validated[field] = null
572
+ } else {
573
+ if (valueValidator) {
574
+ throw new Error(`Field ${field} is not a string or number`)
575
+ }
576
+ validated[field] = object[field]
484
577
  }
485
- validated[field] = object[field]
486
578
  }
487
- }
488
579
 
489
- return validated
490
- }, { ...o, isObject: true, listOf: false }
491
- )
580
+ return validated
581
+ }, { ...o, isObject: true, listOf: false }
582
+ ),
583
+ getExample: () => `{ "key": ${valueValidator?.getExample?.() ?? '"value"'} }`,
584
+ getType: () => `{ "key": ${valueValidator?.getType?.() ?? 'string'} }`,
585
+ })
492
586
 
493
587
  export const objectAnyFieldsAnyValuesValidator = objectAnyFieldsValidator()
494
588
 
@@ -504,51 +598,125 @@ export const escapeString: EscapeWithOptions<string> = (o={}) => string => {
504
598
  }
505
599
  return string
506
600
  }
507
- export const stringValidator: EscapeBuilder<string> = (o={}) => build_validator(
508
- escapeString(o), { ...o, maxLength: o.maxLength ?? 1000, listOf: false }
509
- )
510
- export const stringValidator100: EscapeBuilder<string> = (o={}) => build_validator(
511
- escapeString(o), { ...o, maxLength: 100, listOf: false }
512
- )
513
- export const stringValidator250: EscapeBuilder<string> = (o={}) => build_validator(
514
- escapeString(o), { ...o, maxLength: 250, listOf: false }
515
- )
516
- export const stringValidator1000: EscapeBuilder<string> = (o={}) => build_validator(
517
- escapeString(o), { ...o, maxLength: 1000, listOf: false }
518
- )
519
- export const stringValidator5000: EscapeBuilder<string> = (o={}) => build_validator(
520
- escapeString(o), { ...o, maxLength: 5000, listOf: false }
521
- )
522
- export const stringValidator25000: EscapeBuilder<string> = (o={}) => build_validator(
523
- escapeString(o), { ...o, maxLength: 25000, listOf: false }
524
- )
525
- export const SMSMessageValidator: EscapeBuilder<string> = (o={}) => build_validator(
526
- escapeString(o), { ...o, maxLength: 630, listOf: false }
527
- )
528
601
 
529
- export const listValidator = <T>(b: EscapeFunction<T>): EscapeBuilder<T[]> => o => build_validator(
530
- b, { ...o, listOf: true }
531
- )
532
- export const listValidatorEmptyOk = <T>(b: EscapeFunction<T>): EscapeBuilder<T[]> => o => build_validator(
533
- b, { ...o, listOf: true, emptyListOk: true }
534
- )
602
+ export const stringValidator: ValidatorDefinition<string> = {
603
+ validate: (o={}) => build_validator(
604
+ escapeString(o), { ...o, maxLength: o.maxLength ?? 1000, listOf: false }
605
+ ),
606
+ getExample: getExampleString,
607
+ getType: getTypeString,
608
+ }
609
+ export const stringValidatorOptional: ValidatorDefinition<string> = {
610
+ validate: (o={}) => build_validator(
611
+ escapeString(o), { ...o, maxLength: o.maxLength ?? 1000, listOf: false, isOptional: true }
612
+ ),
613
+ getExample: getExampleString,
614
+ getType: getTypeString,
615
+ }
616
+ export const stringValidator100: ValidatorDefinition<string> = {
617
+ validate: (o={}) => build_validator(
618
+ escapeString(o), { ...o, maxLength: 100, listOf: false }
619
+ ),
620
+ getExample: () => getExampleString,
621
+ getType: () => getTypeString
622
+ }
623
+
624
+ export const stringValidator250: ValidatorDefinition<string> = {
625
+ validate: (o={}) => build_validator(
626
+ escapeString(o), { ...o, maxLength: 250, listOf: false }
627
+ ),
628
+ getExample: getExampleString,
629
+ getType: getTypeString,
630
+ }
631
+ export const stringValidator1000: ValidatorDefinition<string> = {
632
+ validate: (o={}) => build_validator(
633
+ escapeString(o), { ...o, maxLength: 1000, listOf: false }
634
+ ),
635
+ getExample: getExampleString,
636
+ getType: getTypeString,
637
+ }
638
+ export const stringValidator5000: ValidatorDefinition<string> = {
639
+ validate: (o={}) => build_validator(
640
+ escapeString(o), { ...o, maxLength: 5000, listOf: false }
641
+ ),
642
+ getExample: getExampleString,
643
+ getType: getTypeString,
644
+ }
645
+ export const stringValidator5000EmptyOkay: ValidatorDefinition<string> = {
646
+ validate: (o={}) => build_validator(
647
+ escapeString(o), { ...o, maxLength: 5000, listOf: false, emptyStringOk: true }
648
+ ),
649
+ getExample: getExampleString,
650
+ getType: getTypeString,
651
+ }
652
+ export const stringValidator5000Optional: ValidatorDefinition<string> = {
653
+ validate: (o={}) => build_validator(
654
+ escapeString(o), { ...o, maxLength: 5000, listOf: false, isOptional: true, emptyStringOk: true }
655
+ ),
656
+ getExample: getExampleString,
657
+ getType: getTypeString,
658
+ }
659
+ export const stringValidator25000: ValidatorDefinition<string> = {
660
+ validate: (o={}) => build_validator(
661
+ escapeString(o), { ...o, maxLength: 25000, listOf: false }
662
+ ),
663
+ getExample: getExampleString,
664
+ getType: getTypeString,
665
+ }
666
+ export const stringValidator25000EmptyOkay: ValidatorDefinition<string> = {
667
+ validate: (o={}) => build_validator(
668
+ escapeString(o), { ...o, maxLength: 25000, listOf: false, emptyStringOk: true }
669
+ ),
670
+ getExample: getExampleString,
671
+ getType: getTypeString,
672
+ }
673
+ export const SMSMessageValidator: ValidatorDefinition<string> = {
674
+ validate: (o={}) => build_validator(
675
+ escapeString(o), { ...o, maxLength: 630, listOf: false }
676
+ ),
677
+ getExample: getExampleString,
678
+ getType: getTypeString,
679
+ }
535
680
 
536
- export const listOfStringsValidator = listValidator(stringValidator())
537
- export const listOfStringsValidatorEmptyOk = listValidatorEmptyOk(stringValidator())
538
- export const listOfObjectAnyFieldsAnyValuesValidator = listValidator(objectAnyFieldsAnyValuesValidator())
681
+ export const listValidator = <T>(b: ValidatorDefinition<T>, o?: ValidatorOptions | ValidatorOptionsForList): ValidatorDefinition<T[]> => ({
682
+ validate: o => build_validator(b.validate(o as any), { ...o, listOf: true }),
683
+ getExample: () => [b.getExample()],
684
+ getType: () => [b.getExample()],
685
+ })
686
+ export const listValidatorEmptyOk = <T>(b: ValidatorDefinition<T>, o?: ValidatorOptions): ValidatorDefinition<T[]> => ({
687
+ validate: o => build_validator(b.validate(o as any), { ...o, listOf: true, emptyListOk: true }),
688
+ getExample: () => [b.getExample()],
689
+ getType: () => [b.getExample()],
690
+ })
691
+ export const listValidatorOptionalOrEmptyOk = <T>(b: ValidatorDefinition<T>, o?: ValidatorOptions): ValidatorDefinition<T[]> => ({
692
+ validate: o => build_validator(b.validate(o as any), { ...o, listOf: true, emptyListOk: true, isOptional: true }),
693
+ getExample: () => [b.getExample()],
694
+ getType: () => [b.getExample()],
695
+ })
539
696
 
540
- export const booleanValidator: EscapeBuilder<boolean> = (options={}) => build_validator(
541
- boolean => {
542
- if (boolean === 'true') return true
543
- if (boolean === 'false') return false
697
+ export const listOfStringsValidator = listValidator(stringValidator)
698
+ export const listOfStringsValidatorOptionalOrEmptyOk = listValidatorOptionalOrEmptyOk(stringValidator)
699
+ export const listOfStringsValidatorEmptyOk = listValidatorEmptyOk(stringValidator)
700
+ export const listOfObjectAnyFieldsAnyValuesValidator = listValidator(objectAnyFieldsAnyValuesValidator)
544
701
 
545
- if (typeof boolean !== 'boolean') {
546
- throw new Error(options.errorMessage || "Invalid boolean")
547
- }
548
- return boolean
549
- },
550
- { ...options, isBoolean: true, listOf: false }
551
- )
702
+ export const booleanValidatorBuilder: ValidatorBuilder<boolean> = (defaults) => ({
703
+ validate: (options={}) => build_validator(
704
+ boolean => {
705
+ if (boolean === 'true') return true
706
+ if (boolean === 'false') return false
707
+
708
+ if (typeof boolean !== 'boolean') {
709
+ throw new Error(options.errorMessage || "Invalid boolean")
710
+ }
711
+ return boolean
712
+ },
713
+ { ...defaults, ...options, isBoolean: true, listOf: false }
714
+ ),
715
+ getExample: () => true,
716
+ getType: () => "boolean",
717
+ })
718
+ export const booleanValidator = booleanValidatorBuilder({ })
719
+ export const booleanValidatorOptional = booleanValidatorBuilder({ isOptional: true })
552
720
 
553
721
  export const escapeMongoId: EscapeFunction<string> = (mongoId: any) => {
554
722
  if (typeof mongoId !== 'string') throw new Error('Expecting string id')
@@ -557,12 +725,20 @@ export const escapeMongoId: EscapeFunction<string> = (mongoId: any) => {
557
725
  }
558
726
  return mongoId
559
727
  }
560
- export const mongoIdValidator: EscapeBuilder<ObjectId> = (o={}) => build_validator(
561
- s => to_object_id(escapeMongoId(s)), { ...optionsWithDefaults(o), maxLength: 100, listOf: false }
562
- )
563
- export const mongoIdStringValidator: EscapeBuilder<string> = (o={}) => build_validator(
564
- escapeMongoId, { ...optionsWithDefaults(o), maxLength: 100, listOf: false }
565
- )
728
+ export const mongoIdValidator: ValidatorDefinition<ObjectId> = {
729
+ validate: (o={}) => build_validator(
730
+ s => to_object_id(escapeMongoId(s)), { ...optionsWithDefaults(o), maxLength: 100, listOf: false }
731
+ ),
732
+ getType: getTypeString,
733
+ getExample: getExampleObjectId,
734
+ }
735
+ export const buildMongoIdStringValidator: ValidatorBuilder<string> = (options) => ({
736
+ validate: (o={}) => build_validator(
737
+ escapeMongoId, { ...optionsWithDefaults({ ...options, ...o }), maxLength: 100, listOf: false }
738
+ ),
739
+ getType: getTypeString,
740
+ getExample: getExampleObjectId,
741
+ })
566
742
 
567
743
  export const nullValidator: EscapeBuilder<null> = (o={}) => build_validator(
568
744
  v => {
@@ -573,215 +749,296 @@ export const nullValidator: EscapeBuilder<null> = (o={}) => build_validator(
573
749
  { ...o, listOf: false }
574
750
  )
575
751
 
576
- export const mongoIdRequired = mongoIdValidator()
577
- export const mongoIdOptional = mongoIdValidator({ isOptional: true })
578
- export const mongoIdStringRequired = mongoIdStringValidator()
579
- export const mongoIdStringOptional = mongoIdStringValidator({ isOptional: true })
580
- export const listOfMongoIdValidator = listValidator(mongoIdValidator())
581
- export const listOfMongoIdStringValidator = listValidator(mongoIdStringValidator())
582
- export const listOfMongoIdStringValidatorEmptyOk = listValidatorEmptyOk(mongoIdStringValidator())
752
+ export const mongoIdRequired = mongoIdValidator.validate()
753
+ export const mongoIdOptional = mongoIdValidator.validate({ isOptional: true })
754
+ export const listOfMongoIdValidator = listValidator(mongoIdValidator)
755
+
756
+ export const mongoIdStringRequired = buildMongoIdStringValidator({ isOptional: false })
757
+ export const mongoIdStringOptional = buildMongoIdStringValidator({ isOptional: true })
758
+ export const listOfMongoIdStringValidator = listValidator(mongoIdStringRequired)
759
+ export const listOfMongoIdStringValidatorEmptyOk = listValidatorEmptyOk(mongoIdStringRequired)
583
760
 
584
761
  export const first_letter_capitalized = (s='') => s.charAt(0).toUpperCase() + s.slice(1)
585
762
  export const escape_name = (namestring: string) => namestring.replace(/[^a-zA-Z0-9-_ /.]/, '').substring(0, 100)
586
763
 
587
764
  // enforces first-letter capitalization
588
- export const nameValidator: EscapeBuilder<string> = (options={}) => build_validator(
589
- name => {
590
- if (typeof name !== 'string') throw new Error('Expecting string value')
765
+ export const nameValidator: ValidatorDefinition<string> = {
766
+ validate: (options={}) => build_validator(
767
+ name => {
768
+ if (typeof name !== 'string') throw new Error('Expecting string value')
591
769
 
592
- name = escape_name(name)
593
- if (!name) throw new Error("Invalid name")
770
+ name = escape_name(name)
771
+ if (!name) throw new Error("Invalid name")
594
772
 
595
- return first_letter_capitalized(name)
596
- },
597
- { ...options, maxLength: 100, trim: true, listOf: false }
598
- )
773
+ return first_letter_capitalized(name)
774
+ },
775
+ { ...options, maxLength: 100, trim: true, listOf: false }
776
+ ),
777
+ getExample: () => 'John',
778
+ getType: getTypeString,
779
+ }
599
780
 
781
+ export const emailValidator: ValidatorDefinition<string> = {
782
+ validate: (options={}) => build_validator(
783
+ (email) => {
784
+ if (typeof email !== 'string') throw new Error('Expecting string value')
785
+ if (!isEmail(email)) { throw new Error(options.errorMessage || "Invalid email") }
600
786
 
601
- export const emailValidator: EscapeBuilder<string> = (options={}) => build_validator(
602
- (email) => {
603
- if (typeof email !== 'string') throw new Error('Expecting string value')
604
- if (!isEmail(email)) { throw new Error(options.errorMessage || "Invalid email") }
787
+ return email.toLowerCase()
788
+ },
789
+ { ...options, maxLength: 250, listOf: false }
790
+ ),
791
+ getExample: () => "example@tellescope.com",
792
+ getType: getTypeString,
793
+ }
794
+ export const emailValidatorOptional: ValidatorDefinition<string> = {
795
+ validate: (options={}) => build_validator(
796
+ (email) => {
797
+ if (typeof email !== 'string') throw new Error('Expecting string value')
798
+ if (!isEmail(email)) { throw new Error(options.errorMessage || "Invalid email") }
605
799
 
606
- return email.toLowerCase()
607
- },
608
- { ...options, maxLength: 250, listOf: false }
609
- )
800
+ return email.toLowerCase()
801
+ },
802
+ { ...options, maxLength: 250, listOf: false, isOptional: true, emptyStringOk: true }
803
+ ),
804
+ getExample: () => "example@tellescope.com",
805
+ getType: getTypeString,
806
+ }
610
807
 
611
- export const emailValidatorEmptyOkay: EscapeBuilder<string> = (options={}) => build_validator(
612
- (email) => {
613
- if (typeof email !== 'string') throw new Error('Expecting string value')
614
- if (!isEmail(email)) { throw new Error(options.errorMessage || "Invalid email") }
808
+ export const emailValidatorEmptyOkay: ValidatorDefinition<string> = {
809
+ validate: (options={}) => build_validator(
810
+ (email) => {
811
+ if (typeof email !== 'string') throw new Error('Expecting string value')
812
+ if (!isEmail(email)) { throw new Error(options.errorMessage || "Invalid email") }
615
813
 
616
- return email.toLowerCase()
617
- },
618
- { ...options, maxLength: 250, emptyStringOk: true, listOf: false }
619
- )
814
+ return email.toLowerCase()
815
+ },
816
+ { ...options, maxLength: 250, emptyStringOk: true, listOf: false }
817
+ ),
818
+ getExample: () => "example@tellescope.com",
819
+ getType: getTypeString,
820
+ }
620
821
 
621
822
 
622
- export const numberValidatorBuilder: ComplexEscapeBuilder<{ lower: number, upper: number }, number> = r => (options={}) => {
623
- options.isNumber = true
823
+ export const numberValidatorBuilder: ValidatorBuilder<number, { lower: number, upper: number }> = ({ lower, upper, ...higherOptions }) => ({
824
+ validate: (options={}) => {
825
+ options.isNumber = true
624
826
 
625
- return build_validator(
626
- (number: any) => {
627
- number = Number(number) // ok to throw error!
628
- if (typeof number !== "number" || isNaN(number)) {
629
- throw new Error(options.errorMessage || `Not a valid number`)
630
- }
631
- if (!r) return number
827
+ return build_validator(
828
+ (number: any) => {
829
+ number = Number(number) // ok to throw error!
830
+ if (typeof number !== "number" || isNaN(number)) {
831
+ throw new Error(options.errorMessage || `Not a valid number`)
832
+ }
833
+ if (!(lower || upper)) return number
632
834
 
633
- if (!(number >= r.lower && number <= r.upper)) {
634
- throw new Error(options.errorMessage || `Not a valid number for [${r.lower}-${r.upper}]`)
635
- }
636
- return number
637
- },
638
- { ...options, listOf: false }
639
- )
640
- }
835
+ if (!(number >= lower && number <= upper)) {
836
+ throw new Error(options.errorMessage || `Not a valid number for [${lower}-${upper}]`)
837
+ }
838
+ return number
839
+ },
840
+ { ...optionsWithDefaults({ ...higherOptions, ...options }), listOf: false, }
841
+ )
842
+ },
843
+ getExample: () => lower, // `a number from ${lower} to ${upper}`,
844
+ getType: getTypeNumber,
845
+ })
641
846
 
642
847
  export const nonNegNumberValidator = numberValidatorBuilder({ lower: 0, upper: 10000000000000 }) // max is 2286 in UTC MS
643
848
  export const numberValidator = numberValidatorBuilder({ lower: -100000000, upper: 10000000000000 }) // max is 2286 in UTC MS
849
+ export const numberValidatorOptional = numberValidatorBuilder({ lower: -100000000, upper: 10000000000000, isOptional: true, emptyStringOk: true }) // max is 2286 in UTC MS
644
850
  export const fileSizeValidator = numberValidatorBuilder({ lower: 0, upper: MAX_FILE_SIZE })
645
851
 
646
- export const dateValidator: EscapeBuilder<Date> = (options={}) => build_validator(
647
- (date: any) => {
648
- if (isDate(date)) throw new Error(options.errorMessage || "Invalid date")
649
-
650
- return new Date(date)
651
- },
652
- { ...options, maxLength: 250, listOf: false }
653
- )
852
+ export const dateValidator: ValidatorDefinition<Date> = {
853
+ validate: (options={}) => build_validator(
854
+ (date: any) => {
855
+ if (isDate(date)) throw new Error(options.errorMessage || "Invalid date")
654
856
 
655
- export const exactMatchValidator = <T extends string>(matches: T[]): EscapeBuilder<T> => (o={}) => build_validator(
656
- (match: JSONType) => {
657
- if (matches.filter(m => m === match).length === 0) {
658
- throw new Error(`Value must match one of ${matches}`)
659
- }
660
- return match
661
- },
662
- { ...o, listOf: false }
663
- )
664
- export const exactMatchListValidator = <T extends string>(matches: T[]): EscapeBuilder<T[]> => (o={}) => build_validator(
665
- (match: JSONType) => {
666
- if (matches.filter(m => m === match).length === 0) {
667
- throw new Error(`Value must match one of ${matches}`)
668
- }
669
- return match
670
- },
671
- { ...o, listOf: true }
672
- )
857
+ return new Date(date)
858
+ },
859
+ { ...options, maxLength: 250, listOf: false }
860
+ ),
861
+ getExample: () => new Date().toISOString(),
862
+ getType: () => "Date",
863
+ }
864
+ export const dateValidatorOptional: ValidatorDefinition<Date> = {
865
+ validate: (options={}) => build_validator(
866
+ (date: any) => {
867
+ if (isDate(date)) throw new Error(options.errorMessage || "Invalid date")
673
868
 
674
- export const journeysValidator: EscapeBuilder<Indexable> = (options={}) => build_validator(
675
- (journeys) => {
676
- if (typeof journeys !== 'object') {
677
- throw new Error('Expecting an object')
678
- }
869
+ return new Date(date)
870
+ },
871
+ { ...options, maxLength: 250, listOf: false, isOptional: true, emptyStringOk: true }
872
+ ),
873
+ getExample: () => new Date().toISOString(),
874
+ getType: () => "Date",
875
+ }
679
876
 
680
- const mIdValidator = mongoIdValidator()
681
- const stateValidator = stringValidator({ isOptional: true, maxLength: 75, errorMessage: "Journey state names may not exceed 75 characters" })
682
- for (const j in journeys) {
683
- mIdValidator(j);
684
- (journeys as Indexable)[j] = stateValidator(journeys[j as keyof typeof journeys]);
685
- }
686
- return journeys
687
- },
688
- { ...options, isObject: true, listOf: false }
689
- )
877
+ export const exactMatchValidator = <T extends string>(matches: T[]): ValidatorDefinition<T> => ({
878
+ validate: (o={}) => build_validator(
879
+ (match: JSONType) => {
880
+ if (matches.filter(m => m === match).length === 0) {
881
+ throw new Error(`Value must match one of ${matches}`)
882
+ }
883
+ return match
884
+ },
885
+ { ...o, listOf: false }
886
+ ),
887
+ getExample: () => matches[0],
888
+ getType: getTypeString,
889
+ })
890
+ export const exactMatchValidatorOptional = <T extends string>(matches: T[]): ValidatorDefinition<T> => ({
891
+ validate: (o={}) => build_validator(
892
+ (match: JSONType) => {
893
+ if (matches.filter(m => m === match).length === 0) {
894
+ throw new Error(`Value must match one of ${matches}`)
895
+ }
896
+ return match
897
+ },
898
+ { ...o, listOf: false, isOptional: true }
899
+ ),
900
+ getExample: () => matches[0],
901
+ getType: getTypeString,
902
+ })
903
+ export const exactMatchListValidator = <T extends string>(matches: T[]) => listValidator(exactMatchValidator(matches))
690
904
 
691
- export const escape_phone_number = (p='') => p.replace(/[^\d+]/g, '')
905
+ export const journeysValidator: ValidatorDefinition<Indexable> = {
906
+ validate: (options={}) => build_validator(
907
+ (journeys) => {
908
+ if (typeof journeys !== 'object') {
909
+ throw new Error('Expecting an object')
910
+ }
692
911
 
693
- export const phoneValidator: EscapeBuilder<string> = (options={}) => build_validator(
694
- phone => {
695
- if (typeof phone !== "string") throw new Error(`Expecting phone to be string but got ${phone}`)
912
+ const mIdValidator = mongoIdValidator.validate()
913
+ const stateValidator = stringValidator.validate({ isOptional: true, maxLength: 75, errorMessage: "Journey state names may not exceed 75 characters" })
914
+ for (const j in journeys) {
915
+ mIdValidator(j);
916
+ (journeys as Indexable)[j] = stateValidator(journeys[j as keyof typeof journeys]);
917
+ }
918
+ return journeys
919
+ },
920
+ { ...options, isObject: true, listOf: false }
921
+ ),
922
+ getExample: () => ({ [EXAMPLE_OBJECT_ID]: "status" }),
923
+ getType: () => ({ string: "string" }),
924
+ }
696
925
 
697
- let escaped = escape_phone_number(phone)
698
- if (escaped.length < 10) throw new Error(`Phone number must be at least 10 digits`)
926
+ export const escape_phone_number = (p='') => p.replace(/[^\d+]/g, '')
699
927
 
700
- escaped = escaped.startsWith('+') ? escaped
701
- : escaped.length === 10 ? '+1' + escaped // assume US country code for now
702
- : "+" + escaped // assume country code provided, but missing leading +
928
+ export const phoneValidator: ValidatorDefinition<string> = {
929
+ validate: (options={}) => build_validator(
930
+ phone => {
931
+ if (typeof phone !== "string") throw new Error(`Expecting phone to be string but got ${phone}`)
703
932
 
704
- if (!isMobilePhone(escaped, 'any', { strictMode: true })) {
705
- throw `Invalid phone number`
706
- }
933
+ let escaped = escape_phone_number(phone)
934
+ if (escaped.length < 10) throw new Error(`Phone number must be at least 10 digits`)
707
935
 
708
- return escaped
709
- },
710
- { ...options, maxLength: 25, listOf: false }
711
- )
936
+ escaped = escaped.startsWith('+') ? escaped
937
+ : escaped.length === 10 ? '+1' + escaped // assume US country code for now
938
+ : "+" + escaped // assume country code provided, but missing leading +
712
939
 
713
- export const phoneValidatorEmptyOkay: EscapeBuilder<string> = (options={}) => build_validator(
714
- phone => {
715
- if (typeof phone !== "string") throw new Error(`Expecting phone to be string but got ${phone}`)
940
+ if (!isMobilePhone(escaped, 'any', { strictMode: true })) {
941
+ throw `Invalid phone number`
942
+ }
716
943
 
717
- let escaped = escape_phone_number(phone)
718
- if (escaped.length < 10) throw new Error(`Phone number must be at least 10 digits`)
944
+ return escaped
945
+ },
946
+ { ...options, maxLength: 25, listOf: false }
947
+ ),
948
+ getExample: () => "+15555555555",
949
+ getType: getTypeString,
950
+ }
951
+ export const phoneValidatorOptional: ValidatorDefinition<string> = {
952
+ validate: (options={}) => build_validator(
953
+ phone => {
954
+ if (typeof phone !== "string") throw new Error(`Expecting phone to be string but got ${phone}`)
719
955
 
720
- escaped = escaped.startsWith('+') ? escaped
721
- : escaped.length === 10 ? '+1' + escaped // assume US country code for now
722
- : "+" + escaped // assume country code provided, but missing leading +
956
+ let escaped = escape_phone_number(phone)
957
+ if (escaped.length < 10) throw new Error(`Phone number must be at least 10 digits`)
723
958
 
724
- if (!isMobilePhone(escaped, 'any', { strictMode: true })) {
725
- throw `Invalid phone number`
726
- }
959
+ escaped = escaped.startsWith('+') ? escaped
960
+ : escaped.length === 10 ? '+1' + escaped // assume US country code for now
961
+ : "+" + escaped // assume country code provided, but missing leading +
727
962
 
728
- return escaped
729
- },
730
- { ...options, maxLength: 25, listOf: false, emptyStringOk: true }
731
- )
963
+ if (!isMobilePhone(escaped, 'any', { strictMode: true })) {
964
+ throw `Invalid phone number`
965
+ }
732
966
 
733
- export const fileTypeValidator: EscapeBuilder<string> = (options={}) => build_validator(
734
- (s: any) => {
735
- if (typeof s !== 'string') throw new Error("fileType must be a string")
736
- if (!isMimeType(s)) throw new Error(`${s} is not a valid file type`)
967
+ return escaped
968
+ },
969
+ { ...options, maxLength: 25, listOf: false, isOptional: true, emptyStringOk: true }
970
+ ),
971
+ getExample: () => "+15555555555",
972
+ getType: getTypeString,
973
+ }
737
974
 
738
- return s
739
- },
740
- { ...options, listOf: false }
741
- )
975
+ export const fileTypeValidator: ValidatorDefinition<string> = {
976
+ validate: (options={}) => build_validator(
977
+ (s: any) => {
978
+ if (typeof s !== 'string') throw new Error("fileType must be a string")
979
+ if (!isMimeType(s)) throw new Error(`${s} is not a valid file type`)
742
980
 
743
- export const urlValidator: EscapeBuilder<string> = (options={}) => build_validator(
744
- (s: any) => {
745
- if (typeof s !== 'string') throw new Error("URL must be a string")
746
- if (!isURL(s)) throw new Error(`${s} is not a valid URL`)
981
+ return s
982
+ },
983
+ { ...options, listOf: false }
984
+ ),
985
+ getExample: () => 'text/plain',
986
+ getType: getTypeString,
987
+ }
988
+ export const urlValidator: ValidatorDefinition<string> = {
989
+ validate: (options={}) => build_validator(
990
+ (s: any) => {
991
+ if (typeof s !== 'string') throw new Error("URL must be a string")
992
+ if (!isURL(s)) throw new Error(`${s} is not a valid URL`)
747
993
 
748
- return s
749
- },
750
- { ...options, listOf: false }
751
- )
994
+ return s
995
+ },
996
+ { ...options, listOf: false }
997
+ ),
998
+ getExample: () => '"https://www.tellescope.com"',
999
+ getType: getTypeString,
1000
+ }
752
1001
 
753
- export const safeBase64Validator = (options={}) => build_validator(
754
- (sb64: any) => {
755
- if (typeof sb64 !== 'string') throw new Error("Expecting string")
1002
+ export const safeBase64Validator: ValidatorDefinition<string> = {
1003
+ validate: (options={}) => build_validator(
1004
+ (sb64: any) => {
1005
+ if (typeof sb64 !== 'string') throw new Error("Expecting string")
756
1006
 
757
- // https://stackoverflow.com/questions/12930007/how-to-validate-base64-string-using-regex-in-javascript
758
- // regex with = + and / replaced as get_random_base64_URL_safe
759
- if (!/^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}..|[A-Za-z0-9_-]{3}.)?$/.test(sb64)) {
760
- throw `Invalid safe base64`
761
- }
762
- return sb64
763
- },
764
- { ...options, listOf: false }
765
- )
1007
+ // https://stackoverflow.com/questions/12930007/how-to-validate-base64-string-using-regex-in-javascript
1008
+ // regex with = + and / replaced as get_random_base64_URL_safe
1009
+ if (!/^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}..|[A-Za-z0-9_-]{3}.)?$/.test(sb64)) {
1010
+ throw `Invalid safe base64`
1011
+ }
1012
+ return sb64
1013
+ },
1014
+ { ...options, listOf: false }
1015
+ ),
1016
+ getExample: () => '129vjas0fkj1234jgfmnaef',
1017
+ getType: getTypeString,
1018
+ }
766
1019
 
767
- export const subdomainValidator = (options={}) => build_validator(
768
- subdomain => {
769
- if (typeof subdomain !== 'string') throw new Error("Expecting string value")
1020
+ export const subdomainValidator: ValidatorDefinition<string> = {
1021
+ validate: (options={}) => build_validator(
1022
+ subdomain => {
1023
+ if (typeof subdomain !== 'string') throw new Error("Expecting string value")
770
1024
 
771
- subdomain = subdomain.toLowerCase()
772
- if (subdomain.startsWith('-')) {
773
- subdomain = subdomain.substring(1)
774
- }
775
- while (subdomain.endsWith('-')) {
776
- subdomain = subdomain.substring(0, subdomain.length - 1)
777
- }
1025
+ subdomain = subdomain.toLowerCase()
1026
+ if (subdomain.startsWith('-')) {
1027
+ subdomain = subdomain.substring(1)
1028
+ }
1029
+ while (subdomain.endsWith('-')) {
1030
+ subdomain = subdomain.substring(0, subdomain.length - 1)
1031
+ }
778
1032
 
779
- subdomain = subdomain.replace(/[^a-zA-Z\d-]/g, '')
1033
+ subdomain = subdomain.replace(/[^a-zA-Z\d-]/g, '')
780
1034
 
781
- return subdomain
782
- },
783
- { ...options, maxLength: 50, listOf: false }
784
- )
1035
+ return subdomain
1036
+ },
1037
+ { ...options, maxLength: 50, listOf: false }
1038
+ ),
1039
+ getExample: () => 'example',
1040
+ getType: getTypeString,
1041
+ }
785
1042
 
786
1043
  type FileResponse = { type: 'file', name: string, secureName: string }
787
1044
  // export const fileResponseValidator: EscapeBuilder<FileResponse> = (options={}) => build_validator(
@@ -796,16 +1053,16 @@ type FileResponse = { type: 'file', name: string, secureName: string }
796
1053
  // { ...options, isObject: true, listOf: false }
797
1054
  // )
798
1055
  export const fileResponseValidator = objectValidator<FileResponse>({
799
- type: exactMatchValidator(['file'])(),
800
- name: stringValidator({ shouldTruncate: true, maxLength: 250 }),
801
- secureName: stringValidator250(),
1056
+ type: exactMatchValidator(['file']),
1057
+ name: stringValidator1000,
1058
+ secureName: stringValidator250,
802
1059
  })
803
1060
 
804
1061
  type SignatureResponse = { type: 'signature', signed: string | null, fullName: string }
805
1062
  export const signatureResponseValidator = objectValidator<SignatureResponse>({
806
- type: exactMatchValidator(['signature'])(),
807
- fullName: stringValidator({ maxLength: 100 }),
808
- signed: booleanValidator(),
1063
+ type: exactMatchValidator(['signature']),
1064
+ fullName: stringValidator100,
1065
+ signed: booleanValidator,
809
1066
  })
810
1067
 
811
1068
 
@@ -845,22 +1102,25 @@ const _FORM_FIELD_TYPES: { [K in FormFieldType]: any } = {
845
1102
  export const FORM_FIELD_TYPES = Object.keys(_FORM_FIELD_TYPES) as FormFieldType[]
846
1103
  export const formFieldTypeValidator = exactMatchValidator<FormFieldType>(FORM_FIELD_TYPES)
847
1104
 
848
- export const FORM_FIELD_VALIDATORS_BY_TYPE: { [K in FormFieldType]: (value?: FormResponseValueAnswer[keyof FormResponseValueAnswer], options?: any, isOptional?: boolean) => any } = {
849
- 'string': stringValidator({ maxLength: 5000, emptyStringOk: true, errorMessage: "Response must not exceed 5000 characters" }),
850
- 'number': numberValidator({ errorMessage: "Response must be a number" }),
851
- 'email': emailValidator(),
1105
+ export const FORM_FIELD_VALIDATORS_BY_TYPE: { [K in FormFieldType | 'userEmail' | 'phoneNumber']: (value?: FormResponseValueAnswer[keyof FormResponseValueAnswer], options?: any, isOptional?: boolean) => any } = {
1106
+ 'string': stringValidator.validate({ maxLength: 5000, emptyStringOk: true, errorMessage: "Response must not exceed 5000 characters" }),
1107
+ 'number': numberValidator.validate({ errorMessage: "Response must be a number" }),
1108
+ 'email': emailValidator.validate(),
1109
+
1110
+ 'userEmail': emailValidator.validate(),
1111
+ 'phone': phoneValidator.validate(),
1112
+ 'phoneNumber': phoneValidator.validate(), // backwards compatibility with old field name for phone
852
1113
 
853
- // @ts-ignore -- backwards compatibility with old field name for email
854
- 'userEmail': emailValidator(),
855
- 'phone': phoneValidator(),
856
- 'phoneNumber': phoneValidator(), // backwards compatibility with old field name for phone
1114
+ "date": dateValidator.validate(),
1115
+ "ranking": listOfStringsValidator.validate(),
1116
+ "rating": numberValidator.validate(),
857
1117
 
858
1118
  // fileInfo: FileResponse
859
1119
  'file': (fileInfo: any, _, isOptional) => {
860
1120
  if (isOptional && (!fileInfo || object_is_empty(fileInfo))) {
861
1121
  return { type: 'file', secureName: null }
862
1122
  }
863
- return fileResponseValidator()(fileInfo)
1123
+ return fileResponseValidator.validate()(fileInfo)
864
1124
  },
865
1125
  // sigInfo: SignatureResponse
866
1126
 
@@ -868,7 +1128,7 @@ export const FORM_FIELD_VALIDATORS_BY_TYPE: { [K in FormFieldType]: (value?: For
868
1128
  if (isOptional && (!sigInfo || object_is_empty(sigInfo))) {
869
1129
  return { type: 'signature', signed: null }
870
1130
  }
871
- return signatureResponseValidator()(sigInfo)
1131
+ return signatureResponseValidator.validate()(sigInfo)
872
1132
  },
873
1133
 
874
1134
  // choiceInfo: { indexes: [], otherText?: string }
@@ -898,82 +1158,87 @@ export const FORM_FIELD_VALIDATORS_BY_TYPE: { [K in FormFieldType]: (value?: For
898
1158
  return parsed
899
1159
  },
900
1160
  }
901
- export const fieldsValidator: EscapeBuilder<Indexable<string | CustomField>> = (options={}) => build_validator(
902
- (fields: any) => {
903
- if (!is_object(fields)) throw new Error("Expecting an object")
1161
+ export const fieldsValidator: ValidatorDefinition<Indexable<string | CustomField>> = {
1162
+ validate: (options={}) => build_validator(
1163
+ (fields: any) => {
1164
+ if (!is_object(fields)) throw new Error("Expecting an object")
1165
+
1166
+ for (const k in fields) {
1167
+ if (DEFAULT_ENDUSER_FIELDS.includes(k)) throw new Error(`key ${k} conflicts with a built-in field.`)
1168
+ if (k.startsWith('_')) throw new Error("Fields that start with '_' are not allowed")
1169
+ if (is_whitespace(k)) {
1170
+ delete fields[k]
1171
+ continue
1172
+ }
904
1173
 
905
- for (const k in fields) {
906
- if (DEFAULT_ENDUSER_FIELDS.includes(k)) throw new Error(`key ${k} conflicts with a built-in field.`)
907
- if (k.startsWith('_')) throw new Error("Fields that start with '_' are not allowed")
908
- if (is_whitespace(k)) {
909
- delete fields[k]
910
- continue
1174
+ if (k.length > 32) throw new Error(`key ${k} is greater than 32 characters`)
1175
+
1176
+ const val = fields[k]
1177
+ if (typeof val === 'string') {
1178
+ if (val.length > 512) fields[k] = val.substring(0, 512)
1179
+ continue
1180
+ } else if (typeof val === 'number' || val === null || typeof val === 'boolean') {
1181
+ continue // nothing to restrict on number type yet
1182
+ } else if (typeof val === 'object') {
1183
+ if (JSON.stringify(val).length > 1024) throw new Error(`object value for key ${k} exceeds the maximum length of 1024 characters in string representation`)
1184
+ // previous restricted structure for fields object
1185
+ // try {
1186
+ // if (val.type && typeof val.type === 'string') { // form responses can be stored as custom fields (form responses is simple array)
1187
+ // FORM_FIELD_VALIDATORS_BY_TYPE[val.type as keyof typeof FORM_FIELD_VALIDATORS_BY_TYPE](val, undefined as never, undefined as never)
1188
+ // continue
1189
+ // }
1190
+ // if (val.length && typeof val.length === 'number') { // array of strings is ok too, (inclusive of multiple-choice responses)
1191
+ // if (val.find((s: any) => typeof s !== 'string') !== undefined) {
1192
+ // throw new Error('List must contain only strings')
1193
+ // }
1194
+ // continue
1195
+ // }
1196
+
1197
+ // if (val.value === undefined) throw new Error(`value field is undefined for key ${k}`)
1198
+ // if (JSON.stringify(val).length > 1024) throw new Error(`object value for key ${k} exceeds the maximum length of 1024 characters in string representation`)
1199
+
1200
+ // const escaped = { value: val.value } as Indexable // create new object to omit unrecognized fields
1201
+ // escaped.title = val.title // optional
1202
+ // escaped.description = val.description // optional
1203
+ // fields[k] = escaped
1204
+ // } catch(err) {
1205
+ // throw new Error(`object value is invalid JSON for key ${k}`)
1206
+ // }
1207
+ } else {
1208
+ throw new Error(`Expecting value to be a string or object but got ${typeof val} for key {k}`)
1209
+ }
911
1210
  }
912
1211
 
913
- if (k.length > 32) throw new Error(`key ${k} is greater than 32 characters`)
914
-
915
- const val = fields[k]
916
- if (typeof val === 'string') {
917
- if (val.length > 512) fields[k] = val.substring(0, 512)
918
- continue
919
- } else if (typeof val === 'number' || val === null || typeof val === 'boolean') {
920
- continue // nothing to restrict on number type yet
921
- } else if (typeof val === 'object') {
922
- if (JSON.stringify(val).length > 1024) throw new Error(`object value for key ${k} exceeds the maximum length of 1024 characters in string representation`)
923
- // previous restricted structure for fields object
924
- // try {
925
- // if (val.type && typeof val.type === 'string') { // form responses can be stored as custom fields (form responses is simple array)
926
- // FORM_FIELD_VALIDATORS_BY_TYPE[val.type as keyof typeof FORM_FIELD_VALIDATORS_BY_TYPE](val, undefined as never, undefined as never)
927
- // continue
928
- // }
929
- // if (val.length && typeof val.length === 'number') { // array of strings is ok too, (inclusive of multiple-choice responses)
930
- // if (val.find((s: any) => typeof s !== 'string') !== undefined) {
931
- // throw new Error('List must contain only strings')
932
- // }
933
- // continue
934
- // }
935
-
936
- // if (val.value === undefined) throw new Error(`value field is undefined for key ${k}`)
937
- // if (JSON.stringify(val).length > 1024) throw new Error(`object value for key ${k} exceeds the maximum length of 1024 characters in string representation`)
938
-
939
- // const escaped = { value: val.value } as Indexable // create new object to omit unrecognized fields
940
- // escaped.title = val.title // optional
941
- // escaped.description = val.description // optional
942
- // fields[k] = escaped
943
- // } catch(err) {
944
- // throw new Error(`object value is invalid JSON for key ${k}`)
945
- // }
946
- } else {
947
- throw new Error(`Expecting value to be a string or object but got ${typeof val} for key {k}`)
948
- }
949
- }
950
-
951
- return fields
952
- },
953
- { ...options, isObject: true, listOf: false }
954
- )
1212
+ return fields
1213
+ },
1214
+ { ...options, isObject: true, listOf: false }
1215
+ ),
1216
+ getExample: () => `{}`,
1217
+ getType: () => `{}`,
1218
+ }
955
1219
 
956
1220
  export const preferenceValidator = exactMatchValidator<Preference>(['email', 'sms', 'call', 'chat'])
957
1221
 
958
1222
  export const updateOptionsValidator = objectValidator<CustomUpdateOptions>({
959
- replaceObjectFields: booleanValidator({ isOptional: true }),
960
- })
1223
+ replaceObjectFields: booleanValidatorOptional,
1224
+ }, { isOptional: true })
961
1225
 
962
1226
  export const journeyStatePriorityValidator = exactMatchValidator<JourneyStatePriority>(["Disengaged", "N/A", "Engaged"])
963
1227
 
964
1228
  export const journeyStateValidator = objectValidator<JourneyState>({
965
- name: stringValidator100(),
966
- priority: journeyStatePriorityValidator(),
967
- description: stringValidator({ isOptional: true }),
968
- requiresFollowup: booleanValidator({ isOptional: true }),
1229
+ name: stringValidator100,
1230
+ priority: journeyStatePriorityValidator, // deprecated
1231
+ description: stringValidatorOptional, // deprecated
1232
+ requiresFollowup: booleanValidatorOptional, // deprecated
969
1233
  })
970
- export const journeyStateUpdateValidator = objectValidator<JourneyState>({
971
- name: stringValidator100({ isOptional: true }),
972
- priority: journeyStatePriorityValidator({ isOptional: true }),
973
- description: stringValidator({ isOptional: true }),
974
- requiresFollowup: booleanValidator({ isOptional: true }),
975
- })
976
- export const journeyStatesValidator = listValidator(journeyStateValidator())
1234
+ // deprecated
1235
+ // export const journeyStateUpdateValidator = objectValidator<JourneyState>({
1236
+ // name: stringValidator100({ isOptional: true }),
1237
+ // priority: journeyStatePriorityValidator({ isOptional: true }),
1238
+ // description: stringValidator({ isOptional: true }),
1239
+ // requiresFollowup: booleanValidator({ isOptional: true }),
1240
+ // })
1241
+ export const journeyStatesValidator = listValidator(journeyStateValidator)
977
1242
 
978
1243
  export const emailEncodingValidator = exactMatchValidator<EmailEncoding>(['', 'base64'])
979
1244
 
@@ -991,28 +1256,32 @@ export const validateIndexable = <V>(keyValidator: EscapeFunction<string | numbe
991
1256
  },
992
1257
  { ...o, isObject: true, listOf: false }
993
1258
  )
994
- export const indexableValidator = <V>(keyValidator: EscapeFunction<string>, valueValidator: EscapeFunction<V>): EscapeBuilder<{ [index: string]: V }> => (
995
- validateIndexable(keyValidator, valueValidator)
996
- )
997
- export const indexableNumberValidator = <V>(keyValidator: EscapeFunction<number>, valueValidator: EscapeFunction<V>): EscapeBuilder<{ [index: number]: V }> => (
998
- validateIndexable(keyValidator, valueValidator)
999
- )
1259
+ export const indexableValidator = <V>(keyValidator: ValidatorDefinition<string>, valueValidator: ValidatorDefinition<V>): ValidatorDefinition<{ [index: string]: V }> => ({
1260
+ validate: validateIndexable(keyValidator.validate(), valueValidator.validate()),
1261
+ getExample: () => `{ ${keyValidator.getExample()}: ${valueValidator.getExample()} }`,
1262
+ getType: () => `{ ${keyValidator.getType()}: ${valueValidator.getType()} }`,
1263
+ })
1264
+ export const indexableNumberValidator = <V>(keyValidator: ValidatorDefinition<number>, valueValidator: ValidatorDefinition<V>): ValidatorDefinition<{ [index: number]: V }> => ({
1265
+ validate: validateIndexable(keyValidator.validate(), valueValidator.validate()),
1266
+ getExample: () => `{ ${keyValidator.getExample()}: ${valueValidator.getExample()} }`,
1267
+ getType: () => `{ ${keyValidator.getType()}: ${valueValidator.getType()} }`,
1268
+ })
1000
1269
 
1001
1270
  export const rejectionWithMessage: EscapeBuilder<undefined> = o => build_validator(
1002
1271
  v => { throw new Error(o?.errorMessage || 'This field is not valid') },
1003
1272
  { ...o, isOptional: true, listOf: false, }
1004
1273
  )
1005
1274
 
1006
- export const numberToDateValidator = indexableNumberValidator(numberValidator(), dateValidator())
1007
- export const idStringToDateValidator = indexableValidator(mongoIdStringValidator(), dateValidator())
1275
+ export const numberToDateValidator = indexableNumberValidator(numberValidator, dateValidator)
1276
+ export const idStringToDateValidator = indexableValidator(mongoIdStringRequired, dateValidator)
1008
1277
 
1009
1278
  // todo: move preference to FIELD_TYPES with drop-down option in user-facing forms
1010
1279
  const FIELD_TYPES = ['string', 'number', 'email', 'phone', 'multiple_choice', 'file', 'signature']
1011
1280
  const VALIDATE_OPTIONS_FOR_FIELD_TYPES = {
1012
1281
  'multiple_choice': {
1013
- choices: listOfStringsValidator({ maxLength: 100, errorMessage: "Multiple choice options must be under 100 characters, and you must have at least one option." }),
1014
- radio: booleanValidator({ errorMessage: "radio must be a boolean" }),
1015
- other: booleanValidator({ isOptional: true, errorMessage: "other must be a boolean" }),
1282
+ choices: listOfStringsValidator,
1283
+ radio: booleanValidator,
1284
+ other: booleanValidatorOptional,
1016
1285
  REQUIRED: ['choices', 'radio'],
1017
1286
  }
1018
1287
  }
@@ -1055,18 +1324,18 @@ const isFormField = (f: JSONType, fieldOptions={ forUpdate: false }) => {
1055
1324
 
1056
1325
 
1057
1326
  if (!forUpdate && !field.type) throw `field.type is required` // fieldName otherwise given as 'field' in validation for every subfield
1058
- if (field.type) exactMatchValidator(FIELD_TYPES)(field.type)
1327
+ if (field.type) exactMatchValidator(FIELD_TYPES).validate(field.type)
1059
1328
 
1060
1329
  if (!forUpdate && !field.title) throw `field.title is required` // fieldName otherwise given as 'field' in validation for every subfield
1061
1330
  if (field.title) {
1062
- field.title = stringValidator({
1331
+ field.title = stringValidator.validate({
1063
1332
  maxLength: 100,
1064
1333
  errorMessage: "field title is required and must not exceed 100 characters"
1065
1334
  })(field.title)
1066
1335
  }
1067
1336
 
1068
1337
  if (!forUpdate || field.description !== undefined){ // don't overwrite description on update with ''
1069
- field.description = stringValidator({
1338
+ field.description = stringValidator.validate({
1070
1339
  isOptional: true,
1071
1340
  maxLength: 500,
1072
1341
  errorMessage: "field description must be under 500 characters"
@@ -1091,7 +1360,7 @@ const isFormField = (f: JSONType, fieldOptions={ forUpdate: false }) => {
1091
1360
  if (validators[k as keyof typeof validators] === undefined) {
1092
1361
  throw new Error(`Got unexpected option ${k} for field of type ${INTERNAL_NAME_TO_DISPLAY_FIELD[field.type as keyof typeof INTERNAL_NAME_TO_DISPLAY_FIELD] || 'Text'}`)
1093
1362
  }
1094
- field.options[k] = (validators[k as keyof typeof validators] as EscapeFunction)(field.options[k])
1363
+ field.options[k] = (validators[k as keyof typeof validators] as ValidatorDefinition).validate(field.options[k])
1095
1364
  }
1096
1365
  }
1097
1366
 
@@ -1114,59 +1383,60 @@ const isFormField = (f: JSONType, fieldOptions={ forUpdate: false }) => {
1114
1383
  // validate optional vs not at endpoint-level
1115
1384
  export const formResponseAnswerValidator = orValidator<{ [K in FormFieldType]: FormResponseValueAnswer & { type: K } } >({
1116
1385
  email: objectValidator<FormResponseAnswerEmail>({
1117
- type: exactMatchValidator(['email'])(),
1118
- value: emailValidator({ isOptional: true, emptyStringOk: true }),
1119
- })(),
1386
+ type: exactMatchValidator(['email']),
1387
+ value: emailValidatorOptional,
1388
+ }),
1120
1389
  number: objectValidator<FormResponseAnswerNumber>({
1121
- type: exactMatchValidator(['number'])(),
1122
- value: numberValidator({ isOptional: true, emptyStringOk: true }),
1123
- })(),
1390
+ type: exactMatchValidator(['number']),
1391
+ value: numberValidatorOptional,
1392
+ }),
1124
1393
  rating: objectValidator<FormResponseAnswerRating>({
1125
- type: exactMatchValidator(['rating'])(),
1126
- value: numberValidator({ isOptional: true, emptyStringOk: true }),
1127
- })(),
1394
+ type: exactMatchValidator(['rating']),
1395
+ value: numberValidatorOptional,
1396
+ }),
1128
1397
  phone: objectValidator<FormResponseAnswerPhone>({
1129
- type: exactMatchValidator(['phone'])(),
1130
- value: phoneValidator({ isOptional: true, emptyStringOk: true }),
1131
- })(),
1398
+ type: exactMatchValidator(['phone']),
1399
+ value: phoneValidatorOptional,
1400
+ }),
1132
1401
  string: objectValidator<FormResponseAnswerString>({
1133
- type: exactMatchValidator(['string'])(),
1134
- value: stringValidator5000({ isOptional: true, emptyStringOk: true }),
1135
- })(),
1402
+ type: exactMatchValidator(['string']),
1403
+ value: stringValidator5000Optional,
1404
+ }),
1136
1405
  date: objectValidator<FormResponseAnswerDate>({
1137
- type: exactMatchValidator(['date'])(),
1138
- value: dateValidator({ isOptional: true, emptyStringOk: true }),
1139
- })(),
1406
+ type: exactMatchValidator(['date']),
1407
+ value: dateValidatorOptional,
1408
+ }),
1140
1409
  file: objectValidator<FormResponseAnswerFile>({
1141
- type: exactMatchValidator(['file'])(),
1410
+ type: exactMatchValidator(['file']),
1142
1411
  value: objectValidator<FormResponseAnswerFileValue>({
1143
- name: stringValidator5000(),
1144
- secureName: stringValidator250(),
1145
- }, { emptyOk: false })({ isOptional: true }),
1146
- })(),
1412
+ name: stringValidator5000,
1413
+ secureName: stringValidator250,
1414
+ }, { emptyOk: false, isOptional: true }),
1415
+ }),
1147
1416
  multiple_choice: objectValidator<FormResponseAnswerMultipleChoice>({
1148
- type: exactMatchValidator(['multiple_choice'])(),
1149
- value: listOfStringsValidator({ isOptional: true, emptyListOk: true }),
1150
- })(),
1417
+ type: exactMatchValidator(['multiple_choice']),
1418
+ value: listOfStringsValidatorEmptyOk,
1419
+ }),
1151
1420
  ranking: objectValidator<FormResponseAnswerRanking>({
1152
- type: exactMatchValidator(['ranking'])(),
1153
- value: listOfStringsValidator({ isOptional: true, emptyListOk: true }),
1154
- })(),
1421
+ type: exactMatchValidator(['ranking']),
1422
+ value: listOfStringsValidatorOptionalOrEmptyOk,
1423
+ }),
1155
1424
  signature: objectValidator<FormResponseAnswerSignature>({
1156
- type: exactMatchValidator(['signature'])(),
1425
+ type: exactMatchValidator(['signature']),
1157
1426
  value: objectValidator<FormResponseAnswerSignatureValue>({
1158
- fullName: stringValidator250(),
1159
- signed: booleanValidator(),
1160
- }, { emptyOk: false })({ isOptional: true }),
1161
- })(),
1427
+ fullName: stringValidator250,
1428
+ signed: booleanValidator,
1429
+ }, { emptyOk: false, isOptional: true }),
1430
+ }),
1162
1431
  })
1163
1432
 
1164
1433
  export const formResponseValidator = objectValidator<FormResponseValue>({
1165
1434
  fieldId: mongoIdStringRequired,
1166
- fieldTitle: stringValidator5000(),
1167
- answer: formResponseAnswerValidator(),
1435
+ fieldTitle: stringValidator5000,
1436
+ fieldDescription: stringValidator5000Optional,
1437
+ answer: formResponseAnswerValidator,
1168
1438
  })
1169
- export const formResponsesValidator = listValidator(formResponseValidator())
1439
+ export const formResponsesValidator = listValidator(formResponseValidator)
1170
1440
 
1171
1441
  export const intakePhoneValidator = exactMatchValidator<'optional' | 'required'>(['optional', 'required'])
1172
1442
 
@@ -1228,10 +1498,10 @@ export const CUD = Object.keys(_CUD) as CUDType[]
1228
1498
  export const CUDStringValidator = exactMatchValidator<CUDType>(CUD)
1229
1499
 
1230
1500
  export const CUDValidator = objectValidator<CUDSubscription>({
1231
- create: booleanValidator({ isOptional: true }),
1232
- update: booleanValidator({ isOptional: true }),
1233
- delete: booleanValidator({ isOptional: true }),
1234
- })
1501
+ create: booleanValidatorOptional,
1502
+ update: booleanValidatorOptional,
1503
+ delete: booleanValidatorOptional,
1504
+ }, { isOptional: true })
1235
1505
 
1236
1506
  const _UNIT_OF_TIME: { [K in UnitOfTime]: any } = {
1237
1507
  Days: '',
@@ -1243,9 +1513,9 @@ export const UNITS_OF_TIME = Object.keys(_UNIT_OF_TIME) as UnitOfTime[]
1243
1513
 
1244
1514
  export const UnitOfTimeValidator = exactMatchValidator<UnitOfTime>(UNITS_OF_TIME)
1245
1515
 
1246
- const WebhookSubscriptionValidatorObject = {} as { [K in WebhookSupportedModel]: EscapeFunction<CUDSubscription> }
1516
+ const WebhookSubscriptionValidatorObject = {} as { [K in WebhookSupportedModel]: ValidatorDefinition<CUDSubscription> }
1247
1517
  for (const model in WEBHOOK_MODELS) {
1248
- WebhookSubscriptionValidatorObject[model as WebhookSupportedModel] = CUDValidator({ listOf: false, isOptional: true })
1518
+ WebhookSubscriptionValidatorObject[model as WebhookSupportedModel] = CUDValidator
1249
1519
  }
1250
1520
  export const WebhookSubscriptionValidator = objectValidator<{ [K in WebhookSupportedModel]: CUDSubscription}>(
1251
1521
  WebhookSubscriptionValidatorObject,
@@ -1255,15 +1525,15 @@ export const WebhookSubscriptionValidator = objectValidator<{ [K in WebhookSuppo
1255
1525
  export const sessionTypeValidator = exactMatchValidator<SessionType>(['user', 'enduser'])
1256
1526
 
1257
1527
  export const listOfDisplayNameInfo = listValidator(objectValidator<{ fname: string, lname: string, id: string }>({
1258
- fname: nameValidator(),
1259
- lname: nameValidator(),
1260
- id: listOfMongoIdStringValidator(),
1261
- })())
1528
+ fname: nameValidator,
1529
+ lname: nameValidator,
1530
+ id: listOfMongoIdStringValidator,
1531
+ }))
1262
1532
 
1263
1533
  export const attendeeInfoValidator = objectValidator<AttendeeInfo>({
1264
- AttendeeId: stringValidator(),
1265
- ExternalUserId: mongoIdStringValidator(),
1266
- JoinToken: stringValidator(),
1534
+ AttendeeId: stringValidator,
1535
+ ExternalUserId: mongoIdStringRequired,
1536
+ JoinToken: stringValidator,
1267
1537
  })
1268
1538
 
1269
1539
  export const attendeeValidator = objectValidator<{
@@ -1271,55 +1541,55 @@ export const attendeeValidator = objectValidator<{
1271
1541
  id: string,
1272
1542
  info: AttendeeInfo,
1273
1543
  }>({
1274
- type: sessionTypeValidator(),
1275
- id: mongoIdStringValidator(),
1276
- info: attendeeInfoValidator(),
1544
+ type: sessionTypeValidator,
1545
+ id: mongoIdStringRequired,
1546
+ info: attendeeInfoValidator,
1277
1547
  })
1278
- export const listOfAttendeesValidator = listValidator(attendeeValidator())
1548
+ export const listOfAttendeesValidator = listValidator(attendeeValidator)
1279
1549
  export const meetingInfoValidator = objectValidator<{ Meeting: MeetingInfo }>({
1280
- Meeting: objectAnyFieldsAnyValuesValidator(),
1550
+ Meeting: objectAnyFieldsAnyValuesValidator,
1281
1551
  })
1282
1552
 
1283
1553
  export const userIdentityValidator = objectValidator<{
1284
1554
  type: SessionType,
1285
1555
  id: string,
1286
1556
  }>({
1287
- type: sessionTypeValidator(),
1288
- id: mongoIdStringValidator(),
1557
+ type: sessionTypeValidator,
1558
+ id: mongoIdStringRequired,
1289
1559
  })
1290
- export const listOfUserIndentitiesValidator = listValidator(userIdentityValidator())
1560
+ export const listOfUserIndentitiesValidator = listValidator(userIdentityValidator)
1291
1561
 
1292
1562
  export const chatAttachmentValidator = objectValidator<ChatAttachment>({
1293
- type: exactMatchValidator<ChatAttachmentType>(['image', 'video', 'file'])(),
1294
- secureName: stringValidator250(),
1563
+ type: exactMatchValidator<ChatAttachmentType>(['image', 'video', 'file']),
1564
+ secureName: stringValidator250,
1295
1565
  })
1296
- export const listOfChatAttachmentsValidator = listValidatorEmptyOk(chatAttachmentValidator())
1566
+ export const listOfChatAttachmentsValidator = listValidatorEmptyOk(chatAttachmentValidator)
1297
1567
 
1298
1568
  export const meetingsListValidator = listValidator(objectValidator<{
1299
1569
  id: string,
1300
1570
  updatedAt: string,
1301
1571
  status: MeetingStatus,
1302
1572
  }>({
1303
- id: mongoIdStringValidator(),
1304
- updatedAt: stringValidator(),
1305
- status: meetingStatusValidator(),
1306
- })())
1573
+ id: mongoIdStringRequired,
1574
+ updatedAt: stringValidator,
1575
+ status: meetingStatusValidator,
1576
+ }))
1307
1577
 
1308
1578
  export const userDisplayInfoValidator = objectValidator<UserDisplayInfo>({
1309
- id: mongoIdRequired,
1310
- createdAt: dateValidator(),
1311
- avatar: stringValidator(),
1312
- fname: nameValidator(),
1313
- lname: nameValidator(),
1314
- lastActive: dateValidator(),
1315
- lastLogout: dateValidator(),
1316
- email: emailValidator(),
1579
+ id: mongoIdStringRequired,
1580
+ createdAt: dateValidator,
1581
+ avatar: stringValidator,
1582
+ fname: nameValidator,
1583
+ lname: nameValidator,
1584
+ lastActive: dateValidator,
1585
+ lastLogout: dateValidator,
1586
+ email: emailValidator,
1317
1587
  })
1318
- export const meetingDisplayInfoValidator = indexableValidator(mongoIdStringRequired, userDisplayInfoValidator())
1588
+ export const meetingDisplayInfoValidator = indexableValidator(mongoIdStringRequired, userDisplayInfoValidator)
1319
1589
 
1320
1590
  export const chatRoomUserInfoValidator = objectAnyFieldsValidator(objectValidator<ChatRoomUserInfo>({
1321
- unreadCount: nonNegNumberValidator(),
1322
- })())
1591
+ unreadCount: nonNegNumberValidator,
1592
+ }))
1323
1593
 
1324
1594
  const _AUTOMATION_ENDUSER_STATUS: { [K in AutomatedActionStatus]: any } = {
1325
1595
  active: '',
@@ -1360,94 +1630,100 @@ export const MESSAGE_TEMPLATE_MODES = Object.keys(_MESSAGE_TEMPLATE_MODES) as Me
1360
1630
  export const messageTemplateModeValidator = exactMatchValidator<MessageTemplateMode>(MESSAGE_TEMPLATE_MODES)
1361
1631
 
1362
1632
  const sharedReminderValidators = {
1363
- msBeforeStartTime: nonNegNumberValidator(),
1364
- didRemind: booleanValidator({ isOptional: true }),
1633
+ msBeforeStartTime: nonNegNumberValidator,
1634
+ didRemind: booleanValidatorOptional,
1365
1635
  }
1366
1636
 
1367
1637
  export const calendarEventReminderValidator = orValidator<{ [K in CalendarEventReminderType]: CalendarEventReminderInfoForType[K] } >({
1368
1638
  webhook: objectValidator<CalendarEventReminderInfoForType['webhook']>({
1369
- info: objectValidator<{}>({}, { emptyOk: true })({ isOptional: true }),
1370
- type: exactMatchValidator<'webhook'>(['webhook'])(),
1639
+ info: objectValidator<{}>({}, { emptyOk: true, isOptional: true }),
1640
+ type: exactMatchValidator<'webhook'>(['webhook']),
1371
1641
  ...sharedReminderValidators,
1372
- })(),
1642
+ }),
1373
1643
  'add-to-journey': objectValidator<CalendarEventReminderInfoForType['add-to-journey']>({
1374
1644
  info: objectValidator<CalendarEventReminderInfoForType['add-to-journey']['info']>({
1375
- journeyId: mongoIdRequired,
1376
- })(),
1377
- type: exactMatchValidator<'add-to-journey'>(['add-to-journey'])(),
1645
+ journeyId: mongoIdStringRequired,
1646
+ }),
1647
+ type: exactMatchValidator<'add-to-journey'>(['add-to-journey']),
1378
1648
  ...sharedReminderValidators,
1379
- })(),
1649
+ }),
1380
1650
  "enduser-notification": objectValidator<CalendarEventReminderInfoForType['enduser-notification']>({
1381
1651
  info: objectValidator<CalendarEventReminderNotificationInfo>({
1382
- templateId: mongoIdOptional,
1383
- }, { emptyOk: true })(),
1384
- type: exactMatchValidator<'enduser-notification'>(['enduser-notification'])(),
1652
+ templateId: mongoIdStringOptional,
1653
+ }, { emptyOk: true }),
1654
+ type: exactMatchValidator<'enduser-notification'>(['enduser-notification']),
1385
1655
  ...sharedReminderValidators,
1386
- })(),
1656
+ }),
1387
1657
  "user-notification": objectValidator<CalendarEventReminderInfoForType['user-notification']>({
1388
1658
  info: objectValidator<CalendarEventReminderNotificationInfo>({
1389
- templateId: mongoIdOptional,
1390
- }, { emptyOk: true })(),
1391
- type: exactMatchValidator<'user-notification'>(['user-notification'])(),
1659
+ templateId: mongoIdStringOptional,
1660
+ }, { emptyOk: true }),
1661
+ type: exactMatchValidator<'user-notification'>(['user-notification']),
1392
1662
  ...sharedReminderValidators,
1393
- })(),
1663
+ }),
1394
1664
  })
1395
- export const listOfCalendarEventRemindersValidator = listValidatorEmptyOk(calendarEventReminderValidator())
1665
+ export const listOfCalendarEventRemindersValidator = listValidatorEmptyOk(calendarEventReminderValidator)
1396
1666
 
1397
1667
  export const cancelConditionsValidator = listOfObjectsValidator<CancelCondition>({
1398
- type: exactMatchValidator(['formResponse'])(),
1668
+ type: exactMatchValidator(['formResponse']),
1399
1669
  info: objectValidator<FormSubmitCancellationConditionInfo>({
1400
1670
  automationStepId: mongoIdStringRequired,
1401
- }, { emptyOk: false })(),
1671
+ }, { emptyOk: false }),
1402
1672
  })
1673
+ export const cancelConditionsValidatorOptional = listValidatorOptionalOrEmptyOk(objectValidator<CancelCondition>({
1674
+ type: exactMatchValidator(['formResponse']),
1675
+ info: objectValidator<FormSubmitCancellationConditionInfo>({
1676
+ automationStepId: mongoIdStringRequired,
1677
+ }, { emptyOk: false }),
1678
+ }))
1403
1679
 
1404
1680
  const delayValidation = {
1405
1681
  automationStepId: mongoIdStringRequired,
1406
- delayInMS: nonNegNumberValidator(), // use 0 when no delay
1407
- delay: nonNegNumberValidator(), // for UI only
1408
- unit: UnitOfTimeValidator(), // for UI only
1409
- cancelConditions: cancelConditionsValidator({ isOptional: true, emptyListOk: true, })
1682
+ delayInMS: nonNegNumberValidator, // use 0 when no delay
1683
+ delay: nonNegNumberValidator, // for UI only
1684
+ unit: UnitOfTimeValidator, // for UI only
1685
+ cancelConditions: cancelConditionsValidatorOptional,
1410
1686
  }
1411
1687
 
1412
1688
  export const automationEventValidator = orValidator<{ [K in AutomationEventType]: AutomationEvent & { type: K } } >({
1413
1689
  formResponse: objectValidator<FormResponseAutomationEvent>({
1414
- type: exactMatchValidator(['formResponse'])(),
1690
+ type: exactMatchValidator(['formResponse']),
1415
1691
  info: objectValidator<WithAutomationStepId>({
1416
- automationStepId: mongoIdStringValidator(),
1417
- }, { emptyOk: false })(),
1418
- })(),
1692
+ automationStepId: mongoIdStringRequired,
1693
+ }, { emptyOk: false }),
1694
+ }),
1419
1695
  afterAction: objectValidator<AfterActionAutomationEvent>({
1420
- type: exactMatchValidator(['afterAction'])(),
1421
- info: objectValidator<AfterActionEventInfo>(delayValidation, { emptyOk: false })(),
1422
- })(),
1696
+ type: exactMatchValidator(['afterAction']),
1697
+ info: objectValidator<AfterActionEventInfo>(delayValidation, { emptyOk: false }),
1698
+ }),
1423
1699
  formUnsubmitted: objectValidator<FormUnsubmittedEvent>({
1424
- type: exactMatchValidator(['formUnsubmitted'])(),
1700
+ type: exactMatchValidator(['formUnsubmitted']),
1425
1701
  info: objectValidator<FormUnsubmittedEventInfo>({
1426
1702
  ...delayValidation,
1427
1703
  automationStepId: mongoIdStringRequired,
1428
- }, { emptyOk: false })(),
1429
- })(),
1704
+ }, { emptyOk: false }),
1705
+ }),
1430
1706
  onJourneyStart: objectValidator<OnJourneyStartAutomationEvent>({
1431
- type: exactMatchValidator(['onJourneyStart'])(),
1432
- info: objectValidator<{}>({ }, { emptyOk: true })(),
1433
- })(),
1707
+ type: exactMatchValidator(['onJourneyStart']),
1708
+ info: objectValidator<{}>({ }, { emptyOk: true }),
1709
+ }),
1434
1710
  ticketCompleted: objectValidator<TicketCompletedAutomationEvent>({
1435
- type: exactMatchValidator(['ticketCompleted'])(),
1711
+ type: exactMatchValidator(['ticketCompleted']),
1436
1712
  info: objectValidator<TicketCompletedEventInfo>({
1437
1713
  automationStepId: mongoIdStringRequired,
1438
- closedForReason: stringValidator({ isOptional: true }),
1439
- }, { emptyOk: false })(),
1440
- })(),
1714
+ closedForReason: stringValidatorOptional,
1715
+ }, { emptyOk: false }),
1716
+ }),
1441
1717
  })
1442
- export const automationEventsValidator = listValidatorEmptyOk(automationEventValidator())
1718
+ export const automationEventsValidator = listValidatorEmptyOk(automationEventValidator)
1443
1719
 
1444
1720
  export const automationConditionValidator = orValidator<{ [K in AutomationConditionType]: AutomationCondition & { type: K } } >({
1445
1721
  atJourneyState: objectValidator<AtJourneyStateAutomationCondition>({
1446
- type: exactMatchValidator(['atJourneyState'])(),
1447
- info: objectValidator<AutomationForJourneyAndState>({ state: stringValidator100(), journeyId: mongoIdStringRequired }, { emptyOk: false })(),
1448
- })(),
1722
+ type: exactMatchValidator(['atJourneyState']),
1723
+ info: objectValidator<AutomationForJourneyAndState>({ state: stringValidator100, journeyId: mongoIdStringRequired }, { emptyOk: false }),
1724
+ }),
1449
1725
  })
1450
- export const listOfAutomationConditionsValidator = listValidatorEmptyOk(automationConditionValidator())
1726
+ export const listOfAutomationConditionsValidator = listValidatorEmptyOk(automationConditionValidator)
1451
1727
 
1452
1728
  const _SEND_FORM_CHANNELS: { [K in SendFormChannel]: any } = {
1453
1729
  Email: '',
@@ -1455,63 +1731,64 @@ const _SEND_FORM_CHANNELS: { [K in SendFormChannel]: any } = {
1455
1731
  }
1456
1732
  export const SEND_FORM_CHANNELS = Object.keys(_SEND_FORM_CHANNELS) as SendFormChannel[]
1457
1733
  export const sendFormChannelValidator = exactMatchValidator<SendFormChannel>(SEND_FORM_CHANNELS)
1734
+ export const sendFormChannelValidatorOptional = exactMatchValidatorOptional<SendFormChannel>(SEND_FORM_CHANNELS)
1458
1735
 
1459
1736
  export const automationActionValidator = orValidator<{ [K in AutomationActionType]: AutomationAction & { type: K } } >({
1460
1737
  setEnduserStatus: objectValidator<SetEnduserStatusAutomationAction>({
1461
- type: exactMatchValidator(['setEnduserStatus'])(),
1462
- info: objectValidator<SetEnduserStatusInfo>({ status: stringValidator250() }, { emptyOk: false })(),
1463
- })(),
1738
+ type: exactMatchValidator(['setEnduserStatus']),
1739
+ info: objectValidator<SetEnduserStatusInfo>({ status: stringValidator250 }, { emptyOk: false }),
1740
+ }),
1464
1741
  sendEmail: objectValidator<SendEmailAutomationAction>({
1465
- type: exactMatchValidator(['sendEmail'])(),
1466
- info: objectValidator<AutomationForMessage>({ senderId: mongoIdStringValidator(), templateId: mongoIdStringValidator() }, { emptyOk: false })(),
1467
- })(),
1742
+ type: exactMatchValidator(['sendEmail']),
1743
+ info: objectValidator<AutomationForMessage>({ senderId: mongoIdStringRequired, templateId: mongoIdStringRequired }, { emptyOk: false }),
1744
+ }),
1468
1745
  sendSMS: objectValidator<SendSMSAutomationAction>({
1469
- type: exactMatchValidator(['sendSMS'])(),
1470
- info: objectValidator<AutomationForMessage>({ senderId: mongoIdStringValidator(), templateId: mongoIdStringValidator() }, { emptyOk: false })(),
1471
- })(),
1746
+ type: exactMatchValidator(['sendSMS']),
1747
+ info: objectValidator<AutomationForMessage>({ senderId: mongoIdStringRequired, templateId: mongoIdStringRequired }, { emptyOk: false }),
1748
+ }),
1472
1749
  sendForm: objectValidator<SendFormAutomationAction>({
1473
- type: exactMatchValidator(['sendForm'])(),
1750
+ type: exactMatchValidator(['sendForm']),
1474
1751
  info: objectValidator<AutomationForFormRequest>({
1475
- senderId: mongoIdStringValidator(),
1476
- formId: mongoIdStringValidator(),
1477
- channel: sendFormChannelValidator({ isOptional: true }),
1478
- }, { emptyOk: false })(),
1479
- })(),
1752
+ senderId: mongoIdStringRequired,
1753
+ formId: mongoIdStringRequired,
1754
+ channel: sendFormChannelValidatorOptional,
1755
+ }, { emptyOk: false }),
1756
+ }),
1480
1757
  createTicket: objectValidator<CreateTicketAutomationAction>({
1481
- type: exactMatchValidator(['createTicket'])(),
1758
+ type: exactMatchValidator(['createTicket']),
1482
1759
  info: objectValidator<CreateTicketActionInfo>({
1483
- title: stringValidator({ isOptional: false }),
1760
+ title: stringValidatorOptional,
1484
1761
  assignmentStrategy: orValidator<{ [K in CreateTicketAssignmentStrategyType ]: CreateTicketAssignmentStrategy & { type: K } }>({
1485
1762
  'care-team-random': objectValidator<CreateTicketAssignmentStrategy>({
1486
- type: exactMatchValidator<CreateTicketAssignmentStrategyType>(['care-team-random'])(),
1487
- info: objectValidator<object>({}, { emptyOk: true })(),
1488
- })()
1489
- })(),
1490
- closeReasons: listOfStringsValidator({ isOptional: true, emptyListOk: true }),
1763
+ type: exactMatchValidator<CreateTicketAssignmentStrategyType>(['care-team-random']),
1764
+ info: objectValidator<object>({}, { emptyOk: true }),
1765
+ })
1766
+ }),
1767
+ closeReasons: listOfStringsValidatorOptionalOrEmptyOk,
1491
1768
  defaultAssignee: mongoIdStringRequired,
1492
- }, { emptyOk: false })(),
1493
- })(),
1769
+ }, { emptyOk: false }),
1770
+ }),
1494
1771
  sendWebhook: objectValidator<SendWebhookAutomationAction>({
1495
- type: exactMatchValidator(['sendWebhook'])(),
1496
- info: objectValidator<AutomationForWebhook>({ message: stringValidator5000() }, { emptyOk: false })(),
1497
- })(),
1772
+ type: exactMatchValidator(['sendWebhook']),
1773
+ info: objectValidator<AutomationForWebhook>({ message: stringValidator5000 }, { emptyOk: false }),
1774
+ }),
1498
1775
  })
1499
1776
 
1500
1777
  export const relatedRecordValidator = objectValidator<RelatedRecord>({
1501
- type: stringValidator100(),
1502
- id: mongoIdStringValidator(),
1778
+ type: stringValidator100,
1779
+ id: mongoIdStringRequired,
1503
1780
  creator: mongoIdStringOptional,
1504
1781
  })
1505
- export const listOfRelatedRecordsValidator = listValidatorEmptyOk(relatedRecordValidator())
1782
+ export const listOfRelatedRecordsValidator = listValidatorEmptyOk(relatedRecordValidator)
1506
1783
 
1507
1784
  export const searchOptionsValidator = objectValidator<SearchOptions>({
1508
- query: stringValidator100(),
1785
+ query: stringValidator100,
1509
1786
  })
1510
1787
 
1511
1788
  export const notificationPreferenceValidator = objectValidator<NotificationPreference>({
1512
- email: booleanValidator({ isOptional: true }),
1789
+ email: booleanValidatorOptional,
1513
1790
  })
1514
- export const notificationPreferencesValidator = objectAnyFieldsValidator(notificationPreferenceValidator())
1791
+ export const notificationPreferencesValidator = objectAnyFieldsValidator(notificationPreferenceValidator)
1515
1792
 
1516
1793
  export const FHIRObservationCategoryValidator = exactMatchValidator<ObservationCategory>(['vital-signs'])
1517
1794
 
@@ -1529,42 +1806,42 @@ export const FHIR_OBSERVATION_STATUS_CODES = Object.keys(_FHIR_OBSERVATION_STATU
1529
1806
  export const FHIRObservationStatusCodeValidator = exactMatchValidator<ObservationStatusCode>(FHIR_OBSERVATION_STATUS_CODES)
1530
1807
 
1531
1808
  export const FHIRObservationValueValidator = objectValidator<ObservationValue>({
1532
- unit: stringValidator(),
1533
- value: numberValidator(),
1809
+ unit: stringValidator,
1810
+ value: numberValidator,
1534
1811
  })
1535
1812
 
1536
1813
  export const previousFormFieldValidator = orValidator<{ [K in PreviousFormFieldType]: PreviousFormField & { type: K } } >({
1537
1814
  root: objectValidator<PreviousFormFieldRoot>({
1538
- type: exactMatchValidator(['root'])(),
1539
- info: objectValidator<{}>({}, { emptyOk: true })(),
1540
- })(),
1815
+ type: exactMatchValidator(['root']),
1816
+ info: objectValidator<{}>({}, { emptyOk: true }),
1817
+ }),
1541
1818
  "after": objectValidator<PreviousFormFieldAfter>({
1542
- type: exactMatchValidator(['after'])(),
1543
- info: objectValidator<PreviousFormFieldAfterInfo>({ fieldId: mongoIdStringRequired }, { emptyOk: false })(),
1544
- })(),
1819
+ type: exactMatchValidator(['after']),
1820
+ info: objectValidator<PreviousFormFieldAfterInfo>({ fieldId: mongoIdStringRequired }, { emptyOk: false }),
1821
+ }),
1545
1822
  "previousEquals": objectValidator<PreviousFormFieldEquals>({
1546
- type: exactMatchValidator(['previousEquals'])(),
1823
+ type: exactMatchValidator(['previousEquals']),
1547
1824
  info: objectValidator<PreviousFormFieldEqualsInfo>({
1548
1825
  fieldId: mongoIdStringRequired,
1549
- equals: stringValidator250(),
1550
- }, { emptyOk: false })(),
1551
- })(),
1826
+ equals: stringValidator250,
1827
+ }, { emptyOk: false }),
1828
+ }),
1552
1829
  })
1553
- export const previousFormFieldsValidator = listValidatorEmptyOk(previousFormFieldValidator())
1830
+ export const previousFormFieldsValidator = listValidatorEmptyOk(previousFormFieldValidator)
1554
1831
 
1555
1832
  export const portalSettingsValidator = objectValidator<PortalSettings>({
1556
1833
 
1557
1834
  })
1558
1835
 
1559
1836
  export const organizationThemeValidator = objectValidator<OrganizationTheme>({
1560
- logoURL: stringValidator250({ isOptional: true }), // these don't really need to be optional
1561
- themeColor: stringValidator250({ isOptional: true }), // these don't really need to be optional
1562
- name: stringValidator250(),
1563
- subdomain: stringValidator250(),
1564
- businessId: mongoIdRequired,
1565
- faviconURL: stringValidator250(),
1566
- customPortalURL: stringValidator250(),
1567
- portalSettings: portalSettingsValidator(),
1837
+ logoURL: stringValidatorOptional, // these don't really need to be optional
1838
+ themeColor: stringValidatorOptional, // these don't really need to be optional
1839
+ name: stringValidator250,
1840
+ subdomain: stringValidator250,
1841
+ businessId: mongoIdStringRequired,
1842
+ faviconURL: stringValidator250,
1843
+ customPortalURL: stringValidator250,
1844
+ portalSettings: portalSettingsValidator,
1568
1845
  })
1569
1846
 
1570
1847
  const _MANAGED_CONTENT_RECORD_TYPES: { [K in ManagedContentRecordType]: any } = {
@@ -1575,104 +1852,110 @@ const _MANAGED_CONTENT_RECORD_TYPES: { [K in ManagedContentRecordType]: any } =
1575
1852
  export const MANAGED_CONTENT_RECORD_TYPES = Object.keys(_MANAGED_CONTENT_RECORD_TYPES) as ManagedContentRecordType[]
1576
1853
  export const managedContentRecordTypeValidator = exactMatchValidator<ManagedContentRecordType>(MANAGED_CONTENT_RECORD_TYPES)
1577
1854
 
1578
- export const passwordValidator: EscapeBuilder<string> = (o) => build_validator((password) => {
1579
- if (typeof password !== 'string') {
1580
- throw new Error("Password must be a string")
1581
- }
1582
- if (password.length < 8) {
1583
- throw new Error("Password must be at least 8 characters long")
1584
- }
1855
+ export const passwordValidator: ValidatorDefinition<string> = {
1856
+ getExample: getExampleString,
1857
+ getType: getTypeString,
1858
+ validate: (
1859
+ (o) => build_validator((password) => {
1860
+ if (typeof password !== 'string') {
1861
+ throw new Error("Password must be a string")
1862
+ }
1863
+ if (password.length < 8) {
1864
+ throw new Error("Password must be at least 8 characters long")
1865
+ }
1585
1866
 
1586
- if (
1587
- (password.match(/[a-z]/g)?.length ?? 0) < 1 // 1 lowercase
1588
- || (
1589
- (password.match(/[A-Z]/g)?.length ?? 0) < 1 // 1 uppercase
1590
- && (password.match(/[0-9]/g)?.length ?? 0) < 1 // 1 number
1591
- && (password.match(/[^a-zA-Z0-9]/g)?.length ?? 0) < 1 // 1 special character
1592
- )
1593
- ) {
1594
- console.error('bad password regex')
1595
- throw new Error('Password must included 1 uppercase letter, 1 number, or 1 symbol')
1596
- }
1867
+ if (
1868
+ (password.match(/[a-z]/g)?.length ?? 0) < 1 // 1 lowercase
1869
+ || (
1870
+ (password.match(/[A-Z]/g)?.length ?? 0) < 1 // 1 uppercase
1871
+ && (password.match(/[0-9]/g)?.length ?? 0) < 1 // 1 number
1872
+ && (password.match(/[^a-zA-Z0-9]/g)?.length ?? 0) < 1 // 1 special character
1873
+ )
1874
+ ) {
1875
+ console.error('bad password regex')
1876
+ throw new Error('Password must included 1 uppercase letter, 1 number, or 1 symbol')
1877
+ }
1597
1878
 
1598
- return password
1599
- }, { ...o, listOf: false, emptyStringOk: false, })
1879
+ return password
1880
+ }, { ...o, listOf: false, emptyStringOk: false, })
1881
+ ),
1882
+ }
1600
1883
 
1601
1884
  export const flowchartUIValidator = objectValidator<FlowchartUI>({
1602
- x: numberValidator(),
1603
- y: numberValidator(),
1885
+ x: numberValidator,
1886
+ y: numberValidator,
1604
1887
  }, { emptyOk: true })
1605
1888
 
1606
1889
 
1607
1890
 
1608
1891
  export const integrationAuthenticationsValidator = objectValidator<IntegrationAuthentication>({
1609
- type: exactMatchValidator(['oauth2'])(),
1892
+ type: exactMatchValidator(['oauth2']),
1610
1893
  info: objectValidator<OAuth2AuthenticationFields>({
1611
- access_token: stringValidator250(),
1612
- refresh_token: stringValidator250(),
1613
- scope: stringValidator5000(),
1614
- expiry_date: nonNegNumberValidator(),
1615
- token_type: exactMatchValidator<'Bearer'>(['Bearer'])(),
1616
- state: stringValidator250({ isOptional: true }),
1617
- email: emailValidator({ isOptional: true }),
1618
- })(),
1894
+ access_token: stringValidator250,
1895
+ refresh_token: stringValidator250,
1896
+ scope: stringValidator5000,
1897
+ expiry_date: nonNegNumberValidator,
1898
+ token_type: exactMatchValidator<'Bearer'>(['Bearer']),
1899
+ state: stringValidatorOptional,
1900
+ email: emailValidatorOptional,
1901
+ }),
1619
1902
  })
1620
1903
 
1621
1904
 
1622
1905
  export const formFieldOptionsValidator = objectValidator<FormFieldOptions>({
1623
- choices: listOfStringsValidator({ isOptional: true }),
1624
- from: numberValidator({ isOptional: true }),
1625
- to: numberValidator({ isOptional: true }),
1626
- other: stringValidator250({ isOptional: true }),
1627
- radio: booleanValidator({ isOptional: true }),
1906
+ choices: listOfStringsValidatorOptionalOrEmptyOk,
1907
+ from: numberValidatorOptional,
1908
+ to: numberValidatorOptional,
1909
+ other: stringValidatorOptional,
1910
+ radio: booleanValidatorOptional,
1628
1911
  })
1629
1912
 
1630
1913
  export const blockValidator = orValidator<{ [K in BlockType]: Block & { type: K } } >({
1631
1914
  h1: objectValidator<BlockContentH1>({
1632
- type: exactMatchValidator(['h1'])(),
1915
+ type: exactMatchValidator(['h1']),
1633
1916
  info: objectValidator<BlockContentH1['info']>({
1634
- text: stringValidator5000({ emptyStringOk: true }),
1635
- })(),
1636
- })(),
1917
+ text: stringValidator5000EmptyOkay,
1918
+ }),
1919
+ }),
1637
1920
  h2: objectValidator<BlockContentH2>({
1638
- type: exactMatchValidator(['h2'])(),
1921
+ type: exactMatchValidator(['h2']),
1639
1922
  info: objectValidator<BlockContentH1['info']>({
1640
- text: stringValidator5000({ emptyStringOk: true }),
1641
- })(),
1642
- })(),
1923
+ text: stringValidator5000EmptyOkay,
1924
+ }),
1925
+ }),
1643
1926
  html: objectValidator<BlockContentHTML>({
1644
- type: exactMatchValidator(['html'])(),
1927
+ type: exactMatchValidator(['html']),
1645
1928
  info: objectValidator<BlockContentHTML['info']>({
1646
- html: stringValidator25000({ emptyStringOk: true }),
1647
- })(),
1648
- })(),
1929
+ html: stringValidator25000EmptyOkay,
1930
+ }),
1931
+ }),
1649
1932
  image: objectValidator<BlockContentImage>({
1650
- type: exactMatchValidator(['image'])(),
1933
+ type: exactMatchValidator(['image']),
1651
1934
  info: objectValidator<BlockContentImage['info']>({
1652
- link: stringValidator5000({ emptyStringOk: true }),
1653
- name: stringValidator250({ isOptional: true, emptyStringOk: true }),
1654
- height: nonNegNumberValidator({ isOptional: true }),
1655
- width: nonNegNumberValidator({ isOptional: true }),
1656
- })(),
1657
- })(),
1935
+ link: stringValidator5000EmptyOkay,
1936
+ name: stringValidatorOptional,
1937
+ height: numberValidatorOptional,
1938
+ width: numberValidatorOptional,
1939
+ }),
1940
+ }),
1658
1941
  pdf: objectValidator<BlockContentPDF>({
1659
- type: exactMatchValidator(['pdf'])(),
1942
+ type: exactMatchValidator(['pdf']),
1660
1943
  info: objectValidator<BlockContentPDF['info']>({
1661
- link: stringValidator5000({ emptyStringOk: true }),
1662
- name: stringValidator250({ isOptional: true, emptyStringOk: true }),
1663
- height: nonNegNumberValidator({ isOptional: true }),
1664
- width: nonNegNumberValidator({ isOptional: true }),
1665
- })(),
1666
- })(),
1944
+ link: stringValidator5000EmptyOkay,
1945
+ name: stringValidatorOptional,
1946
+ height: numberValidatorOptional,
1947
+ width: numberValidatorOptional,
1948
+ }),
1949
+ }),
1667
1950
  youtube: objectValidator<BlockContentYoutube>({
1668
- type: exactMatchValidator(['youtube'])(),
1951
+ type: exactMatchValidator(['youtube']),
1669
1952
  info: objectValidator<BlockContentYoutube['info']>({
1670
- link: stringValidator5000({ emptyStringOk: true }),
1671
- name: stringValidator250({ isOptional: true, emptyStringOk: true }),
1672
- height: nonNegNumberValidator({ isOptional: true }),
1673
- width: nonNegNumberValidator({ isOptional: true }),
1674
- })(),
1675
- })(),
1953
+ link: stringValidator5000EmptyOkay,
1954
+ name: stringValidatorOptional,
1955
+ height: numberValidatorOptional,
1956
+ width: numberValidatorOptional,
1957
+ }),
1958
+ }),
1676
1959
  })
1677
1960
 
1678
1961
  const _BLOCK_TYPES: { [K in BlockType]: any } = {
@@ -1687,7 +1970,7 @@ export const BLOCK_TYPES = Object.keys(_BLOCK_TYPES) as BlockType[]
1687
1970
  export const blockTypeValidator = exactMatchValidator<BlockType>(BLOCK_TYPES)
1688
1971
  export const is_block_type = (type: any): type is BlockType => BLOCK_TYPES.includes(type)
1689
1972
 
1690
- export const blocksValidator = listValidatorEmptyOk(blockValidator())
1973
+ export const blocksValidator = listValidatorEmptyOk(blockValidator)
1691
1974
 
1692
1975
 
1693
1976
  const _DATABASE_RECORD_FIELD_TYPES: { [K in DatabaseRecordFieldType]: any } = {
@@ -1717,33 +2000,33 @@ export const is_database_record_field_type = (type: any): type is DatabaseRecord
1717
2000
 
1718
2001
  // structure as above instead if need unique label or additional config based on type
1719
2002
  export const databaseFieldValidator = objectValidator<DatabaseRecordField>({
1720
- type: databaseRecordFieldTypeValidator(),
1721
- label: stringValidator250(),
2003
+ type: databaseRecordFieldTypeValidator,
2004
+ label: stringValidator250,
1722
2005
  })
1723
- export const databaseFieldsValidator = listValidator(databaseFieldValidator())
2006
+ export const databaseFieldsValidator = listValidator(databaseFieldValidator)
1724
2007
 
1725
2008
 
1726
2009
  export const databaseRecordValueValidator = orValidator<{ [K in DatabaseRecordFieldType]: DatabaseRecordValues[K] } >({
1727
2010
  string: objectValidator<DatabaseRecordValues['string']>({
1728
- type: exactMatchValidator(['string'])(),
1729
- value: stringValidator1000(),
1730
- })(),
2011
+ type: exactMatchValidator(['string']),
2012
+ value: stringValidator1000,
2013
+ }),
1731
2014
  'string-long': objectValidator<DatabaseRecordValues['string-long']>({
1732
- type: exactMatchValidator(['string-long'])(),
1733
- value: stringValidator5000(),
1734
- })(),
2015
+ type: exactMatchValidator(['string-long']),
2016
+ value: stringValidator5000,
2017
+ }),
1735
2018
  'number': objectValidator<DatabaseRecordValues['number']>({
1736
- type: exactMatchValidator(['number'])(),
1737
- value: numberValidator(),
1738
- })(),
2019
+ type: exactMatchValidator(['number']),
2020
+ value: numberValidator,
2021
+ }),
1739
2022
  })
1740
- export const databaseRecordValuesValidator = listValidator(databaseRecordValueValidator())
2023
+ export const databaseRecordValuesValidator = listValidator(databaseRecordValueValidator)
1741
2024
 
1742
2025
  export const organizationAccessValidator = objectValidator<OrganizationAccess>({
1743
- create: booleanValidator({ isOptional: true }),
1744
- update: booleanValidator({ isOptional: true }),
1745
- read: booleanValidator({ isOptional: true }),
1746
- delete: booleanValidator({ isOptional: true }),
2026
+ create: booleanValidatorOptional,
2027
+ update: booleanValidatorOptional,
2028
+ read: booleanValidatorOptional,
2029
+ delete: booleanValidatorOptional,
1747
2030
  })
1748
2031
 
1749
2032
  const _PORTAL_PAGES: { [K in PortalPage]: any } = {
@@ -1757,36 +2040,43 @@ const _PORTAL_PAGES: { [K in PortalPage]: any } = {
1757
2040
  export const PORTAL_PAGES = Object.keys(_PORTAL_PAGES) as PortalPage[]
1758
2041
  export const portalPageValidator = exactMatchValidator<PortalPage>(PORTAL_PAGES)
1759
2042
 
2043
+ const _FORM_TYPES: { [K in FormType]: any } = {
2044
+ note: true,
2045
+ enduserFacing: true,
2046
+ }
2047
+ export const FORM_TYPES = Object.keys(_FORM_TYPES) as FormType[]
2048
+ export const formTypeValidator = exactMatchValidator<FormType>(FORM_TYPES)
2049
+
1760
2050
 
1761
2051
  export const portalBlockValidator = orValidator<{ [K in PortalBlockType]: PortalBlockForType[K] } >({
1762
2052
  carePlan: objectValidator<PortalBlockForType['carePlan']>({
1763
- type: exactMatchValidator(['carePlan'])(),
1764
- info: objectValidator<PortalBlockForType['carePlan']['info']>({}, { emptyOk: true })()
1765
- })(),
2053
+ type: exactMatchValidator(['carePlan']),
2054
+ info: objectValidator<PortalBlockForType['carePlan']['info']>({}, { emptyOk: true })
2055
+ }),
1766
2056
  education: objectValidator<PortalBlockForType['education']>({
1767
- type: exactMatchValidator(['education'])(),
1768
- info: objectValidator<PortalBlockForType['education']['info']>({}, { emptyOk: true })()
1769
- })(),
2057
+ type: exactMatchValidator(['education']),
2058
+ info: objectValidator<PortalBlockForType['education']['info']>({}, { emptyOk: true })
2059
+ }),
1770
2060
  careTeam: objectValidator<PortalBlockForType['careTeam']>({
1771
- type: exactMatchValidator(['careTeam'])(),
2061
+ type: exactMatchValidator(['careTeam']),
1772
2062
  info: objectValidator<PortalBlockForType['careTeam']['info']>({
1773
- title: stringValidator(),
2063
+ title: stringValidator,
1774
2064
  // members: listValidatorEmptyOk(
1775
2065
  // objectValidator<CareTeamMemberPortalCustomizationInfo>({
1776
2066
  // title: stringValidator(),
1777
2067
  // role: stringValidator({ isOptional: true }),
1778
2068
  // })()
1779
2069
  // )()
1780
- })()
1781
- })(),
2070
+ })
2071
+ }),
1782
2072
  text: objectValidator<PortalBlockForType['text']>({
1783
- type: exactMatchValidator(['text'])(),
2073
+ type: exactMatchValidator(['text']),
1784
2074
  info: objectValidator<PortalBlockForType['text']['info']>({
1785
- text: stringValidator5000(),
1786
- })()
1787
- })(),
2075
+ text: stringValidator5000,
2076
+ })
2077
+ }),
1788
2078
  })
1789
- export const portalBlocksValidator = listValidatorEmptyOk(portalBlockValidator())
2079
+ export const portalBlocksValidator = listValidatorEmptyOk(portalBlockValidator)
1790
2080
 
1791
2081
  const _PORTAL_BLOCK_TYPES: { [K in PortalBlockType]: any } = {
1792
2082
  carePlan: '',
@@ -1802,14 +2092,14 @@ export const enduserTaskForEventValidator = objectValidator<EnduserTaskForEvent>
1802
2092
  id: mongoIdStringRequired,
1803
2093
  enduserId: mongoIdStringRequired,
1804
2094
  })
1805
- export const enduserTasksForEventValidator = listValidatorEmptyOk(enduserTaskForEventValidator())
2095
+ export const enduserTasksForEventValidator = listValidatorEmptyOk(enduserTaskForEventValidator)
1806
2096
 
1807
2097
  export const enduserFormResponseForEventValidator = objectValidator<EnduserFormResponseForEvent>({
1808
2098
  enduserId: mongoIdStringRequired,
1809
2099
  formId: mongoIdStringRequired,
1810
- accessCode: stringValidator1000(),
2100
+ accessCode: stringValidator1000,
1811
2101
  })
1812
- export const enduserFormResponsesForEventValidator = listValidatorEmptyOk(enduserFormResponseForEventValidator())
2102
+ export const enduserFormResponsesForEventValidator = listValidatorEmptyOk(enduserFormResponseForEventValidator)
1813
2103
 
1814
2104
  export const VALID_STATES: string[] = [
1815
2105
  "AK",
@@ -1873,23 +2163,24 @@ export const VALID_STATES: string[] = [
1873
2163
  export const stateValidator = exactMatchValidator(VALID_STATES)
1874
2164
 
1875
2165
  export const stateCredentialValidator = objectValidator<StateCredentialInfo>({
1876
- expiresAt: dateValidator({ isOptional: true }),
1877
- state: stateValidator(),
2166
+ expiresAt: dateValidatorOptional,
2167
+ state: stateValidator,
1878
2168
  })
1879
- export const stateCredentialsValidator = listValidatorEmptyOk(stateCredentialValidator())
2169
+ export const stateCredentialsValidator = listValidatorEmptyOk(stateCredentialValidator)
1880
2170
 
1881
2171
  export const availabilityBlockValidator = objectValidator<AvailabilityBlock>({
1882
- durationInMinutes: nonNegNumberValidator(),
1883
- startTimeInMS: nonNegNumberValidator(),
2172
+ durationInMinutes: nonNegNumberValidator,
2173
+ startTimeInMS: nonNegNumberValidator,
1884
2174
  userId: mongoIdStringRequired,
1885
2175
  })
1886
- export const availabilityBlocksValidator = listValidatorEmptyOk(availabilityBlockValidator())
2176
+ export const availabilityBlocksValidator = listValidatorEmptyOk(availabilityBlockValidator)
1887
2177
 
1888
2178
  export const weeklyAvailabilityValidator = objectValidator<WeeklyAvailability>({
1889
- dayOfWeekStartingSundayIndexedByZero: nonNegNumberValidator(),
1890
- endTimeInMinutes: nonNegNumberValidator(),
1891
- startTimeInMinutes: nonNegNumberValidator(),
2179
+ dayOfWeekStartingSundayIndexedByZero: nonNegNumberValidator,
2180
+ endTimeInMinutes: nonNegNumberValidator,
2181
+ startTimeInMinutes: nonNegNumberValidator,
1892
2182
  })
1893
- export const weeklyAvailabilitiesValidator = listValidatorEmptyOk(weeklyAvailabilityValidator())
2183
+ export const weeklyAvailabilitiesValidator = listValidatorEmptyOk(weeklyAvailabilityValidator)
2184
+
2185
+ export const timezoneValidator = exactMatchValidator<Timezone>(Object.keys(TIMEZONES) as Timezone[])
1894
2186
 
1895
- export const timezoneValidator = exactMatchValidator<Timezone>(Object.keys(TIMEZONES) as Timezone[])