1 # Licensed under the Apache License, Version 2.0 (the "License"); you may
2 # not use this file except in compliance with the License. You may obtain
3 # a copy of the License at
5 # http://www.apache.org/licenses/LICENSE-2.0
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 # License for the specific language governing permissions and limitations
14 import dateutil.parser
20 # from toscaparser.elements import constraints
21 from toscaparser.common.exception import ExceptionCollector
22 from toscaparser.common.exception import InvalidTOSCAVersionPropertyException
23 from toscaparser.common.exception import RangeValueError
24 from toscaparser.utils.gettextutils import _
26 log = logging.getLogger('tosca')
28 RANGE_UNBOUNDED = 'UNBOUNDED'
31 def str_to_num(value):
32 '''Convert a string representation of a number into a numeric type.'''
33 # TODO(TBD) we should not allow numeric values in, input should be str
34 if isinstance(value, numbers.Number):
42 def validate_numeric(value):
43 if not isinstance(value, numbers.Number):
44 ExceptionCollector.appendException(
45 ValueError(_('"%s" is not a numeric.') % value))
49 def validate_integer(value):
50 if not isinstance(value, int):
54 ExceptionCollector.appendException(
55 ValueError(_('"%s" is not an integer.') % value))
59 def validate_float(value):
60 if not isinstance(value, float):
61 ExceptionCollector.appendException(
62 ValueError(_('"%s" is not a float.') % value))
66 def validate_string(value):
67 if not isinstance(value, six.string_types):
68 ExceptionCollector.appendException(
69 ValueError(_('"%s" is not a string.') % value))
73 def validate_list(value):
74 if not isinstance(value, list):
75 ExceptionCollector.appendException(
76 ValueError(_('"%s" is not a list.') % value))
80 def validate_range(range):
83 # validate range list has a min and max
85 ExceptionCollector.appendException(
86 ValueError(_('"%s" is not a valid range.') % range))
87 # validate min and max are numerics or the keyword UNBOUNDED
88 min_test = max_test = False
89 if not range[0] == RANGE_UNBOUNDED:
90 min = validate_numeric(range[0])
93 if not range[1] == RANGE_UNBOUNDED:
94 max = validate_numeric(range[1])
97 # validate the max > min (account for UNBOUNDED)
98 if not min_test and not max_test:
99 # Note: min == max is allowed
101 ExceptionCollector.appendException(
102 ValueError(_('"%s" is not a valid range.') % range))
107 def validate_value_in_range(value, range, prop_name):
108 validate_numeric(value)
109 validate_range(range)
111 # Note: value is valid if equal to min
112 if range[0] != RANGE_UNBOUNDED:
114 ExceptionCollector.appendException(
115 RangeValueError(pname=prop_name,
119 # Note: value is valid if equal to max
120 if range[1] != RANGE_UNBOUNDED:
122 ExceptionCollector.appendException(
123 RangeValueError(pname=prop_name,
130 def validate_map(value):
131 if not isinstance(value, collections.Mapping):
132 ExceptionCollector.appendException(
133 ValueError(_('"%s" is not a map.') % value))
137 def validate_boolean(value):
138 if isinstance(value, bool):
141 if isinstance(value, str):
142 normalised = value.lower()
143 if normalised in ['true', 'false']:
144 return normalised == 'true'
146 ExceptionCollector.appendException(
147 ValueError(_('"%s" is not a boolean.') % value))
150 def validate_timestamp(value):
152 # Note: we must return our own exception message
153 # as dateutil's parser returns different types / values on
154 # different systems. OSX, for example, returns a tuple
155 # containing a different error message than Linux
156 dateutil.parser.parse(value)
157 except Exception as e:
158 original_err_msg = str(e)
159 log.error(original_err_msg)
160 ExceptionCollector.appendException(
161 ValueError(_('"%(val)s" is not a valid timestamp. "%(msg)s"') %
162 {'val': value, 'msg': original_err_msg}))
166 class TOSCAVersionProperty(object):
168 VERSION_RE = re.compile('^(?P<major_version>([0-9][0-9]*))'
169 '(\.(?P<minor_version>([0-9][0-9]*)))?'
170 '(\.(?P<fix_version>([0-9][0-9]*)))?'
171 '(\.(?P<qualifier>([0-9A-Za-z]+)))?'
172 '(\-(?P<build_version>[0-9])*)?$')
174 def __init__(self, version):
175 self.version = str(version)
176 match = self.VERSION_RE.match(self.version)
178 ExceptionCollector.appendException(
179 InvalidTOSCAVersionPropertyException(what=(self.version)))
181 ver = match.groupdict()
182 if self.version in ['0', '0.0', '0.0.0']:
183 log.warning(_('Version assumed as not provided'))
185 self.minor_version = ver['minor_version']
186 self.major_version = ver['major_version']
187 self.fix_version = ver['fix_version']
188 self.qualifier = self._validate_qualifier(ver['qualifier'])
189 self.build_version = self._validate_build(ver['build_version'])
190 self._validate_major_version(self.major_version)
192 def _validate_major_version(self, value):
193 """Validate major version
195 Checks if only major version is provided and assumes
197 Eg: If version = 18, then it returns version = '18.0'
200 if self.minor_version is None and self.build_version is None and \
202 log.warning(_('Minor version assumed "0".'))
203 self.version = '.'.join([value, '0'])
206 def _validate_qualifier(self, value):
207 """Validate qualifier
209 TOSCA version is invalid if a qualifier is present without the
210 fix version or with all of major, minor and fix version 0s.
212 For example, the following versions are invalid
216 if (self.fix_version is None and value) or \
217 (self.minor_version == self.major_version ==
218 self.fix_version == '0' and value):
219 ExceptionCollector.appendException(
220 InvalidTOSCAVersionPropertyException(what=(self.version)))
223 def _validate_build(self, value):
224 """Validate build version
226 TOSCA version is invalid if build version is present without the
228 Eg: version = 18.0.0-1 is invalid.
230 if not self.qualifier and value:
231 ExceptionCollector.appendException(
232 InvalidTOSCAVersionPropertyException(what=(self.version)))
235 def get_version(self):