Skip to content

Commit

Permalink
data-quality: Ensure Schedule templates (id & url) are valid UriTempl…
Browse files Browse the repository at this point in the history
…ates
  • Loading branch information
Jared Parnell committed Mar 24, 2021
1 parent 00faf5f commit 8d48385
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/classes/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ const Field = class {
return this.data.minValueInclusive;
}

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

get standard() {
return this.data.standard;
}
Expand Down
127 changes: 127 additions & 0 deletions src/rules/data-quality/schedule-templates-are-valid-rule-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
const ScheduleTemplatesValid = require('./schedule-templates-are-valid-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('ScheduleTemplatesValid', () => {
let model;
let rule;

beforeEach(() => {
model = new Model({
type: 'Schedule',
fields: {
idTemplate: {
fieldName: 'idTemplate',
sameAs: 'https://openactive.io/idTemplate',
requiredType: 'https://schema.org/Text',
example: 'https://api.example.org/session-series/123/{startDate}',
description: [
'An RFC6570 compliant URI template that can be used to generate a unique identifier (`@id`) for every event described by the schedule. This property is required if the data provider is supporting third-party booking via the Open Booking API, or providing complimentary individual `subEvent`s.',
],
valueConstraint: 'UriTemplate',
},
urlTemplate: {
fieldName: 'urlTemplate',
sameAs: 'https://schema.org/urlTemplate',
requiredType: 'https://schema.org/Text',
example: 'https://example.org/session-series/123/{startDate}',
description: [
'An RFC6570 compliant URI template that can be used to generate a unique `url` for every event described by the schedule. This property is required if the data provider wants to provide participants with a unique URL to book to attend an event.',
],
valueConstraint: 'UriTemplate',
},
},
}, 'latest');
rule = new ScheduleTemplatesValid();
});

it('should target idTemplate and urlTemplate in Schedule model', () => {
let isTargeted = rule.isFieldTargeted(model, 'idTemplate');
expect(isTargeted).toBe(true);

isTargeted = rule.isFieldTargeted(model, 'urlTemplate');
expect(isTargeted).toBe(true);
});

it('should return no errors if the urlTemplate is valid', async () => {
const data = {
'@type': 'Schedule',
urlTemplate: 'https://api.example.org/session-series/123/{startDate}',
};

const nodeToTest = new ModelNode(
'$',
data,
null,
model,
);

const errors = await rule.validate(nodeToTest);

expect(errors.length).toBe(0);
});

it('should return no errors if the idTemplate is valid', async () => {
const data = {
'@type': 'Schedule',
idTemplate: 'https://api.example.org/session-series/123/{startDate}',
};

const nodeToTest = new ModelNode(
'$',
data,
null,
model,
);

const errors = await rule.validate(nodeToTest);

expect(errors.length).toBe(0);
});

it('should return errors if the urlTemplate is not valid', async () => {
const data = {
'@type': 'Schedule',
urlTemplate: 'htts://api.example.org/session-series/123/',
};

const nodeToTest = new ModelNode(
'$',
data,
null,
model,
);

const errors = await rule.validate(nodeToTest);

expect(errors.length).toBe(1);
for (const error of errors) {
expect(error.type).toBe(ValidationErrorType.INVALID_FORMAT);
expect(error.severity).toBe(ValidationErrorSeverity.FAILURE);
}
});

it('should return errors if the idTemplate is not valid', async () => {
const data = {
'@type': 'Schedule',
idTemplate: 'htts://api.example.org/session-series/123/',
};

const nodeToTest = new ModelNode(
'$',
data,
null,
model,
);

const errors = await rule.validate(nodeToTest);

expect(errors.length).toBe(1);
for (const error of errors) {
expect(error.type).toBe(ValidationErrorType.INVALID_FORMAT);
expect(error.severity).toBe(ValidationErrorSeverity.FAILURE);
}
});
});
52 changes: 52 additions & 0 deletions src/rules/data-quality/schedule-templates-are-valid-rule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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 ScheduleTemplatesValid extends Rule {
constructor(options) {
super(options);
this.targetFields = { Schedule: ['urlTemplate', 'idTemplate'] };
this.meta = {
name: 'ScheduleTemplatesValid',
description: 'Validates that the urlTemplate is of the correct format',
tests: {
default: {
description: 'Validates that the @context url matches the correct scheme and subdomain (https://openactive.io).',
message: 'When referencing the OpenActive domain, you must start your URLs with https://openactive.io.',
category: ValidationErrorCategory.CONFORMANCE,
severity: ValidationErrorSeverity.FAILURE,
type: ValidationErrorType.INVALID_FORMAT,
},
},
};
}

validateField(node, field) {
const fieldObj = node.model.getField(field);
const fieldValue = node.getValue(field);

if (typeof fieldValue !== 'string') {
return [];
}

const errors = [];

if (typeof fieldObj.valueConstraint !== 'undefined'
&& (fieldObj.valueConstraint === 'UriTemplate'
&& !PropertyHelper.isUrlTemplate(fieldValue))) {
errors.push(
this.createError(
'default',
{
fieldValue,
path: node.getPath(field),
},
),
);
}

return errors;
}
};

0 comments on commit 8d48385

Please sign in to comment.