Skip to content

Commit

Permalink
Extend JSON schema with 'uniqueObjects' keyword and validator
Browse files Browse the repository at this point in the history
Fixes #34
  • Loading branch information
mark-saeon committed Aug 15, 2018
1 parent 5ac7872 commit ed09811
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 15 deletions.
6 changes: 5 additions & 1 deletion ckanext/metadata/logic/json_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,16 @@ def add_error(node, path, message):
error.path.append(required_key)

elif error.schema_path[-1] == 'minItems':
error.path.append('__length')
error.path.append('__minItems')
error.message = 'Array has too few items'

elif error.schema_path[-1] == 'not' and error.validator_value == {}:
error.message = 'This key may not be present in the dictionary'

elif error.schema_path[-1] == 'uniqueObjects':
error.path.append('__uniqueObjects')
error.message = 'Array has non-unique objects'

add_error(errors, error.path, error.message)

return errors
14 changes: 14 additions & 0 deletions ckanext/metadata/logic/json_validator_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ def role_validator(validator, role_name, instance, schema):
yield jsonschema.ValidationError("Role validation has not been implemented yet")


def unique_objects_validator(validator, key_properties, instance, schema):
"""
"uniqueObjects" keyword validator: for an array comprising objects, this checks that the objects
are unique with respect to the named properties.
"""
if validator.is_type(instance, 'array') and validator.is_type(schema.get('items', {}), 'object'):
key_objects = []
for obj in instance:
key_object = {k: v for k, v in obj.items() if k in key_properties}
if key_object in key_objects:
yield jsonschema.ValidationError(_("%r has non-unique objects") % (instance,))
key_objects += [key_object]


@checks_format('doi')
def is_doi(instance):
if not isinstance(instance, basestring):
Expand Down
3 changes: 2 additions & 1 deletion ckanext/metadata/logic/workflow_validator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# encoding: utf-8

from ckanext.metadata.logic.json_validator import JSONValidator
from ckanext.metadata.logic.json_validator_functions import objectid_validator, role_validator
from ckanext.metadata.logic.json_validator_functions import objectid_validator, role_validator, unique_objects_validator


class WorkflowValidator(JSONValidator):
Expand All @@ -14,6 +14,7 @@ def _validators(cls):
return {
'objectid': objectid_validator,
'role': role_validator,
'uniqueObjects': unique_objects_validator,
}

@classmethod
Expand Down
2 changes: 2 additions & 0 deletions ckanext/metadata/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from paste.deploy.converters import asbool
import pkg_resources
from collections import deque
import traceback

from ckan.tests import factories as ckan_factories
from ckan.tests.helpers import FunctionalTestBase, call_action
Expand Down Expand Up @@ -194,6 +195,7 @@ def _test_action(self, action_name, should_error=False, exception_class=tk.Valid
else:
result = e.message
except Exception, e:
traceback.print_exc()
assert False, "Unexpected exception %s: %s" % (type(e), e)
else:
if should_error:
Expand Down
21 changes: 10 additions & 11 deletions ckanext/metadata/tests/test_metadata_record_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ def test_workflow_transition_published(self):
*jsonpatch_ids,
**{
'errors/identifier': 'This key may not be present in the dictionary',
'quality_control/__length': 'Array has too few items',
'quality_control/__minItems': 'Array has too few items',
})
assert_package_has_extra(metadata_record['id'], 'workflow_state_id', '')

Expand All @@ -799,16 +799,15 @@ def test_workflow_transition_published(self):
value=json.dumps({"userid": self.normal_user['id'], "date": "2018-08-15"}),
)['id']]

# TODO: the following depends on implementation of userid uniqueness checking
# self._test_action('metadata_record_workflow_state_transition', id=metadata_record['id'],
# workflow_state_id=workflow_state_published['id'])
# self._assert_workflow_activity_logged(metadata_record['id'],
# workflow_state_published['id'],
# *jsonpatch_ids,
# **{
# 'quality_control': 'uniqueness of user id...',
# })
# assert_package_has_extra(metadata_record['id'], 'workflow_state_id', '')
self._test_action('metadata_record_workflow_state_transition', id=metadata_record['id'],
workflow_state_id=workflow_state_published['id'])
self._assert_workflow_activity_logged(metadata_record['id'],
workflow_state_published['id'],
*jsonpatch_ids,
**{
'quality_control/__uniqueObjects': 'Array has non-unique objects',
})
assert_package_has_extra(metadata_record['id'], 'workflow_state_id', '')

jsonpatch_ids += [call_action('metadata_record_workflow_annotation_create', id=metadata_record['id'],
path='/quality_control',
Expand Down
3 changes: 1 addition & 2 deletions examples/workflow_state_published_rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@
"required": ["userid", "date"]
},
"minItems": 2,
"uniqueItems": true,
"$comment": "to fix: as it stands this sub-schema will not enforce different users, because of the date property"
"uniqueObjects": ["userid"]
},
"validated": {
"type": "boolean",
Expand Down

0 comments on commit ed09811

Please sign in to comment.