From 27e77657c2e02e35225c7d49744d53a9cf3653ce Mon Sep 17 00:00:00 2001 From: Jared Parnell Date: Tue, 2 Mar 2021 16:00:31 +0000 Subject: [PATCH 1/4] rules: format: Add lat/lng precision rule --- .../format/lat-lng-precision-rule-spec.js | 85 +++++++++++++++++++ src/rules/format/lat-lng-precision-rule.js | 69 +++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/rules/format/lat-lng-precision-rule-spec.js create mode 100644 src/rules/format/lat-lng-precision-rule.js diff --git a/src/rules/format/lat-lng-precision-rule-spec.js b/src/rules/format/lat-lng-precision-rule-spec.js new file mode 100644 index 0000000..02d8806 --- /dev/null +++ b/src/rules/format/lat-lng-precision-rule-spec.js @@ -0,0 +1,85 @@ +const LatLngPrecisionRule = require('./lat-lng-precision-rule'); +const Model = require('../../classes/model'); +const ModelNode = require('../../classes/model-node'); +const ValidationErrorType = require('../../errors/validation-error-type'); +const ValidationErrorSeverity = require('../../errors/validation-error-severity'); + +describe('LatLngPrecisionRule', () => { + const rule = new LatLngPrecisionRule(); + + const model = new Model({ + type: 'GeoCoordinates', + fields: { + latitude: { + fieldName: 'latitude', + minDecimalPlaces: 2, + sameAs: 'https://schema.org/latitude', + requiredType: 'https://schema.org/Number', + }, + longitude: { + fieldName: 'longitude', + maxDecimalPlaces: 2, + sameAs: 'https://schema.org/longitude', + requiredType: 'https://schema.org/Number', + }, + }, + }, 'latest'); + model.hasSpecification = true; + + it('should target lat / long fields of GeoCoordinates', () => { + let isTargeted = rule.isFieldTargeted(model, 'latitude'); + expect(isTargeted).toBe(true); + + isTargeted = rule.isFieldTargeted(model, 'longitude'); + expect(isTargeted).toBe(true); + + isTargeted = rule.isFieldTargeted(model, 'type'); + expect(isTargeted).toBe(false); + }); + + it('should return no error for a value above a minDecimalPlaces threshold', async () => { + const values = [ + -89.123456, + 5.01123, + 70.445234, + ]; + + for (const value of values) { + const data = { + latitude: value, + longitude: 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 below a minDecimalPlaces threshold', async () => { + const values = [ + 90.1, + -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.FAILURE); + } + }); +}); diff --git a/src/rules/format/lat-lng-precision-rule.js b/src/rules/format/lat-lng-precision-rule.js new file mode 100644 index 0000000..aa61193 --- /dev/null +++ b/src/rules/format/lat-lng-precision-rule.js @@ -0,0 +1,69 @@ +const Rule = require('../rule'); +const PrecisionHelper = require('../../helpers/precision'); +const ValidationErrorType = require('../../errors/validation-error-type'); +const ValidationErrorCategory = require('../../errors/validation-error-category'); +const ValidationErrorSeverity = require('../../errors/validation-error-severity'); + +module.exports = class LatLngPrecisionRule extends Rule { + constructor(options) { + super(options); + this.targetFields = { + GeoCoordinates: ['latitude', 'longitude'], + }; + this.meta = { + name: 'LatLngPrecisionRule', + description: 'Validates that latitude and longitude 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: 2, + }, + category: ValidationErrorCategory.CONFORMANCE, + severity: ValidationErrorSeverity.FAILURE, + type: ValidationErrorType.INVALID_PRECISION, + }, + }, + }; + } + + validateField(node, field) { + // Don't do this check for models that we don't actually have a spec for + if (!node.model.hasSpecification) { + return []; + } + if (!node.model.hasField(field)) { + return []; + } + + const errors = []; + + // Get the field object + const fieldObj = node.model.getField(field); + const fieldValue = node.getMappedValue(field); + + if (typeof fieldValue !== 'number') { + 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, + }, + ), + ); + } + return errors; + } +}; From 84bdeda9719e0fac0d955d858d0ffbedab485eb5 Mon Sep 17 00:00:00 2001 From: Jared Parnell Date: Tue, 2 Mar 2021 16:01:07 +0000 Subject: [PATCH 2/4] rules: core: Remove lat precision rule --- src/rules/core/precision-rule-spec.js | 62 +++------------------------ src/rules/core/precision-rule.js | 27 ------------ 2 files changed, 6 insertions(+), 83 deletions(-) diff --git a/src/rules/core/precision-rule-spec.js b/src/rules/core/precision-rule-spec.js index 5907b95..33b2bca 100644 --- a/src/rules/core/precision-rule-spec.js +++ b/src/rules/core/precision-rule-spec.js @@ -10,12 +10,6 @@ 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, @@ -27,60 +21,15 @@ describe('PrecisionRule', () => { model.hasSpecification = true; it('should target any field', () => { - const isTargeted = rule.isFieldTargeted(model, 'latitude'); + const isTargeted = rule.isFieldTargeted(model, 'price'); expect(isTargeted).toBe(true); }); - it('should return no error for a value above a minDecimalPlaces threshold', async () => { + it('should return no error for a value with the correct number of decimal places', async () => { const values = [ - -89.123456, - 5.01123, - 70.445234, - ]; - - 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(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, + -89.12, + 0.01, + 70.44, ]; for (const value of values) { @@ -97,6 +46,7 @@ describe('PrecisionRule', () => { 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 6fb62c2..a5bd85d 100644 --- a/src/rules/core/precision-rule.js +++ b/src/rules/core/precision-rule.js @@ -12,16 +12,6 @@ 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.', @@ -55,23 +45,6 @@ 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 ) { From 9a20fb21be8e52725ae6d78ab0357153bb49fe63 Mon Sep 17 00:00:00 2001 From: Jared Parnell Date: Tue, 2 Mar 2021 16:10:22 +0000 Subject: [PATCH 3/4] rules: format: Fix test paremeters and expectations --- src/rules/format/lat-lng-precision-rule-spec.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/rules/format/lat-lng-precision-rule-spec.js b/src/rules/format/lat-lng-precision-rule-spec.js index 02d8806..36637ea 100644 --- a/src/rules/format/lat-lng-precision-rule-spec.js +++ b/src/rules/format/lat-lng-precision-rule-spec.js @@ -18,7 +18,7 @@ describe('LatLngPrecisionRule', () => { }, longitude: { fieldName: 'longitude', - maxDecimalPlaces: 2, + minDecimalPlaces: 2, sameAs: 'https://schema.org/longitude', requiredType: 'https://schema.org/Number', }, @@ -69,6 +69,7 @@ describe('LatLngPrecisionRule', () => { for (const value of values) { const data = { latitude: value, + longitude: value, }; const nodeToTest = new ModelNode( '$', @@ -77,9 +78,11 @@ describe('LatLngPrecisionRule', () => { model, ); const errors = await rule.validate(nodeToTest); - expect(errors.length).toBe(1); + expect(errors.length).toBe(2); expect(errors[0].type).toBe(ValidationErrorType.INVALID_PRECISION); expect(errors[0].severity).toBe(ValidationErrorSeverity.FAILURE); + expect(errors[1].type).toBe(ValidationErrorType.INVALID_PRECISION); + expect(errors[1].severity).toBe(ValidationErrorSeverity.FAILURE); } }); }); From 689a51fe80f36f577517f7106c8067e7081807a0 Mon Sep 17 00:00:00 2001 From: Jared Parnell Date: Tue, 2 Mar 2021 16:48:43 +0000 Subject: [PATCH 4/4] Revert "rules: core: Remove lat precision rule" This reverts commit c917ea56c3e90ab3541d119467b375f10b4917b3. --- src/rules/core/precision-rule-spec.js | 62 ++++++++++++++++++++++++--- src/rules/core/precision-rule.js | 27 ++++++++++++ 2 files changed, 83 insertions(+), 6 deletions(-) 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 ) {