Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed performance/load-test.js
Empty file.
7 changes: 5 additions & 2 deletions src/classes/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ const Field = class {
return this.data.allowReferencing;
}

get valueConstraint() {
return this.data.valueConstraint;
}

get standard() {
return this.data.standard;
}
Expand Down Expand Up @@ -168,8 +172,7 @@ const Field = class {
}
if (!isEnum) {
// Is this a URL template?
// This processes most strings... so could be a bit intensive
if (PropertyHelper.isUrlTemplate(data)) {
if (this.valueConstraint === 'UriTemplate' && PropertyHelper.isUrlTemplate(data)) {
returnType = 'https://schema.org/Text';
} else if (DataModelHelper.getProperties(this.version).has(data)) {
returnType = 'https://schema.org/Property';
Expand Down
3 changes: 2 additions & 1 deletion src/errors/validation-error-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ const ValidationErrorType = {
TYPE_LIMITS_USE: 'type_limits_use',
WRONG_BASE_TYPE: 'wrong_base_type',
FIELD_NOT_ALLOWED: 'field_not_allowed',
BELOW_MIN_VALUE_INCLUSIVE: 'BELOW_MIN_VALUE_INCLUSIVE',
BELOW_MIN_VALUE_INCLUSIVE: 'below_min_value_inclusive',
VALUE_OUTWITH_CONSTRAINT: 'value_outwith_constraint',
};

module.exports = Object.freeze(ValidationErrorType);
4 changes: 4 additions & 0 deletions src/helpers/property.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ const PropertyHelper = class {
return false;
}

static isValidUUID(data) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(data);
}

static clearCache() {
this.enumCache = {};
this.propertyCache = {};
Expand Down
119 changes: 119 additions & 0 deletions src/rules/core/valueconstraint-rule-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const ValueConstraintRule = require('./valueconstraint-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('ValueConstraintRule', () => {
const rule = new ValueConstraintRule();

const model = new Model({
type: 'Schedule',
fields: {
uuid: {
fieldName: 'uuid',
valueConstraint: 'UUID',
},
uritemplate: {
fieldName: 'uritemplate',
valueConstraint: 'UriTemplate',
},
},
}, 'latest');
model.hasSpecification = true;

it('should target any field', () => {
const isTargeted = rule.isFieldTargeted(model, 'repeatCount');
expect(isTargeted).toBe(true);
});

it('should return no errors for a value that is a valid UUID', async () => {
const values = [
'123e4567-e89b-12d3-a456-426614174000',
'00000000-0000-0000-0000-000000000000',
];

for (const value of values) {
const data = {
uuid: value,
};
const nodeToTest = new ModelNode(
'$',
data,
null,
model,
);
const errors = await rule.validate(nodeToTest);
expect(errors.length).toBe(0);
}
});

it('should return an error when the value is not a valid UUID', async () => {
const values = [
'123E4567-E89B-12D3-A456-426614174000',
'000000000000000000000000000000000000',
'123e4567-e89b-12d3-a456-4266141740000',
'123e4567-e89b-12d3-a456-42661417400',
];

for (const value of values) {
const data = {
uuid: 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.VALUE_OUTWITH_CONSTRAINT);
expect(errors[0].severity).toBe(ValidationErrorSeverity.FAILURE);
}
});

it('should return no errors for a value that is a valid URI Template', async () => {
const values = [
'https://api.example.org/session-series/123/{startDate}',
];

for (const value of values) {
const data = {
uritemplate: value,
};
const nodeToTest = new ModelNode(
'$',
data,
null,
model,
);
const errors = await rule.validate(nodeToTest);
expect(errors.length).toBe(0);
}
});

it('should return an error when the value is not a valid URI Template', async () => {
const values = [
'https://api.example.org/session-series/123/',
];

for (const value of values) {
const data = {
uritemplate: 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.VALUE_OUTWITH_CONSTRAINT);
expect(errors[0].severity).toBe(ValidationErrorSeverity.FAILURE);
}
});
});
65 changes: 65 additions & 0 deletions src/rules/core/valueconstraint-rule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const Rule = require('../rule');
const PropertyHelper = require('../../helpers/property');
const ValidationErrorType = require('../../errors/validation-error-type');
const ValidationErrorCategory = require('../../errors/validation-error-category');
const ValidationErrorSeverity = require('../../errors/validation-error-severity');

module.exports = class ValueConstraintRule extends Rule {
constructor(options) {
super(options);
this.targetFields = '*';
this.meta = {
name: 'ValueConstraintRule',
description: 'Validates that all properties meet the associated valueConstraint parameter.',
tests: {
valueConstraint: {
description: 'Raises a failure if the value does not match the associated constraint',
message: 'The value of this property did not match the expected "{{valueConstraint}}" format.',
sampleValues: {
valueConstraint: 'UriTemplate',
},
category: ValidationErrorCategory.DATA_QUALITY,
severity: ValidationErrorSeverity.FAILURE,
type: ValidationErrorType.VALUE_OUTWITH_CONSTRAINT,
},
},
};
}

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 fieldObj.valueConstraint !== 'undefined'
&& ((fieldObj.valueConstraint === 'UriTemplate' && !PropertyHelper.isUrlTemplate(fieldValue))
|| (fieldObj.valueConstraint === 'UUID' && !PropertyHelper.isValidUUID(fieldValue)))) {
errors.push(
this.createError(
'valueConstraint',
{
fieldValue,
path: node.getPath(field),
},
{
valueConstraint: fieldObj.valueConstraint,
},
),
);
} else {
return [];
}

return errors;
}
};