diff --git a/solar/solar/core/validation.py b/solar/solar/core/validation.py index 623a950e..01045f8a 100644 --- a/solar/solar/core/validation.py +++ b/solar/solar/core/validation.py @@ -1,4 +1,4 @@ -from jsonschema import validate, ValidationError +from jsonschema import validate, ValidationError, SchemaError def schema_input_type(schema): @@ -13,38 +13,64 @@ def schema_input_type(schema): return 'simple' -def construct_jsonschema(schema): +def _construct_jsonschema(schema, definition_base=''): """Construct jsonschema from our metadata input schema. :param schema: :return: """ - if schema == 'str': - return {'type': 'string'} + return {'type': 'string'}, {} if schema == 'str!': - return {'type': 'string', 'minLength': 1} + return {'type': 'string', 'minLength': 1}, {} if schema == 'int' or schema == 'int!': - return {'type': 'number'} + return {'type': 'number'}, {} if isinstance(schema, list): + items, definitions = _construct_jsonschema(schema[0], definition_base=definition_base) + return { 'type': 'array', - 'items': construct_jsonschema(schema[0]), - } + 'items': items, + }, definitions if isinstance(schema, dict): - return { + properties = {} + definitions = {} + + for k, v in schema.items(): + if isinstance(v, dict) or isinstance(v, list): + key = '{}_{}'.format(definition_base, k) + properties[k] = {'$ref': '#/definitions/{}'.format(key)} + definitions[key], new_definitions = _construct_jsonschema(v, definition_base=key) + else: + properties[k], new_definitions = _construct_jsonschema(v, definition_base=definition_base) + + definitions.update(new_definitions) + + required = [k for k, v in schema.items() if + isinstance(v, basestring) and v.endswith('!')] + + ret = { 'type': 'object', - 'properties': { - k: construct_jsonschema(v) for k, v in schema.items() - }, - 'required': [k for k, v in schema.items() if - isinstance(v, basestring) and v.endswith('!')], + 'properties': properties, } + if required: + ret['required'] = required + + return ret, definitions + + +def construct_jsonschema(schema): + jsonschema, definitions = _construct_jsonschema(schema) + + jsonschema['definitions'] = definitions + + return jsonschema + def validate_input(value, jsonschema=None, schema=None): """Validate single input according to schema. @@ -54,13 +80,16 @@ def validate_input(value, jsonschema=None, schema=None): :param schema: Our custom, simplified schema :return: list with errors """ + if jsonschema is None: + jsonschema = construct_jsonschema(schema) try: - if jsonschema: - validate(value, jsonschema) - else: - validate(value, construct_jsonschema(schema)) + validate(value, jsonschema) except ValidationError as e: return [e.message] + except: + print 'jsonschema', jsonschema + print 'value', value + raise def validate_resource(r): diff --git a/solar/solar/test/test_validation.py b/solar/solar/test/test_validation.py index 0b4afa6d..da0da6d0 100644 --- a/solar/solar/test/test_validation.py +++ b/solar/solar/test/test_validation.py @@ -102,5 +102,73 @@ input: errors = sv.validate_resource(r) self.assertListEqual(errors.keys(), ['values']) + def test_complex_input(self): + sample_meta_dir = self.make_resource_meta(""" +id: sample +handler: ansible +version: 1.0.0 +input: + values: + schema: {l: [{a: int}]} + value: {l: [{a: 1}]} + """) + + r = self.create_resource( + 'r', sample_meta_dir, { + 'values': { + 'l': [{'a': 1}], + } + } + ) + errors = sv.validate_resource(r) + self.assertEqual(errors, {}) + + r.update({ + 'values': { + 'l': [{'a': 'x'}], + } + }) + errors = sv.validate_resource(r) + self.assertListEqual(errors.keys(), ['values']) + + r.update({'values': {'l': [{'a': 1, 'c': 3}]}}) + errors = sv.validate_resource(r) + self.assertEqual(errors, {}) + + def test_more_complex_input(self): + sample_meta_dir = self.make_resource_meta(""" +id: sample +handler: ansible +version: 1.0.0 +input: + values: + schema: {l: [{a: int}], d: {x: [int]}} + value: {l: [{a: 1}], d: {x: [1, 2]}} + """) + + r = self.create_resource( + 'r', sample_meta_dir, { + 'values': { + 'l': [{'a': 1}], + 'd': {'x': [1, 2]} + } + } + ) + errors = sv.validate_resource(r) + self.assertEqual(errors, {}) + + r.update({ + 'values': { + 'l': [{'a': 1}], + 'd': [] + } + }) + errors = sv.validate_resource(r) + self.assertListEqual(errors.keys(), ['values']) + + r.update({'values': {'a': 1, 'c': 3}}) + errors = sv.validate_resource(r) + self.assertEqual(errors, {}) + if __name__ == '__main__': unittest.main()