Skip to content

Commit e655a56

Browse files
committed
Breaking change: rename scalePrecision to precisionScale and interpret terms correctly
1 parent 185bc2e commit e655a56

11 files changed

Lines changed: 315 additions & 159 deletions

.idea/runConfigurations/All_Tests.xml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/api/rules/precisionScale.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
id: precisionScale
3+
title: '.precisionScale'
4+
---
5+
6+
The `.precisionScale` rule is used to ensure that the value of a given `number` property is permissible for the specified **precision** and **scale**.
7+
8+
These terms are defined as follows:
9+
10+
- **Precision** is the number of digits in a number.
11+
- **Scale** is the number of digits to the right of the decimal point in a number.
12+
13+
:::warning
14+
15+
In releases prior to `v5.0.0` the `.precisionScale` rule was called `.scalePrecision` and the parameter naming was incorrect!
16+
17+
:::
18+
19+
## Example
20+
21+
```typescript
22+
import { Validator } from 'fluentvalidation-ts';
23+
24+
type FormModel = {
25+
price: number;
26+
};
27+
28+
class FormValidator extends Validator<FormModel> {
29+
constructor() {
30+
super();
31+
32+
this.ruleFor('price').precisionScale(4, 2);
33+
}
34+
}
35+
36+
const formValidator = new FormValidator();
37+
38+
formValidator.validate({ price: 10.01 });
39+
// ✔ {}
40+
41+
formValidator.validate({ price: 0.001 }); // Too many digits after the decimal point
42+
// ❌ { price: 'Value must be no more than 4 digits in total, with allowance for 2 decimals' }
43+
44+
formValidator.validate({ price: 100.1 }); // Too many digits (when accounting for reserved digits after the decimal point)
45+
// ❌ { price: 'Value must be no more than 4 digits in total, with allowance for 2 decimals' }
46+
```
47+
48+
## Reference
49+
50+
### `.precisionScale(precision: number, scale: number)`
51+
52+
A number validation rule which takes in an allowed precision and scale, and ensures that the value of the given property is permissible.
53+
54+
:::danger
55+
56+
Due to rounding issues with floating point numbers in JavaScript, this rule may not function as expected for large precisions/scales.
57+
58+
:::
59+
60+
### `precision`
61+
62+
This is the total number of digits that the value may have (taking into account the number of digits "reserved" for after the decimal point).
63+
64+
The maximum number of significant digits allowed before the decimal point (i.e. the integer part) can be calculated as `(precision - scale)`.
65+
66+
### `scale`
67+
68+
This is the maximum number of digits after the decimal point that the value may have.
69+
70+
:::note
71+
72+
When `precision` and `scale` are equal, the "leading zero" to the left of the decimal point is **not** counted as a digit (e.g. a value of `0.01` would be viewed as `.01`).
73+
74+
:::
75+
76+
## Example Message
77+
78+
> Value must not be more than `[precision]` digits in total, with allowance for `[scale]` decimals

docs/api/rules/scalePrecision.md

Lines changed: 0 additions & 80 deletions
This file was deleted.

src/rules/PrecisionScaleRule.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Rule } from './Rule';
2+
3+
export class PrecisionScaleRule<TModel, TValue> extends Rule<TModel, TValue> {
4+
constructor(precision: number, scale: number) {
5+
// istanbul ignore next - https://github.com/gotwarlost/istanbul/issues/690
6+
super((value: TValue) => {
7+
if (value == null || value === 0) {
8+
return null;
9+
}
10+
if (typeof value !== 'number') {
11+
throw new TypeError('A non-number value was passed to the precisionScale rule');
12+
}
13+
14+
const regex = precisionScaleRegex({ precision, scale });
15+
16+
if (!regex.test(value.toString())) {
17+
return `Value must not be more than ${precision} digits in total, with allowance for ${scale} decimals`;
18+
}
19+
20+
return null;
21+
});
22+
}
23+
}
24+
25+
const precisionScaleRegex = ({ precision, scale }: { precision: number; scale: number }) => {
26+
const integerDigits = precision - scale;
27+
28+
return integerDigits === 0
29+
? new RegExp(`^-?0?\\.\\d{0,${scale}}$`) // The leading 0 to the left of the decimal point does not count towards precision
30+
: new RegExp(`^-?\\d{1,${integerDigits}}(\\.\\d{1,${scale}})?$`);
31+
};

src/rules/ScalePrecisionRule.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/valueValidator/AsyncRuleValidators.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export type AsyncRuleValidators<TModel, TValue> = {
5656
TValue,
5757
(lowerBound: number, upperBound: number) => AsyncRuleValidatorsAndExtensions<TModel, TValue>
5858
>;
59-
scalePrecision: IfNumber<
59+
precisionScale: IfNumber<
6060
TValue,
6161
(precision: number, scale: number) => AsyncRuleValidatorsAndExtensions<TModel, TValue>
6262
>;

src/valueValidator/CoreValueValidatorBuilder.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { NotEqualRule } from '@/rules/NotEqualRule';
1919
import { NotNullRule, NotNullRuleOptions } from '@/rules/NotNullRule';
2020
import { NullRule, NullRuleOptions } from '@/rules/NullRule';
2121
import { Rule } from '@/rules/Rule';
22-
import { ScalePrecisionRule } from '@/rules/ScalePrecisionRule';
22+
import { PrecisionScaleRule } from '@/rules/PrecisionScaleRule';
2323
import { ValidatorRule } from '@/rules/ValidatorRule';
2424
import { Predicate } from '@/types/Predicate';
2525
import { AppliesTo } from '@/types/AppliesTo';
@@ -240,12 +240,12 @@ export abstract class CoreValueValidatorBuilder<
240240
return this.getAllRulesAndExtensions();
241241
};
242242

243-
public scalePrecision = (precision: number, scale: number) => {
244-
if (scale - precision <= 0) {
245-
throw new Error('Invalid scale and precision were passed to the scalePrecision rule');
243+
public precisionScale = (precision: number, scale: number) => {
244+
if (precision < 1 || scale < 0 || precision < scale) {
245+
throw new Error('Invalid scale and precision were passed to the precisionScale rule');
246246
}
247-
const scalePrecisionRule = new ScalePrecisionRule<TModel, TTransformedValue>(precision, scale);
248-
this.pushRule(scalePrecisionRule);
247+
const precisionScaleRule = new PrecisionScaleRule<TModel, TTransformedValue>(precision, scale);
248+
this.pushRule(precisionScaleRule);
249249
return this.getAllRulesAndExtensions();
250250
};
251251

@@ -270,7 +270,7 @@ export abstract class CoreValueValidatorBuilder<
270270
exclusiveBetween: this.exclusiveBetween,
271271
inclusiveBetween: this.inclusiveBetween,
272272
setValidator: this.setValidator,
273-
scalePrecision: this.scalePrecision,
273+
precisionScale: this.precisionScale,
274274
});
275275

276276
protected abstract getAllRules: () => TRuleValidators;

src/valueValidator/RuleValidators.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export type RuleValidators<TModel, TValue> = {
4040
TValue,
4141
(lowerBound: number, upperBound: number) => RuleValidatorsAndExtensions<TModel, TValue>
4242
>;
43-
scalePrecision: IfNumber<
43+
precisionScale: IfNumber<
4444
TValue,
4545
(precision: number, scale: number) => RuleValidatorsAndExtensions<TModel, TValue>
4646
>;

0 commit comments

Comments
 (0)