Skip to content

Commit cfbd715

Browse files
[feature] added rule for currency validation
1 parent 55d3e68 commit cfbd715

File tree

7 files changed

+110
-15
lines changed

7 files changed

+110
-15
lines changed

README.md

+37-11
Original file line numberDiff line numberDiff line change
@@ -70,32 +70,34 @@ $ npm run bench
7070
- [Properties](#properties-1)
7171
- [`class`](#class)
7272
- [Properties](#properties-2)
73-
- [`date`](#date)
73+
- [`currency`](#currency)
7474
- [Properties](#properties-3)
75-
- [`email`](#email)
75+
- [`date`](#date)
7676
- [Properties](#properties-4)
77-
- [`enum`](#enum)
77+
- [`email`](#email)
7878
- [Properties](#properties-5)
79-
- [`equal`](#equal)
79+
- [`enum`](#enum)
8080
- [Properties](#properties-6)
81-
- [`forbidden`](#forbidden)
81+
- [`equal`](#equal)
8282
- [Properties](#properties-7)
83+
- [`forbidden`](#forbidden)
84+
- [Properties](#properties-8)
8385
- [`function`](#function)
8486
- [`luhn`](#luhn)
8587
- [`mac`](#mac)
8688
- [`multi`](#multi)
8789
- [`number`](#number)
88-
- [Properties](#properties-8)
89-
- [`object`](#object)
9090
- [Properties](#properties-9)
91-
- [`string`](#string)
91+
- [`object`](#object)
9292
- [Properties](#properties-10)
93-
- [`tuple`](#tuple)
93+
- [`string`](#string)
9494
- [Properties](#properties-11)
95-
- [`url`](#url)
95+
- [`tuple`](#tuple)
9696
- [Properties](#properties-12)
97-
- [`uuid`](#uuid)
97+
- [`url`](#url)
9898
- [Properties](#properties-13)
99+
- [`uuid`](#uuid)
100+
- [Properties](#properties-14)
99101
- [Custom validator](#custom-validator)
100102
- [Custom validation for built-in rules](#custom-validation-for-built-in-rules)
101103
- [Custom error messages (l10n)](#custom-error-messages-l10n)
@@ -514,6 +516,30 @@ Property | Default | Description
514516
-------- | -------- | -----------
515517
`instanceOf` | `null` | Checked Class.
516518

519+
## `currency`
520+
This is a `Currency` validator to check if the value is a valid currency string.
521+
522+
```js
523+
const schema = {
524+
money_amount: { type: "currency", currencySymbol: '$' }
525+
}
526+
527+
v.validate({ money_amount: '$12.99'}, schema); // Valid
528+
v.validate({ money_amount: '$0.99'}, schema); // Valid
529+
v.validate({ money_amount: '$12,345.99'}, schema); // Valid
530+
v.validate({ money_amount: '$123,456.99'}, schema); // Valid
531+
532+
v.validate({ money_amount: '$1234,567.99'}, schema); // Fail
533+
v.validate({ money_amount: '$1,23,456.99'}, schema); // Fail
534+
v.validate({ money_amount: '$12,34.5.99' }, schema); // Fail
535+
```
536+
537+
### Properties
538+
Property | Default | Description
539+
-------- | -------- | -----------
540+
`currencySymbol` | `null` | The currency symbol expected in string (as prefix).
541+
`symbolOptional` | `false` | Toggle to make the symbol optional in string, although, if present it would only allow the currencySymbol.
542+
517543
## `date`
518544
This is a `Date` validator.
519545

lib/messages.js

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ module.exports = {
4141

4242
boolean: "The '{field}' field must be a boolean.",
4343

44+
currency: "The '{field}' must be a valid currency format",
45+
4446
date: "The '{field}' field must be a Date.",
4547
dateMin: "The '{field}' field must be greater than or equal to {expected}.",
4648
dateMax: "The '{field}' field must be less than or equal to {expected}.",

lib/rules/currency.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use strict";
2+
const CURRENCY_REGEX = `(?=.*\\\\d)^~1(([0-9]\\\\d{0,2}(,\\\\d{3})*)|0)?(\\\\.\\\\d{1,2})?$`;
3+
/** Signature: function(value, field, parent, errors, context)
4+
*/
5+
6+
module.exports = function ({schema, messages}, path, context) {
7+
const currencySymbol = schema.currencySymbol || null;
8+
let isCurrencySymbolMandatory = !schema.symbolOptional;
9+
let finalRegex = CURRENCY_REGEX.replace('~1', currencySymbol ? (`\\\\${currencySymbol}${(isCurrencySymbolMandatory ? '' : '?')}`) : '');
10+
return {
11+
source: `
12+
if (!value.match(new RegExp('${finalRegex}'))){
13+
return ${this.makeError({
14+
type: "currency",
15+
actual: "value",
16+
messages
17+
})}
18+
}
19+
return value;
20+
`
21+
};
22+
};

lib/validator.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ function loadRules() {
1414
boolean: require("./rules/boolean"),
1515
class: require("./rules/class"),
1616
custom: require("./rules/custom"),
17+
currency: require("./rules/currency"),
1718
date: require("./rules/date"),
1819
email: require("./rules/email"),
1920
enum: require("./rules/enum"),
@@ -384,12 +385,12 @@ class Validator {
384385
const ruleVName = "rule" + ruleIndex;
385386
const fnCustomErrorsVName = "fnCustomErrors" + ruleIndex;
386387
if (typeof schema[fnName] == "function") {
387-
if (context.customs[ruleIndex]) {
388+
if (context.customs[ruleIndex]) {
388389
context.customs[ruleIndex].messages = messages;
389390
context.customs[ruleIndex].schema = schema;
390391
}
391392
else context.customs[ruleIndex] = { messages, schema };
392-
393+
393394
if (this.opts.useNewCustomCheckerFunction) {
394395
return `
395396
const ${ruleVName} = context.customs[${ruleIndex}];

test/rules/currency.spec.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"use strict";
2+
3+
const Validator = require("../../lib/validator");
4+
const v = new Validator();
5+
6+
describe("Test rule: currency", () => {
7+
it("should have decimal optional, and correctly placed if present", () => {
8+
const check = v.compile({$$root: true, type: "currency", 'currencySymbol': '$', 'symbolOptional': true});
9+
expect(check("$12.2")).toEqual(true);
10+
expect(check("$12,222.2")).toEqual(true);
11+
expect(check("$12,222")).toEqual(true);
12+
expect(check("$12,222.0")).toEqual(true);
13+
expect(check("$1.22.00")).toEqual([{"actual": "$1.22.00", "field": undefined, "message": "The '' must be a valid currency format", "type": "currency"}]);
14+
});
15+
16+
it("should check comma placement is correct", () => {
17+
const check = v.compile({$$root: true, type: "currency", 'currencySymbol': '$', 'symbolOptional': true});
18+
expect(check("$12.2")).toEqual(true);
19+
expect(check("$12,222.2")).toEqual(true);
20+
expect(check("$122,222.2")).toEqual(true);
21+
expect(check("$1234,222.2")).toEqual([{"actual": "$1234,222.2", "field": undefined, "message": "The '' must be a valid currency format", "type": "currency"}]);
22+
expect(check("$1,2,222")).toEqual( [{"actual": "$1,2,222", "field": undefined, "message": "The '' must be a valid currency format", "type": "currency"}]);
23+
});
24+
25+
it("should not allow any currency symbol , if not supplied in schema", () => {
26+
let check = v.compile({$$root: true, type: "currency"});
27+
expect(check("12.2")).toEqual(true);
28+
expect(check("$12.2")).toEqual( [{"actual": "$12.2", "field": undefined, "message": "The '' must be a valid currency format", "type": "currency"}]);
29+
});
30+
31+
it("should not allow any other currency symbol, other than supplied in schema", () => {
32+
let check = v.compile({$$root: true, type: "currency", 'currencySymbol': '$', 'symbolOptional': false});
33+
expect(check("$12.2")).toEqual(true);
34+
expect(check("#12.2")).toEqual([{"actual": "#12.2", "field": undefined, "message": "The '' must be a valid currency format", "type": "currency"}]);
35+
});
36+
37+
it("should keep currency symbol optional, if symbolOptional is true in schema", () => {
38+
let check = v.compile({$$root: true, type: "currency", 'currencySymbol': '$', 'symbolOptional': true});
39+
expect(check("$12.2")).toEqual(true);
40+
expect(check("12.2")).toEqual(true);
41+
expect(check("#12.2")).toEqual([{"actual": "#12.2", "field": undefined, "message": "The '' must be a valid currency format", "type": "currency"}]
42+
);
43+
});
44+
});

test/typescript/validator.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ describe('TypeScript Definitions', () => {
1414
expect(v.validate).toBeInstanceOf(Function);
1515
expect(v.add).toBeInstanceOf(Function);
1616

17-
expect(Object.keys(v.rules)).toHaveProperty('length', 20);
17+
expect(Object.keys(v.rules)).toHaveProperty('length', 21);
1818
});
1919

2020
it('should create instance with custom messages', () => {

test/validator.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe("Test constructor", () => {
1212
expect(v.validate).toBeInstanceOf(Function);
1313
expect(v.add).toBeInstanceOf(Function);
1414

15-
expect(Object.keys(v.rules).length).toBe(20);
15+
expect(Object.keys(v.rules).length).toBe(21);
1616
});
1717

1818
it("should create instance with custom messages", () => {

0 commit comments

Comments
 (0)