Merge "Add verigraph code base"
[parser.git] / tosca2heat / tosca-parser / toscaparser / utils / validateutils.py
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
4 #
5 #         http://www.apache.org/licenses/LICENSE-2.0
6 #
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
11 #    under the License.
12
13 import collections
14 import dateutil.parser
15 import logging
16 import numbers
17 import re
18 import six
19
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 _
25
26 log = logging.getLogger('tosca')
27
28 RANGE_UNBOUNDED = 'UNBOUNDED'
29
30
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):
35         return value
36     try:
37         return int(value)
38     except ValueError:
39         return float(value)
40
41
42 def validate_numeric(value):
43     if not isinstance(value, numbers.Number):
44         ExceptionCollector.appendException(
45             ValueError(_('"%s" is not a numeric.') % value))
46     return value
47
48
49 def validate_integer(value):
50     if not isinstance(value, int):
51         try:
52             value = int(value)
53         except Exception:
54             ExceptionCollector.appendException(
55                 ValueError(_('"%s" is not an integer.') % value))
56     return value
57
58
59 def validate_float(value):
60     if not isinstance(value, float):
61         ExceptionCollector.appendException(
62             ValueError(_('"%s" is not a float.') % value))
63     return value
64
65
66 def validate_string(value):
67     if not isinstance(value, six.string_types):
68         ExceptionCollector.appendException(
69             ValueError(_('"%s" is not a string.') % value))
70     return value
71
72
73 def validate_list(value):
74     if not isinstance(value, list):
75         ExceptionCollector.appendException(
76             ValueError(_('"%s" is not a list.') % value))
77     return value
78
79
80 def validate_range(range):
81     # list class check
82     validate_list(range)
83     # validate range list has a min and max
84     if len(range) != 2:
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])
91     else:
92         min_test = True
93     if not range[1] == RANGE_UNBOUNDED:
94         max = validate_numeric(range[1])
95     else:
96         max_test = True
97     # validate the max > min (account for UNBOUNDED)
98     if not min_test and not max_test:
99         # Note: min == max is allowed
100         if min > max:
101             ExceptionCollector.appendException(
102                 ValueError(_('"%s" is not a valid range.') % range))
103
104     return range
105
106
107 def validate_value_in_range(value, range, prop_name):
108     validate_numeric(value)
109     validate_range(range)
110
111     # Note: value is valid if equal to min
112     if range[0] != RANGE_UNBOUNDED:
113         if value < range[0]:
114             ExceptionCollector.appendException(
115                 RangeValueError(pname=prop_name,
116                                 pvalue=value,
117                                 vmin=range[0],
118                                 vmax=range[1]))
119     # Note: value is valid if equal to max
120     if range[1] != RANGE_UNBOUNDED:
121         if value > range[1]:
122             ExceptionCollector.appendException(
123                 RangeValueError(pname=prop_name,
124                                 pvalue=value,
125                                 vmin=range[0],
126                                 vmax=range[1]))
127     return value
128
129
130 def validate_map(value):
131     if not isinstance(value, collections.Mapping):
132         ExceptionCollector.appendException(
133             ValueError(_('"%s" is not a map.') % value))
134     return value
135
136
137 def validate_boolean(value):
138     if isinstance(value, bool):
139         return value
140
141     if isinstance(value, str):
142         normalised = value.lower()
143         if normalised in ['true', 'false']:
144             return normalised == 'true'
145
146     ExceptionCollector.appendException(
147         ValueError(_('"%s" is not a boolean.') % value))
148
149
150 def validate_timestamp(value):
151     try:
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}))
163     return
164
165
166 class TOSCAVersionProperty(object):
167
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])*)?$')
173
174     def __init__(self, version):
175         self.version = str(version)
176         match = self.VERSION_RE.match(self.version)
177         if not match:
178             ExceptionCollector.appendException(
179                 InvalidTOSCAVersionPropertyException(what=(self.version)))
180             return
181         ver = match.groupdict()
182         if self.version in ['0', '0.0', '0.0.0']:
183             log.warning(_('Version assumed as not provided'))
184             self.version = None
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)
191
192     def _validate_major_version(self, value):
193         """Validate major version
194
195         Checks if only major version is provided and assumes
196         minor version as 0.
197         Eg: If version = 18, then it returns version = '18.0'
198         """
199
200         if self.minor_version is None and self.build_version is None and \
201                 value != '0':
202             log.warning(_('Minor version assumed "0".'))
203             self.version = '.'.join([value, '0'])
204         return value
205
206     def _validate_qualifier(self, value):
207         """Validate qualifier
208
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.
211
212            For example, the following versions are invalid
213               18.0.abc
214               0.0.0.abc
215         """
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)))
221         return value
222
223     def _validate_build(self, value):
224         """Validate build version
225
226            TOSCA version is invalid if build version is present without the
227            qualifier.
228            Eg: version = 18.0.0-1 is invalid.
229         """
230         if not self.qualifier and value:
231             ExceptionCollector.appendException(
232                 InvalidTOSCAVersionPropertyException(what=(self.version)))
233         return value
234
235     def get_version(self):
236         return self.version