-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/id reference for feeds validation #412
base: master
Are you sure you want to change the base?
Changes from all commits
c2c89e9
21f316e
1c1947d
b3d0a0f
edb157a
58238e6
ee9618c
97ca8a3
48f2974
ad93a96
da9f503
cc59db2
e04f092
2abee72
c006451
c0dc390
1cb989b
b7a7467
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -181,4 +181,8 @@ const ModelNode = class { | |
} | ||
}; | ||
|
||
/** | ||
* @typedef {ModelNode} ModelNodeType | ||
*/ | ||
|
||
module.exports = ModelNode; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
const Model = require('../../classes/model'); | ||
const ModelNode = require('../../classes/model-node'); | ||
const OptionsHelper = require('../../helpers/options'); | ||
const IdReferencesForCertainFeedsRule = require('./id-references-for-certain-feeds-rule'); | ||
const ValidationErrorType = require('../../errors/validation-error-type'); | ||
const ValidationErrorSeverity = require('../../errors/validation-error-severity'); | ||
|
||
describe('IdReferencesForCertainFeedsRule', () => { | ||
const rule = new IdReferencesForCertainFeedsRule(); | ||
|
||
describe('for kind FacilityUse/Slot or IndividualFacilityUse/Slot feeds', () => { | ||
const model = new Model({ | ||
type: 'Slot', | ||
validationMode: { | ||
RPDEFeed: 'feed', | ||
BookableRPDEFeed: 'feed', | ||
}, | ||
rpdeKind: [ | ||
'FacilityUse/Slot', | ||
'IndividualFacilityUse/Slot', | ||
], | ||
imperativeConfigurationWithContext: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given this same property is being used here for both RPDE kind and parent, suggest this data models property should be renamed
This should make the configuration clearer |
||
feed: { | ||
'FacilityUse/Slot': { | ||
referencedFields: [ | ||
'facilityUse', | ||
], | ||
}, | ||
'IndividualFacilityUse/Slot': { | ||
referencedFields: [ | ||
'facilityUse', | ||
], | ||
}, | ||
}, | ||
}, | ||
imperativeConfiguration: { | ||
feed: {}, | ||
}, | ||
}, 'latest'); | ||
model.hasSpecification = true; | ||
it('should validate that facilityUse within the Slot is a ID reference, and not an object', async () => { | ||
const options = new OptionsHelper({ validationMode: 'RPDEFeed', rpdeKind: 'FacilityUse/Slot' }); | ||
|
||
const data = { | ||
'@context': 'https://openactive.io/', | ||
'@type': 'Slot', | ||
facilityUse: 'https://example.com/item/2', | ||
}; | ||
|
||
const nodeToTest = new ModelNode( | ||
'$', | ||
data, | ||
null, | ||
model, | ||
options, | ||
); | ||
const errors = await rule.validate(nodeToTest); | ||
|
||
expect(errors.length).toBe(0); | ||
}); | ||
it('should error when the facilityUse within the Slot is an object not an ID reference', async () => { | ||
const options = new OptionsHelper({ validationMode: 'RPDEFeed', rpdeKind: 'FacilityUse/Slot' }); | ||
|
||
const data = { | ||
'@context': 'https://openactive.io/', | ||
'@type': 'Slot', | ||
facilityUse: { | ||
'@id': 'https://example.com/item/2', | ||
}, | ||
}; | ||
|
||
const nodeToTest = new ModelNode( | ||
'$', | ||
data, | ||
null, | ||
model, | ||
options, | ||
); | ||
const errors = await rule.validate(nodeToTest); | ||
|
||
expect(errors.length).toBe(1); | ||
expect(errors[0].type).toBe(ValidationErrorType.FIELD_NOT_ID_REFERENCE); | ||
expect(errors[0].severity).toBe(ValidationErrorSeverity.FAILURE); | ||
}); | ||
}); | ||
describe('for kind ScheduledSessions feeds', () => { | ||
const model = new Model({ | ||
type: 'ScheduledSession', | ||
validationMode: { | ||
RPDEFeed: 'feed', | ||
BookableRPDEFeed: 'feed', | ||
}, | ||
rpdeKind: [ | ||
'ScheduledSession', | ||
'ScheduledSession.SessionSeries', | ||
], | ||
imperativeConfigurationWithContext: { | ||
feed: { | ||
ScheduledSession: { | ||
referencedFields: [ | ||
'superEvent', | ||
], | ||
}, | ||
}, | ||
}, | ||
imperativeConfiguration: { | ||
feed: {}, | ||
}, | ||
}, 'latest'); | ||
model.hasSpecification = true; | ||
|
||
it('should validate that superEvent within the ScheduledSession is a ID reference, and not an object', async () => { | ||
const options = new OptionsHelper({ validationMode: 'RPDEFeed', rpdeKind: 'ScheduledSession' }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apologies, I think we might have crossed wires slightly in our design chat on this... There are some challenges with specifying
So as an alternative proposal, I'd recommend we make the following changes:
So hence following example should pass the validator: {
"state": "updated",
"kind": "ScheduledSession",
"id": "C5EE1E55-2DE6-44F7-A865-42F268A82C63",
"modified": 1521565719,
"data": {
"@context": [
"https://openactive.io/",
"https://openactive.io/ns-beta"
],
"@type": "ScheduledSession",
"@id": "https://opensessions.io/api/session-series/1402CBP20150217#/subEvent/C5EE1E55-2DE6-44F7-A865-42F268A82C63",
"identifier": "C5EE1E55-2DE6-44F7-A865-42F268A82C63",
"superEvent": "https://opensessions.io/api/session-series/1402CBP20150217",
"startDate": "2016-05-09T18:15:00Z",
"endDate": "2016-05-09T19:15:00Z",
"duration": "PT1H",
"eventStatus": "https://schema.org/EventScheduled",
"maximumAttendeeCapacity": 10,
"remainingAttendeeCapacity": 0,
"url": "https://bookwhen.com/hulafit/e/ev-ssyp-20160510011500?r=oa"
}
} |
||
|
||
const data = { | ||
'@context': 'https://openactive.io/', | ||
'@type': 'ScheduledSession', | ||
superEvent: 'https://example.com/item/2', | ||
}; | ||
|
||
const nodeToTest = new ModelNode( | ||
'$', | ||
data, | ||
null, | ||
model, | ||
options, | ||
); | ||
const errors = await rule.validate(nodeToTest); | ||
|
||
expect(errors.length).toBe(0); | ||
}); | ||
it('should error when superEvent within the ScheduledSession is an object not an ID reference', async () => { | ||
const options = new OptionsHelper({ validationMode: 'RPDEFeed', rpdeKind: 'ScheduledSession' }); | ||
|
||
const data = { | ||
'@context': 'https://openactive.io/', | ||
'@type': 'ScheduledSession', | ||
superEvent: { | ||
'@id': 'https://example.com/item/2', | ||
}, | ||
}; | ||
|
||
const nodeToTest = new ModelNode( | ||
'$', | ||
data, | ||
null, | ||
model, | ||
options, | ||
); | ||
const errors = await rule.validate(nodeToTest); | ||
|
||
expect(errors.length).toBe(1); | ||
expect(errors[0].type).toBe(ValidationErrorType.FIELD_NOT_ID_REFERENCE); | ||
expect(errors[0].severity).toBe(ValidationErrorSeverity.FAILURE); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
const Rule = require('../rule'); | ||
const PropertyHelper = require('../../helpers/property'); | ||
const ValidationErrorCategory = require('../../errors/validation-error-category'); | ||
const ValidationErrorSeverity = require('../../errors/validation-error-severity'); | ||
const validationErrorType = require('../../errors/validation-error-type'); | ||
|
||
/** @typedef {import('../../classes/model-node').ModelNodeType} ModelNode */ | ||
|
||
class IdReferencesForCertainFeedsRule extends Rule { | ||
constructor(options) { | ||
super(options); | ||
this.targetValidationModes = [ | ||
'RPDEFeed', | ||
'BookableRPDEFeed', | ||
]; | ||
this.targetRpdeKinds = [ | ||
'FacilityUse/Slot', | ||
'IndividualFacilityUse/Slot', | ||
'ScheduledSession', | ||
]; | ||
this.targetModels = '*'; | ||
this.meta = { | ||
name: 'IdReferencesForCertainFeedsRule', | ||
description: 'Validates that certain properties in the specified feeds are an ID reference and not objects', | ||
tests: { | ||
default: { | ||
description: `Raises a failure if properties within the data object in a RPDE Feed is not an ID reference | ||
(ie a reference to the object and not the object itself)`, | ||
message: 'For {{rpdeKind}} feeds, {{field}} must be an compact ID reference, not the object representing the data itself', | ||
sampleValues: { | ||
rpdeKind: 'FacilityUse/Slot', | ||
field: 'facilityUse', | ||
}, | ||
category: ValidationErrorCategory.CONFORMANCE, | ||
severity: ValidationErrorSeverity.FAILURE, | ||
type: validationErrorType.FIELD_NOT_ID_REFERENCE, | ||
}, | ||
}, | ||
}; | ||
} | ||
|
||
/** | ||
* @param {ModelNode} node | ||
*/ | ||
validateModel(node) { | ||
// Don't do this check for models that we don't actually have a spec for or for models that aren't JSON-LD | ||
if (!node.model.hasSpecification || !node.model.isJsonLd) { | ||
return []; | ||
} | ||
|
||
const errors = []; | ||
const referencedFields = node.model.getReferencedFields(node.options.validationMode, { rpdeKind: node.options.rpdeKind }); | ||
for (const field of referencedFields) { | ||
const fieldValue = node.getValue(field); | ||
|
||
if (typeof fieldValue !== 'string' || !PropertyHelper.isUrl(fieldValue)) { | ||
errors.push( | ||
this.createError( | ||
'default', | ||
{ | ||
fieldValue, | ||
path: node.getPath(field), | ||
}, | ||
{ referencedField: field }, | ||
), | ||
); | ||
} | ||
} | ||
|
||
return errors; | ||
} | ||
} | ||
|
||
module.exports = IdReferencesForCertainFeedsRule; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For these rules, see the refactoring of
#407
.Recommend refactoring to separate the request/response concerns (which should be covered by the validation mode), so that these rules rely purely on the validation files.
Note although #407 limits the
@id
reference rules to apply only to certain validation modes as an initial release, once this PR is completed suggest that we update them to apply to all validation modes (i.e.targetValidationModes = '*'
)