@tellescope/validation 1.3.48 → 1.4.1

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