diff --git a/src/rules/core/precision-rule-spec.js b/src/rules/core/precision-rule-spec.js index 33b2bca..5907b95 100644 --- a/src/rules/core/precision-rule-spec.js +++ b/src/rules/core/precision-rule-spec.js @@ -10,6 +10,12 @@ describe('PrecisionRule', () => { const model = new Model({ type: 'Precision', fields: { + latitude: { + fieldName: 'latitude', + minDecimalPlaces: 3, + sameAs: 'https://schema.org/latitude', + requiredType: 'https://schema.org/Number', + }, price: { fieldName: 'price', maxDecimalPlaces: 2, @@ -21,20 +27,20 @@ describe('PrecisionRule', () => { model.hasSpecification = true; it('should target any field', () => { - const isTargeted = rule.isFieldTargeted(model, 'price'); + const isTargeted = rule.isFieldTargeted(model, 'latitude'); expect(isTargeted).toBe(true); }); - it('should return no error for a value with the correct number of decimal places', async () => { + it('should return no error for a value above a minDecimalPlaces threshold', async () => { const values = [ - -89.12, - 0.01, - 70.44, + -89.123456, + 5.01123, + 70.445234, ]; for (const value of values) { const data = { - price: value, + latitude: value, }; const nodeToTest = new ModelNode( '$', @@ -46,7 +52,51 @@ describe('PrecisionRule', () => { expect(errors.length).toBe(0); } }); + it('should return an error for a value below a minDecimalPlaces threshold', async () => { + const values = [ + 90.120, + -100.1, + 110, + ]; + for (const value of values) { + const data = { + latitude: value, + }; + const nodeToTest = new ModelNode( + '$', + data, + null, + model, + ); + const errors = await rule.validate(nodeToTest); + expect(errors.length).toBe(1); + expect(errors[0].type).toBe(ValidationErrorType.INVALID_PRECISION); + expect(errors[0].severity).toBe(ValidationErrorSeverity.SUGGESTION); + } + }); + + it('should return no error for a value below a maxDecimalPlaces threshold', async () => { + const values = [ + 10, + 10.50, + 9.99, + ]; + + for (const value of values) { + const data = { + price: value, + }; + const nodeToTest = new ModelNode( + '$', + data, + null, + model, + ); + const errors = await rule.validate(nodeToTest); + expect(errors.length).toBe(0); + } + }); it('should return an error for a value above a maxDecimalPlaces threshold', async () => { const values = [ 90.995, diff --git a/src/rules/core/precision-rule.js b/src/rules/core/precision-rule.js index a5bd85d..6fb62c2 100644 --- a/src/rules/core/precision-rule.js +++ b/src/rules/core/precision-rule.js @@ -12,6 +12,16 @@ module.exports = class PrecisionRule extends Rule { name: 'PrecisionRule', description: 'Validates that all properties are to the correct number of decimal places.', tests: { + belowMinimum: { + description: 'Raises a suggestion if a number\'s precision is below the minimum number of decimal places suggested for a property.', + message: 'The value of this property should have at least {{minDecimalPlaces}} decimal places. Note that this notice will also appear when trailing zeros have been truncated.', + sampleValues: { + minDecimalPlaces: 3, + }, + category: ValidationErrorCategory.DATA_QUALITY, + severity: ValidationErrorSeverity.SUGGESTION, + type: ValidationErrorType.INVALID_PRECISION, + }, aboveMaximum: { description: 'Raises a warning if a number\'s precision is above the maximum number of decimal places required for a property.', message: 'The value of this property must not exceed {{maxDecimalPlaces}} decimal places.', @@ -45,6 +55,23 @@ module.exports = class PrecisionRule extends Rule { return []; } + if ( + typeof fieldObj.minDecimalPlaces !== 'undefined' + && PrecisionHelper.getPrecision(fieldValue) < fieldObj.minDecimalPlaces + ) { + errors.push( + this.createError( + 'belowMinimum', + { + value: fieldValue, + path: node.getPath(field), + }, + { + minDecimalPlaces: fieldObj.minDecimalPlaces, + }, + ), + ); + } if (typeof fieldObj.maxDecimalPlaces !== 'undefined' && PrecisionHelper.getPrecision(fieldValue) > fieldObj.maxDecimalPlaces ) {