-
Notifications
You must be signed in to change notification settings - Fork 88
/
a25_validate.js
994 lines (899 loc) · 38.7 KB
/
a25_validate.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
/**
@module breeze
**/
var Validator = (function () {
var INT16_MIN = -32768;
var INT16_MAX = 32767;
var INT32_MIN = -2147483648;
var INT32_MAX = 2147483647;
var BYTE_MIN = 0;
var BYTE_MAX = 255;
// add common props and methods for every validator 'context' here.
var rootContext = {
displayName: function (context) {
if (context.property) {
return context.property.resolveProperty("displayName") || context.propertyName || context.property.name;
} else {
return "Value";
}
}
};
/**
Instances of the Validator class provide the logic to validate another object and provide a description of any errors
encountered during the validation process. They are typically associated with a 'validators' property on the following types: {{#crossLink "EntityType"}}{{/crossLink}},
{{#crossLink "DataProperty"}}{{/crossLink}} or {{#crossLink "NavigationProperty"}}{{/crossLink}}.
A number of property level validators are registered automatically, i.e added to each DataProperty.validators property
based on {{#crossLink "DataProperty"}}{{/crossLink}} metadata. For example,
- DataProperty.dataType -> one of the 'dataType' validator methods such as Validator.int64, Validator.date, Validator.bool etc.
- DataProperty.maxLength -> Validator.maxLength
- DataProperty.isNullable -> Validator.required (if not nullable)
@class Validator
**/
/**
Validator constructor - This method is used to create create custom validations. Several
basic "Validator" construction methods are also provided as static methods to this class. These methods
provide a simpler syntax for creating basic validations.
Many of these stock validators are inspired by and implemented to conform to the validators defined at
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx
Sometimes a custom validator will be required.
@example
Most validators will be 'property' level validators, like this.
@example
// v is this function is the value to be validated, in this case a "country" string.
var valFn = function (v) {
if (v == null) return true;
return (core.stringStartsWith(v, "US"));
};
var countryValidator = new Validator("countryIsUS", valFn, {
displayName: "Country",
messageTemplate: "'%displayName%' must start with 'US'"
});
// Now plug it into Breeze.
// Assume em1 is a preexisting EntityManager.
var custType = metadataStore.getEntityType("Customer");
var countryProp = custType.getProperty("Country");
// Note that validator is added to a 'DataProperty' validators collection.
prop.validators.push(countryValidator);
Entity level validators are also possible
@example
function isValidZipCode(value) {
var re = /^\d{5}([\-]\d{4})?$/;
return (re.test(value));
}
// v in this case will be a Customer entity
var valFn = function (v) {
// This validator only validates US Zip Codes.
if ( v.getProperty("Country") === "USA") {
var postalCode = v.getProperty("PostalCode");
return isValidZipCode(postalCode);
}
return true;
};
var zipCodeValidator = new Validator("zipCodeValidator", valFn,
{ messageTemplate: "For the US, this is not a valid PostalCode" });
// Now plug it into Breeze.
// Assume em1 is a preexisting EntityManager.
var custType = em1.metadataStore.getEntityType("Customer");
// Note that validator is added to an 'EntityType' validators collection.
custType.validators.push(zipCodeValidator);
What is commonly needed is a way of creating a parameterized function that will itself
return a new Validator. This requires the use of a 'context' object.
@example
// create a function that will take in a config object
// and will return a validator
var numericRangeValidator = function(context) {
var valFn = function(v, ctx) {
if (v == null) return true;
if (typeof(v) !== "number") return false;
if (ctx.min != null && v < ctx.min) return false;
if (ctx.max != null && v > ctx.max) return false;
return true;
};
// The last parameter below is the 'context' object that will be passed into the 'ctx' parameter above
// when this validator executes. Several other properties, such as displayName will get added to this object as well.
return new Validator("numericRange", valFn, {
messageTemplate: "'%displayName%' must be a number between the values of %min% and %max%",
min: context.min,
max: context.max
});
};
// Assume that freightProperty is a DataEntityProperty that describes numeric values.
// register the validator
freightProperty.validators.push(numericRangeValidator({ min: 100, max: 500 }));
Breeze substitutes context values and functions for the tokens in the messageTemplate when preparing the runtime error message;
'displayName' is a pre-defined context function that is always available.
Please note that Breeze substitutes the empty string for falsey parameters. That usually works in your favor.
Sometimes it doesn't as when the 'min' value is zero in which case the message text would have a hole
where the 'min' value goes, saying: "... an integer between the values of and ...". That is not what you want.
To avoid this effect, you may can bake certain of the context values into the 'messageTemplate' itself
as shown in this revision to the pertinent part of the previous example:
@example
// ... as before
// ... but bake the min/max values into the message template.
var template = breeze.core.formatString(
"'%displayName%' must be a number between the values of %1 and %2",
context.min, context.max);
return new Validator("numericRange", valFn, {
messageTemplate: template,
min: context.min,
max: context.max
});
@method <ctor> Validator
@param name {String} The name of this validator.
@param validatorFn {Function} A function to perform validation.
validatorFn(value, context)
@param validatorFn.value {Object} Value to be validated
@param validatorFn.context {Object} The same context object passed into the constructor with the following additional properties if not
otherwise specified.
@param validatorFn.context.value {Object} The value being validated.
@param validatorFn.context.name {String} The name of the validator being executed.
@param validatorFn.context.displayName {String} This will be either the value of the property's 'displayName' property or
the value of its 'name' property or the string 'Value'
@param validatorFn.context.messageTemplate {String} This will either be the value of Validator.messageTemplates[ {this validators name}] or null. Validator.messageTemplates
is an object that is keyed by validator name and that can be added to in order to 'register' your own message for a given validator.
The following property can also be specified for any validator to force a specific errorMessage string
@param [validatorFn.context.message] {String} If this property is set it will be used instead of the 'messageTemplate' property when an
error message is generated.
@param [context] {Object} A free form object whose properties will made available during the validation and error message creation process.
This object will be passed into the Validator's validation function whenever 'validate' is called. See above for a description
of additional properties that will be automatically added to this object if not otherwise specified.
**/
var ctor = function Validator(name, valFn, context) {
// _baseContext is what will get serialized
this._baseContext = context || {};
this._baseContext.name = name;
context = __extend(Object.create(rootContext), this._baseContext);
context.messageTemplate = context.messageTemplate || ctor.messageTemplates[name];
this.name = name;
this.valFn = valFn;
this.context = context;
};
var proto = ctor.prototype;
proto._$typeName = "Validator";
/**
The name of this validator.
__readOnly__
@property name {String}
**/
/**
The context for this validator.
This object will typically contain at a minimum the following properties. "name", "displayName", and "message" or "messageTemplate".
__readOnly__
@property context {Object}
**/
/**
Run this validator against the specified value. This method will usually be called internally either
automatically by an property change, entity attach, query or save operation, or manually as a result of
a validateEntity call on the EntityAspect. The resulting ValidationResults are available via the
EntityAspect.getValidationErrors method.
However, you can also call a validator directly either for testing purposes or some other reason if needed.
@example
// using one of the predefined validators
var validator = Validator.maxLength({ maxLength: 5, displayName: "City" });
// should be ok because "asdf".length < 5
var result = validator.validate("asdf");
ok(result === null);
result = validator.validate("adasdfasdf");
// extract all of the properties of the 'result'
var errMsg = result.errorMessage;
var context = result.context;
var sameValidator = result.validator;
@method validate
@param value {Object} Value to validate
@param additionalContext {Object} Any additional contextual information that the Validator
can make use of.
@return {ValidationError|null} A ValidationError if validation fails, null otherwise
**/
proto.validate = function (value, additionalContext) {
var currentContext;
if (additionalContext) {
currentContext = __extend(Object.create(this.context), additionalContext);
} else {
currentContext = this.context;
}
this.currentContext = currentContext;
try {
if (this.valFn(value, currentContext)) {
return null;
} else {
currentContext.value = value;
return new ValidationError(this, currentContext, this.getMessage());
}
} catch (e) {
return new ValidationError(this, currentContext, "Exception occured while executing this validator: " + this.name);
}
};
// context.value is not avail unless validate was called first.
/**
Returns the message generated by the most recent execution of this Validator.
@example
var v0 = Validator.maxLength({ maxLength: 5, displayName: "City" });
v0.validate("adasdfasdf");
var errMessage = v0.getMessage());
@method getMessage
@return {String}
**/
proto.getMessage = function () {
try {
var context = this.currentContext;
var message = context.message;
if (message) {
if (typeof (message) === "function") {
return message(context);
} else {
return message;
}
} else if (context.messageTemplate) {
return formatTemplate(context.messageTemplate, context);
} else {
return "invalid value: " + (this.name || "{unnamed validator}");
}
} catch (e) {
return "Unable to format error message" + e.toString();
}
};
proto.toJSON = function () {
return this._baseContext;
};
/**
Creates a validator instance from a JSON object or an array of instances from an array of JSON objects.
@method fromJSON
@static
@param json {Object} JSON object that represents the serialized version of a validator.
**/
ctor.fromJSON = function (json) {
if (Array.isArray(json)) {
return json.map(function (js) {
return ctor.fromJSON(js);
});
}
;
var validatorName = "Validator." + json.name;
var fn = __config.getRegisteredFunction(validatorName);
if (!fn) {
throw new Error("Unable to locate a validator named:" + json.name);
}
return fn(json);
};
/**
Register a validator instance so that any deserialized metadata can reference it.
@method register
@static
@param validator {Validator} Validator to register.
**/
ctor.register = function (validator) {
__config.registerFunction(function () {
return validator;
}, "Validator." + validator.name);
};
/**
Register a validator factory so that any deserialized metadata can reference it.
@method registerFactory
@static
@param validatorFactory {Function} A function that optionally takes a context property and returns a Validator instance.
@param name {String} The name of the validator.
**/
ctor.registerFactory = function (validatorFn, name) {
__config.registerFunction(validatorFn, "Validator." + name);
};
/**
Map of standard error message templates keyed by validator name.
You can add to or modify this object to customize the template used for any validation error message.
@example
// v is this function is the value to be validated, in this case a "country" string.
var valFn = function (v) {
if (v == null) return true;
return (core.stringStartsWith(v, "US"));
};
var countryValidator = new Validator("countryIsUS", valFn, { displayName: "Country" });
Validator.messageTemplates.countryIsUS = "'%displayName%' must start with 'US'";
// This will have a similar effect to this
var countryValidator = new Validator("countryIsUS", valFn, {
displayName: "Country",
messageTemplate: "'%displayName%' must start with 'US'"
});
@property messageTemplates {Object}
@static
**/
ctor.messageTemplates = {
bool: "'%displayName%' must be a 'true' or 'false' value",
creditCard: "The %displayName% is not a valid credit card number",
date: "'%displayName%' must be a date",
duration: "'%displayName%' must be a ISO8601 duration string, such as 'P3H24M60S'",
emailAddress: "The %displayName% '%value%' is not a valid email address",
guid: "'%displayName%' must be a GUID",
integer: "'%displayName%' must be an integer",
integerRange: "'%displayName%' must be an integer between the values of %minValue% and %maxValue%",
maxLength: "'%displayName%' must be a string with %maxLength% characters or less",
number: "'%displayName%' must be a number",
phone: "The %displayName% '%value%' is not a valid phone number",
regularExpression: "The %displayName% '%value%' does not match '%expression%'",
required: "'%displayName%' is required",
string: "'%displayName%' must be a string",
stringLength: "'%displayName%' must be a string with between %minLength% and %maxLength% characters",
url: "The %displayName% '%value%' is not a valid url"
};
/**
Returns a standard 'required value' Validator
@example
// Assume em1 is a preexisting EntityManager.
var custType = em1.metadataStore.getEntityType("Customer");
var regionProperty - custType.getProperty("Region");
// Makes "Region" on Customer a required property.
regionProperty.validators.push(Validator.required());
// or to allow empty strings
regionProperty.validators.push(Validator.required({ allowEmptyStrings: true }););
@method required
@static
@param context {Object}
@param [context.allowEmptyStrings] {Boolean} If this parameter is omitted or false then empty strings do NOT pass validation.
@return {Validator} A new Validator
**/
ctor.required = function (context) {
var valFn = function (v, ctx) {
if (typeof v === "string") {
if (ctx && ctx.allowEmptyStrings) return true;
return v.length > 0;
} else {
return v != null;
}
};
return new ctor("required", valFn, context);
};
/**
Returns a standard maximum string length Validator; the maximum length must be specified
@example
// Assume em1 is a preexisting EntityManager.
var custType = em1.metadataStore.getEntityType("Customer");
var regionProperty - custType.getProperty("Region");
// Validates that the value of the Region property on Customer will be less than or equal to 5 characters.
regionProperty.validators.push(Validator.maxLength( {maxLength: 5}));
@method maxLength
@static
@param context {Object}
@param context.maxLength {Integer}
@return {Validator} A new Validator
**/
ctor.maxLength = function (context) {
var valFn = function (v, ctx) {
if (v == null) return true;
if (typeof (v) !== "string") return false;
return v.length <= ctx.maxLength;
};
return new ctor("maxLength", valFn, context);
};
/**
Returns a standard string length Validator; both minimum and maximum lengths must be specified.
@example
// Assume em1 is a preexisting EntityManager.
var custType = em1.metadataStore.getEntityType("Customer");
var regionProperty - custType.getProperty("Region");
// Validates that the value of the Region property on Customer will be
// between 2 and 5 characters
regionProperty.validators.push(Validator.stringLength( {minLength: 2, maxLength: 5});
@method stringLength
@static
@param context {Object}
@param context.maxLength {Integer}
@param context.minLength {Integer}
@return {Validator} A new Validator
**/
ctor.stringLength = function (context) {
var valFn = function (v, ctx) {
if (v == null) return true;
if (typeof (v) !== "string") return false;
if (ctx.minLength != null && v.length < ctx.minLength) return false;
if (ctx.maxLength != null && v.length > ctx.maxLength) return false;
return true;
};
return new ctor("stringLength", valFn, context);
};
/**
Returns a standard string dataType Validator.
@example
// Assume em1 is a preexisting EntityManager.
var custType = em1.metadataStore.getEntityType("Customer");
var regionProperty - custType.getProperty("Region");
// Validates that the value of the Region property on Customer is a string.
regionProperty.validators.push(Validator.string());
@method string
@static
@return {Validator} A new Validator
**/
ctor.string = function () {
var valFn = function (v) {
if (v == null) return true;
return (typeof v === "string");
};
return new ctor("string", valFn);
};
/**
Returns a Guid data type Validator.
@example
// Assume em1 is a preexisting EntityManager.
var custType = em1.metadataStore.getEntityType("Customer");
var customerIdProperty - custType.getProperty("CustomerID");
// Validates that the value of the CustomerID property on Customer is a Guid.
customerIdProperty.validators.push(Validator.guid());
@method guid
@static
@return {Validator} A new Validator
**/
ctor.guid = function () {
var valFn = function (v) {
if (v == null) return true;
return __isGuid(v);
};
return new ctor("guid", valFn);
};
/**
Returns a ISO 8601 duration string Validator.
@example
// Assume em1 is a preexisting EntityManager.
var eventType = em1.metadataStore.getEntityType("Event");
var elapsedTimeProperty - eventType.getProperty("ElapsedTime");
// Validates that the value of the ElapsedTime property on Customer is a duration.
elapsedTimeProperty.validators.push(Validator.duration());
@method duration
@static
@return {Validator} A new Validator
**/
ctor.duration = function () {
var valFn = function (v) {
if (v == null) return true;
return __isDuration(v);
};
return new ctor("duration", valFn);
};
/**
Returns a standard numeric data type Validator.
@example
// Assume em1 is a preexisting EntityManager.
var orderType = em1.metadataStore.getEntityType("Order");
var freightProperty - orderType.getProperty("Freight");
// Validates that the value of the Freight property on Order is a number.
freightProperty.validators.push(Validator.number());
@method number
@static
@return {Validator} A new Validator
**/
// TODO: may need to have seperate logic for single.
ctor.number = ctor.double = ctor.single = function (context) {
var valFn = function (v, ctx) {
if (v == null) return true;
if (typeof v === "string" && ctx && ctx.allowString) {
v = parseFloat(v, 10);
}
return (typeof v === "number" && !isNaN(v));
};
return new ctor("number", valFn, context);
};
/**
Returns a standard large integer data type - 64 bit - Validator.
@example
// Assume em1 is a preexisting EntityManager.
var orderType = em1.metadataStore.getEntityType("Order");
var freightProperty - orderType.getProperty("Freight");
// Validates that the value of the Freight property on Order is within the range of a 64 bit integer.
freightProperty.validators.push(Validator.int64());
@method int64
@static
@return {Validator} A new Validator
**/
ctor.integer = ctor.int64 = function (context) {
var valFn = function (v, ctx) {
if (v == null) return true;
if (typeof v === "string" && ctx && ctx.allowString) {
v = parseInt(v, 10);
}
return (typeof v === "number") && (!isNaN(v)) && Math.floor(v) === v;
};
return new ctor("integer", valFn, context);
};
/**
Returns a standard 32 bit integer data type Validator.
@example
// Assume em1 is a preexisting EntityManager.
var orderType = em1.metadataStore.getEntityType("Order");
var freightProperty - orderType.getProperty("Freight");
freightProperty.validators.push(Validator.int32());
@method int32
@static
@return {Validator} A new Validator
**/
ctor.int32 = function (context) {
return intRangeValidatorCtor("int32", INT32_MIN, INT32_MAX, context)();
};
/**
Returns a standard 16 bit integer data type Validator.
@example
// Assume em1 is a preexisting EntityManager.
var orderType = em1.metadataStore.getEntityType("Order");
var freightProperty - orderType.getProperty("Freight");
// Validates that the value of the Freight property on Order is within the range of a 16 bit integer.
freightProperty.validators.push(Validator.int16());
@method int16
@static
@return {Validator} A new Validator
**/
ctor.int16 = function (context) {
return intRangeValidatorCtor("int16", INT16_MIN, INT16_MAX, context)();
};
/**
Returns a standard byte data type Validator. (This is a integer between 0 and 255 inclusive for js purposes).
@example
// Assume em1 is a preexisting EntityManager.
var orderType = em1.metadataStore.getEntityType("Order");
var freightProperty - orderType.getProperty("Freight");
// Validates that the value of the Freight property on Order is within the range of a 16 bit integer.
// Probably not a very good validation to place on the Freight property.
regionProperty.validators.push(Validator.byte());
@method byte
@static
@return {Validator} A new Validator
**/
ctor.byte = function (context) {
return intRangeValidatorCtor("byte", BYTE_MIN, BYTE_MAX, context)();
};
/**
Returns a standard boolean data type Validator.
@example
// Assume em1 is a preexisting EntityManager.
var productType = em1.metadataStore.getEntityType("Product");
var discontinuedProperty - productType.getProperty("Discontinued");
// Validates that the value of the Discontinued property on Product is a boolean
discontinuedProperty.validators.push(Validator.bool());
@method bool
@static
@return {Validator} A new Validator
**/
ctor.bool = function () {
var valFn = function (v) {
if (v == null) return true;
return (v === true) || (v === false);
};
return new ctor("bool", valFn);
};
ctor.none = function () {
var valFn = function (v) {
return true;
};
return new ctor("none", valFn);
};
/**
Returns a standard date data type Validator.
@example
// Assume em1 is a preexisting EntityManager.
var orderType = em1.metadataStore.getEntityType("Order");
var orderDateProperty - orderType.getProperty("OrderDate");
// Validates that the value of the OrderDate property on Order is a date
// Probably not a very good validation to place on the Freight property.
orderDateProperty.validators.push(Validator.date());
@method date
@static
@return {Validator} A new Validator
**/
ctor.date = function () {
var valFn = function (v) {
if (v == null) return true;
if (typeof v === "string") {
try {
return !isNaN(Date.parse(v));
// old code
// return __isDate(new Date(v));
} catch (e) {
return false;
}
} else {
return __isDate(v);
}
};
return new ctor("date", valFn);
};
/**
Returns a credit card number validator
Performs a luhn algorithm checksum test for plausability
catches simple mistakes; only service knows for sure
@example
// Assume em is a preexisting EntityManager.
var personType = em.metadataStore.getEntityType("Person");
var creditCardProperty = personType.getProperty("creditCard");
// Validates that the value of the Person.creditCard property is credit card.
creditCardProperty.validators.push(Validator.creditCard());
@method creditCard
@static
@param [context] {Object} optional parameters to pass through to validation constructor
@return {Validator} A new Validator
**/
ctor.creditCard = function (context) {
function valFn(v) {
if (v == null || v === '') return true;
if (typeof (v) !== 'string') return false;
v = v.replace(/(\-|\s)/g, ""); // remove dashes and spaces
if (!v || /\D/.test(v)) return false; // all digits, not empty
return luhn(v);
};
return new ctor('creditCard', valFn, context);
};
// http://rosettacode.org/wiki/Luhn_test_of_credit_card_numbers#JavaScript
function luhn(a, b, c, d, e) {
for (d = +a[b = a.length - 1], e = 0; b--;)
c = +a[b], d += ++e % 2 ? 2 * c % 10 + (c > 4) : c;
return !(d % 10);
};
/**
Returns a regular expression validator; the expression must be specified
@example
// Add validator to a property. Assume em is a preexisting EntityManager.
var customerType = em.metadataStore.getEntityType("Customer");
var regionProperty = customerType.getProperty("Region");
// Validates that the value of Customer.Region is 2 char uppercase alpha.
regionProperty.validators.push(Validator.regularExpression( {expression: '^[A-Z]{2}$'} );
@method regularExpression
@static
@param context {Object}
@param context.expression {String} String form of the regular expression to apply
@return {Validator} A new Validator
**/
ctor.regularExpression = function (context) {
function valFn(v, ctx) {
// do not invalidate if empty; use a separate required test
if (v == null || v === '') return true;
if (typeof (v) !== 'string') return false;
try {
var re = new RegExp(ctx.expression);
} catch (e) {
throw new Error('Missing or invalid expression parameter to regExp validator');
}
return re.test(v);
};
return new ctor('regularExpression', valFn, context);
};
/**
Returns the email address validator
@example
// Assume em is a preexisting EntityManager.
var personType = em.metadataStore.getEntityType("Person");
var emailProperty = personType.getProperty("email");
// Validates that the value of the Person.email property is an email address.
emailProperty.validators.push(Validator.emailAddress());
@method emailAddress
@static
@param [context] {Object} optional parameters to pass through to validation constructor
@return {Validator} A new Validator
**/
ctor.emailAddress = function (context) {
// See https://github.com/srkirkland/DataAnnotationsExtensions/blob/master/DataAnnotationsExtensions/EmailAttribute.cs
var reEmailAddress = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i;
return makeRegExpValidator('emailAddress', reEmailAddress, null, context);
};
/**
Returns the phone validator
Provides basic assertions on the format and will help to eliminate most nonsense input
Matches:
International dialing prefix: {{}, +, 0, 0000} (with or without a trailing break character, if not '+': [-/. ])
> ((\+)|(0(\d+)?[-/.\s]))
Country code: {{}, 1, ..., 999} (with or without a trailing break character: [-/. ])
> [1-9]\d{,2}[-/.\s]?
Area code: {(0), ..., (000000), 0, ..., 000000} (with or without a trailing break character: [-/. ])
> ((\(\d{1,6}\)|\d{1,6})[-/.\s]?)?
Local: {0, ...}+ (with or without a trailing break character: [-/. ])
> (\d+[-/.\s]?)+\d+
@example
// Assume em is a preexisting EntityManager.
var customerType = em.metadataStore.getEntityType("Customer");
var phoneProperty = customerType.getProperty("phone");
// Validates that the value of the Customer.phone property is phone.
phoneProperty.validators.push(Validator.phone());
@method phone
@static
@param [context] {Object} optional parameters to pass through to validation constructor
@return {Validator} A new Validator
**/
ctor.phone = function (context) {
// See https://github.com/srkirkland/DataAnnotationsExtensions/blob/master/DataAnnotationsExtensions/Expressions.cs
var rePhone = /^((\+|(0(\d+)?[-/.\s]?))[1-9]\d{0,2}[-/.\s]?)?((\(\d{1,6}\)|\d{1,6})[-/.\s]?)?(\d+[-/.\s]?)+\d+$/;
return makeRegExpValidator('phone', rePhone, null, context);
};
/**
Returns the URL (protocol required) validator
@example
// Assume em is a preexisting EntityManager.
var personType = em.metadataStore.getEntityType("Person");
var websiteProperty = personType.getProperty("website");
// Validates that the value of the Person.website property is a URL.
websiteProperty.validators.push(Validator.url());
@method url
@static
@param [context] {Object} optional parameters to pass through to validation constructor
@return {Validator} A new Validator
**/
ctor.url = function (context) {
//See https://github.com/srkirkland/DataAnnotationsExtensions/blob/master/DataAnnotationsExtensions/UrlAttribute.cs
var reUrlProtocolRequired = /^(https?|ftp):\/\/(((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-fA-F]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|([a-zA-Z][\-a-zA-Z0-9]*)|((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-fA-F]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-fA-F]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-fA-F]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-fA-F]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/;
return makeRegExpValidator('url', reUrlProtocolRequired, null, context);
};
/**
Creates a regular expression validator with a fixed expression.
Many of the stock validators are built with this factory method.
Their expressions are often derived from
https://github.com/srkirkland/DataAnnotationsExtensions/blob/master/DataAnnotationsExtensions
You can try many of them at http://dataannotationsextensions.org/
@example
// Make a zipcode validator
function zipValidator = Validator.makeRegExpValidator(
"zipVal,
/^\d{5}([\-]\d{4})?$/,
"The %displayName% '%value%' is not a valid U.S. zipcode");
// Register it.
Validator.register(zipValidator);
// Add it to a data property. Assume em is a preexisting EntityManager.
var custType = em.metadataStore.getEntityType("Customer");
var zipProperty = custType.getProperty("PostalCode");
zipProperty.validators.push(zipValidator);
@method makeRegExpValidator
@static
@param validatorName {String} name of this validator
@param expression {String | RegExp} regular expression to apply
@param [defaultMessage] {String} default message for failed validations
@param [context] {Object} optional parameters to pass through to validation constructor
@return {Validator} A new Validator
**/
ctor.makeRegExpValidator = makeRegExpValidator;
function makeRegExpValidator(validatorName, expression, defaultMessage, context) {
if (defaultMessage) {
ctor.messageTemplates[validatorName] = defaultMessage;
}
var re = (typeof (expression) === 'string') ? new RegExp(expression) : expression;
var valFn = function (v) {
// do not invalidate if empty; use a separate required test
if (v == null || v === '') return true;
if (typeof (v) !== 'string') return false;
return re.test(v);
};
return new ctor(validatorName, valFn, context);
};
// register all validators
__objectForEach(ctor, function (key, value) {
if (typeof (value) !== "function") {
return;
}
if (key === "fromJSON" || key === "register" ||
key === "registerFactory" || key === "makeRegExpValidator") {
return;
}
__config.registerFunction(value, "Validator." + key);
});
// private funcs
function formatTemplate(template, vars, ownPropertiesOnly) {
if (!vars) return template;
return template.replace(/%([^%]+)%/g, function (_, key) {
var valOrFn;
if (ownPropertiesOnly) {
valOrFn = vars.hasOwnProperty(key) ? vars[key] : '';
} else {
valOrFn = vars[key];
}
if (valOrFn != null) {
if (__isFunction(valOrFn)) {
return valOrFn(vars);
} else {
return valOrFn;
}
} else {
return "";
}
});
}
function intRangeValidatorCtor(validatorName, minValue, maxValue, context) {
context = context || {};
if (minValue !== undefined) { context.min = minValue; }
if (maxValue !== undefined) { context.max = maxValue; }
var templateExists = context.messageTemplate || ctor.messageTemplates[validatorName];
if (!templateExists) {
ctor.messageTemplates[validatorName] = __formatString("'%displayName%' must be an integer between the values of %1 and %2",
minValue, maxValue);
}
return function () {
var valFn = function (v, ctx) {
if (v == null) return true;
if (typeof v === "string" && ctx && ctx.allowString) {
v = parseInt(v, 0);
}
if ((typeof v === "number") && (!isNaN(v)) && Math.floor(v) === v) {
if (minValue != null && v < minValue) {
return false;
}
if (maxValue != null && v > maxValue) {
return false;
}
return true;
} else {
return false;
}
};
return new ctor(validatorName, valFn, context);
};
}
return ctor;
})();
var ValidationError = (function () {
/**
A ValidationError is used to describe a failed validation.
@class ValidationError
**/
/**
Constructs a new ValidationError
@method <ctor> ValidationError
@param validator {Validator || null} The Validator used to create this error, if any.
@param context { ContextObject || null} The Context object used in conjunction with the Validator to create this error.
@param errorMessage { String} The actual error message
@param [key] {String} An optional key used to define a key for this error. One will be created automatically if not provided here.
**/
var ctor = function ValidationError(validator, context, errorMessage, key) {
assertParam(validator, "validator").isOptional().isInstanceOf(Validator).check();
assertParam(errorMessage, "errorMessage").isNonEmptyString().check();
assertParam(key, "key").isOptional().isNonEmptyString().check();
this.validator = validator;
context = context || {};
this.context = context;
this.errorMessage = errorMessage;
this.property = context.property;
this.propertyName = context.propertyName || (context.property && context.property.name);
if (key) {
this.key = key;
} else {
this.key = ValidationError.getKey(validator || errorMessage, this.propertyName);
}
this.isServerError = false;
};
/**
The Validator associated with this ValidationError.
__readOnly__
@property validator {Validator}
**/
/**
A 'context' object associated with this ValidationError.
__readOnly__
@property context {Object}
**/
/**
The DataProperty or NavigationProperty associated with this ValidationError.
__readOnly__
@property property {DataProperty|NavigationProperty}
**/
/**
The property name associated with this ValidationError. This will be a "property path" for any properties of a complex object.
__readOnly__
@property propertyName {String}
**/
/**
The error message associated with the ValidationError.
__readOnly__
@property errorMessage {string}
**/
/**
The key by which this validation error may be removed from a collection of ValidationErrors.
__readOnly__
@property key {string}
**/
/**
Whether this is a server error.
__readOnly__
@property isServerError {bool}
**/
/**
Composes a ValidationError 'key' given a validator or an errorName and an optional propertyName
@method getKey
@static
@param validator {ValidatorOrErrorKey} A Validator or an "error name" if no validator is available.
@param [propertyName] A property name
@return {String} A ValidationError 'key'
**/
ctor.getKey = function (validatorOrErrorName, propertyName) {
return (validatorOrErrorName.name || validatorOrErrorName) + (propertyName ? ":" + propertyName : "");
};
return ctor;
})();
breeze.Validator = Validator;
breeze.ValidationError = ValidationError;