Specification of which property/argument violated contract was added
When some property or argument value violated its contract it was impossible to tell which property/argument caused the exception. This change adds prefix to ContractViolationException message that tells not only which property or argument failed to validate but for composite properties also the path within the data. For example if we change ApacheHttpServer name property contract from $.string() to $.int() we will get [io.murano.Environment.applications[0]] [io.murano.apps.apache.ApacheHttpServer.name] Value 'ApacheHttpServer' violates int() contract Also for check() contracts ability to provide custom error message was added Change-Id: I6953ec84140f4bed3d50aa181244f89682b2b2fb Closes-Bug: #1496044
This commit is contained in:
parent
c9cc6a95a2
commit
1d72b9dd61
@ -83,7 +83,17 @@ class DslContractSyntaxError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class ContractViolationException(Exception):
|
class ContractViolationException(Exception):
|
||||||
pass
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(ContractViolationException, self).__init__(*args, **kwargs)
|
||||||
|
self._path = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return self._path
|
||||||
|
|
||||||
|
@path.setter
|
||||||
|
def path(self, value):
|
||||||
|
self._path = value
|
||||||
|
|
||||||
|
|
||||||
class ValueIsMissingError(Exception):
|
class ValueIsMissingError(Exception):
|
||||||
|
@ -67,7 +67,8 @@ class MuranoClass(dsl_types.MuranoClass):
|
|||||||
|
|
||||||
properties = data.get('Properties') or {}
|
properties = data.get('Properties') or {}
|
||||||
for property_name, property_spec in properties.iteritems():
|
for property_name, property_spec in properties.iteritems():
|
||||||
spec = typespec.PropertySpec(property_spec, type_obj)
|
spec = typespec.PropertySpec(
|
||||||
|
property_name, property_spec, type_obj)
|
||||||
type_obj.add_property(property_name, spec)
|
type_obj.add_property(property_name, spec)
|
||||||
|
|
||||||
methods = data.get('Methods') or data.get('Workflow') or {}
|
methods = data.get('Methods') or data.get('Workflow') or {}
|
||||||
|
@ -69,7 +69,7 @@ class MuranoMethod(dsl_types.MuranoMethod):
|
|||||||
raise ValueError()
|
raise ValueError()
|
||||||
name = record.keys()[0]
|
name = record.keys()[0]
|
||||||
self._arguments_scheme[name] = typespec.ArgumentSpec(
|
self._arguments_scheme[name] = typespec.ArgumentSpec(
|
||||||
record[name], self.murano_class)
|
self.name, name, record[name], self.murano_class)
|
||||||
self._yaql_function_definition = \
|
self._yaql_function_definition = \
|
||||||
yaql_integration.build_wrapper_function_definition(self)
|
yaql_integration.build_wrapper_function_definition(self)
|
||||||
|
|
||||||
|
@ -47,7 +47,8 @@ class TypeScheme(object):
|
|||||||
return int(value)
|
return int(value)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise exceptions.ContractViolationException(
|
raise exceptions.ContractViolationException(
|
||||||
'Value {0} violates int() contract'.format(value))
|
'Value {0} violates int() contract'.format(
|
||||||
|
format_scalar(value)))
|
||||||
|
|
||||||
@specs.parameter('value', nullable=True)
|
@specs.parameter('value', nullable=True)
|
||||||
@specs.method
|
@specs.method
|
||||||
@ -60,7 +61,8 @@ class TypeScheme(object):
|
|||||||
return unicode(value)
|
return unicode(value)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise exceptions.ContractViolationException(
|
raise exceptions.ContractViolationException(
|
||||||
'Value {0} violates string() contract'.format(value))
|
'Value {0} violates string() contract'.format(
|
||||||
|
format_scalar(value)))
|
||||||
|
|
||||||
@specs.parameter('value', nullable=True)
|
@specs.parameter('value', nullable=True)
|
||||||
@specs.method
|
@specs.method
|
||||||
@ -89,14 +91,17 @@ class TypeScheme(object):
|
|||||||
|
|
||||||
@specs.parameter('value', nullable=True)
|
@specs.parameter('value', nullable=True)
|
||||||
@specs.parameter('predicate', yaqltypes.Lambda(with_context=True))
|
@specs.parameter('predicate', yaqltypes.Lambda(with_context=True))
|
||||||
|
@specs.parameter('msg', yaqltypes.String(nullable=True))
|
||||||
@specs.method
|
@specs.method
|
||||||
def check(value, predicate):
|
def check(value, predicate, msg=None):
|
||||||
if isinstance(value, TypeScheme.ObjRef) or predicate(
|
if isinstance(value, TypeScheme.ObjRef) or predicate(
|
||||||
root_context.create_child_context(), value):
|
root_context.create_child_context(), value):
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
raise exceptions.ContractViolationException(
|
if not msg:
|
||||||
"Value {0} doesn't match predicate".format(value))
|
msg = "Value {0} doesn't match predicate".format(
|
||||||
|
format_scalar(value))
|
||||||
|
raise exceptions.ContractViolationException(msg)
|
||||||
|
|
||||||
@specs.parameter('obj', TypeScheme.ObjRef, nullable=True)
|
@specs.parameter('obj', TypeScheme.ObjRef, nullable=True)
|
||||||
@specs.name('owned')
|
@specs.name('owned')
|
||||||
@ -117,8 +122,7 @@ class TypeScheme(object):
|
|||||||
p = p.owner
|
p = p.owner
|
||||||
|
|
||||||
raise exceptions.ContractViolationException(
|
raise exceptions.ContractViolationException(
|
||||||
'Object {0} violates owned() contract'.format(
|
'Object {0} violates owned() contract'.format(obj))
|
||||||
obj.object_id))
|
|
||||||
|
|
||||||
@specs.parameter('obj', TypeScheme.ObjRef, nullable=True)
|
@specs.parameter('obj', TypeScheme.ObjRef, nullable=True)
|
||||||
@specs.name('not_owned')
|
@specs.name('not_owned')
|
||||||
@ -139,8 +143,7 @@ class TypeScheme(object):
|
|||||||
return obj
|
return obj
|
||||||
else:
|
else:
|
||||||
raise exceptions.ContractViolationException(
|
raise exceptions.ContractViolationException(
|
||||||
'Object {0} violates notOwned() contract'.format(
|
'Object {0} violates notOwned() contract'.format(obj))
|
||||||
obj.object_id))
|
|
||||||
|
|
||||||
@specs.parameter('name', dsl.MuranoTypeName(
|
@specs.parameter('name', dsl.MuranoTypeName(
|
||||||
False, root_context))
|
False, root_context))
|
||||||
@ -182,7 +185,7 @@ class TypeScheme(object):
|
|||||||
else:
|
else:
|
||||||
raise exceptions.ContractViolationException(
|
raise exceptions.ContractViolationException(
|
||||||
'Value {0} cannot be represented as class {1}'.format(
|
'Value {0} cannot be represented as class {1}'.format(
|
||||||
value, name))
|
format_scalar(value), name))
|
||||||
if not helpers.is_instance_of(
|
if not helpers.is_instance_of(
|
||||||
obj, murano_class.name,
|
obj, murano_class.name,
|
||||||
version_spec or helpers.get_type(root_context)):
|
version_spec or helpers.get_type(root_context)):
|
||||||
@ -205,12 +208,13 @@ class TypeScheme(object):
|
|||||||
context.register_function(not_owned)
|
context.register_function(not_owned)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def _map_dict(self, data, spec, context):
|
def _map_dict(self, data, spec, context, path):
|
||||||
if data is None or data is dsl.NO_VALUE:
|
if data is None or data is dsl.NO_VALUE:
|
||||||
data = {}
|
data = {}
|
||||||
if not isinstance(data, utils.MappingType):
|
if not isinstance(data, utils.MappingType):
|
||||||
raise exceptions.ContractViolationException(
|
raise exceptions.ContractViolationException(
|
||||||
'Supplied is not of a dictionary type')
|
'Value {0} is not of a dictionary type'.format(
|
||||||
|
format_scalar(data)))
|
||||||
if not spec:
|
if not spec:
|
||||||
return data
|
return data
|
||||||
result = {}
|
result = {}
|
||||||
@ -220,23 +224,27 @@ class TypeScheme(object):
|
|||||||
if yaql_key is not None:
|
if yaql_key is not None:
|
||||||
raise exceptions.DslContractSyntaxError(
|
raise exceptions.DslContractSyntaxError(
|
||||||
'Dictionary contract '
|
'Dictionary contract '
|
||||||
'cannot have more than one expression keys')
|
'cannot have more than one expression key')
|
||||||
else:
|
else:
|
||||||
yaql_key = key
|
yaql_key = key
|
||||||
else:
|
else:
|
||||||
result[key] = self._map(data.get(key), value, context)
|
result[key] = self._map(
|
||||||
|
data.get(key), value, context, '{0}[{1}]'.format(
|
||||||
|
path, format_scalar(key)))
|
||||||
|
|
||||||
if yaql_key is not None:
|
if yaql_key is not None:
|
||||||
yaql_value = spec[yaql_key]
|
yaql_value = spec[yaql_key]
|
||||||
for key, value in data.iteritems():
|
for key, value in data.iteritems():
|
||||||
if key in result:
|
if key in result:
|
||||||
continue
|
continue
|
||||||
result[self._map(key, yaql_key, context)] = self._map(
|
key = self._map(key, yaql_key, context, path)
|
||||||
value, yaql_value, context)
|
result[key] = self._map(
|
||||||
|
value, yaql_value, context, '{0}[{1}]'.format(
|
||||||
|
path, format_scalar(key)))
|
||||||
|
|
||||||
return utils.FrozenDict(result)
|
return utils.FrozenDict(result)
|
||||||
|
|
||||||
def _map_list(self, data, spec, context):
|
def _map_list(self, data, spec, context, path):
|
||||||
if not utils.is_sequence(data):
|
if not utils.is_sequence(data):
|
||||||
if data is None or data is dsl.NO_VALUE:
|
if data is None or data is dsl.NO_VALUE:
|
||||||
data = []
|
data = []
|
||||||
@ -267,26 +275,32 @@ class TypeScheme(object):
|
|||||||
if index >= len(spec) - shift
|
if index >= len(spec) - shift
|
||||||
else spec[index]
|
else spec[index]
|
||||||
)
|
)
|
||||||
yield self._map(item, spec_item, context)
|
yield self._map(
|
||||||
|
item, spec_item, context, '{0}[{1}]'.format(path, index))
|
||||||
|
|
||||||
return tuple(map_func())
|
return tuple(map_func())
|
||||||
|
|
||||||
def _map_scalar(self, data, spec):
|
def _map_scalar(self, data, spec):
|
||||||
if data != spec:
|
if data != spec:
|
||||||
raise exceptions.ContractViolationException(
|
raise exceptions.ContractViolationException(
|
||||||
'Value {0} is not equal to {1}'.format(data, spec))
|
'Value {0} is not equal to {1}'.format(
|
||||||
|
format_scalar(data), spec))
|
||||||
else:
|
else:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def _map(self, data, spec, context):
|
def _map(self, data, spec, context, path):
|
||||||
child_context = context.create_child_context()
|
child_context = context.create_child_context()
|
||||||
if isinstance(spec, dsl_types.YaqlExpression):
|
if isinstance(spec, dsl_types.YaqlExpression):
|
||||||
child_context[''] = data
|
child_context[''] = data
|
||||||
|
try:
|
||||||
return spec(context=child_context)
|
return spec(context=child_context)
|
||||||
|
except exceptions.ContractViolationException as e:
|
||||||
|
e.path = path
|
||||||
|
raise
|
||||||
elif isinstance(spec, utils.MappingType):
|
elif isinstance(spec, utils.MappingType):
|
||||||
return self._map_dict(data, spec, child_context)
|
return self._map_dict(data, spec, child_context, path)
|
||||||
elif utils.is_sequence(spec):
|
elif utils.is_sequence(spec):
|
||||||
return self._map_list(data, spec, child_context)
|
return self._map_list(data, spec, child_context, path)
|
||||||
else:
|
else:
|
||||||
return self._map_scalar(data, spec)
|
return self._map_scalar(data, spec)
|
||||||
|
|
||||||
@ -299,4 +313,10 @@ class TypeScheme(object):
|
|||||||
data = helpers.evaluate(default, context)
|
data = helpers.evaluate(default, context)
|
||||||
|
|
||||||
context = self.prepare_context(context, this, owner, default)
|
context = self.prepare_context(context, this, owner, default)
|
||||||
return self._map(data, self._spec, context)
|
return self._map(data, self._spec, context, '')
|
||||||
|
|
||||||
|
|
||||||
|
def format_scalar(value):
|
||||||
|
if isinstance(value, types.StringTypes):
|
||||||
|
return "'{0}'".format(value)
|
||||||
|
return unicode(value)
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import sys
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
from murano.dsl import exceptions
|
from murano.dsl import exceptions
|
||||||
@ -63,8 +64,32 @@ class Spec(object):
|
|||||||
|
|
||||||
|
|
||||||
class PropertySpec(Spec):
|
class PropertySpec(Spec):
|
||||||
pass
|
def __init__(self, property_name, declaration, container_class):
|
||||||
|
super(PropertySpec, self).__init__(declaration, container_class)
|
||||||
|
self.property_name = property_name
|
||||||
|
self.class_name = container_class.name
|
||||||
|
|
||||||
|
def validate(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return super(PropertySpec, self).validate(*args, **kwargs)
|
||||||
|
except exceptions.ContractViolationException as e:
|
||||||
|
msg = u'[{0}.{1}{2}] {3}'.format(
|
||||||
|
self.class_name, self.property_name, e.path, unicode(e))
|
||||||
|
raise exceptions.ContractViolationException, msg, sys.exc_info()[2]
|
||||||
|
|
||||||
|
|
||||||
class ArgumentSpec(Spec):
|
class ArgumentSpec(Spec):
|
||||||
pass
|
def __init__(self, method_name, arg_name, declaration, container_class):
|
||||||
|
super(ArgumentSpec, self).__init__(declaration, container_class)
|
||||||
|
self.method_name = method_name
|
||||||
|
self.arg_name = arg_name
|
||||||
|
self.class_name = container_class.name
|
||||||
|
|
||||||
|
def validate(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return super(ArgumentSpec, self).validate(*args, **kwargs)
|
||||||
|
except exceptions.ContractViolationException as e:
|
||||||
|
msg = u'[{0}::{1}({2}{3})] {4}'.format(
|
||||||
|
self.class_name, self.method_name, self.arg_name,
|
||||||
|
e.path, unicode(e))
|
||||||
|
raise exceptions.ContractViolationException, msg, sys.exc_info()[2]
|
||||||
|
Loading…
Reference in New Issue
Block a user