Coverage for watcher/api/controllers/v1/types.py: 79%
131 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-17 12:22 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-17 12:22 +0000
1# Copyright 2013 Red Hat, Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16from oslo_serialization import jsonutils
17from oslo_utils import strutils
18import wsme
19from wsme import types as wtypes
21from watcher._i18n import _
22from watcher.common import exception
23from watcher.common import utils
26class UuidOrNameType(wtypes.UserType):
27 """A simple UUID or logical name type."""
29 basetype = wtypes.text
30 name = 'uuid_or_name'
32 @staticmethod
33 def validate(value):
34 if not (utils.is_uuid_like(value) or utils.is_hostname_safe(value)):
35 raise exception.InvalidUuidOrName(name=value)
36 return value
38 @staticmethod
39 def frombasetype(value):
40 if value is None:
41 return None
42 return UuidOrNameType.validate(value)
45class IntervalOrCron(wtypes.UserType):
46 """A simple int value or cron syntax type"""
48 basetype = wtypes.text
49 name = 'interval_or_cron'
51 @staticmethod
52 def validate(value):
53 if not (utils.is_int_like(value) or utils.is_cron_like(value)): 53 ↛ 54line 53 didn't jump to line 54 because the condition on line 53 was never true
54 raise exception.InvalidIntervalOrCron(name=value)
55 return value
57 @staticmethod
58 def frombasetype(value):
59 if value is None: 59 ↛ 60line 59 didn't jump to line 60 because the condition on line 59 was never true
60 return None
61 return IntervalOrCron.validate(value)
64interval_or_cron = IntervalOrCron()
67class NameType(wtypes.UserType):
68 """A simple logical name type."""
70 basetype = wtypes.text
71 name = 'name'
73 @staticmethod
74 def validate(value):
75 if not utils.is_hostname_safe(value):
76 raise exception.InvalidName(name=value)
77 return value
79 @staticmethod
80 def frombasetype(value):
81 if value is None:
82 return None
83 return NameType.validate(value)
86class UuidType(wtypes.UserType):
87 """A simple UUID type."""
89 basetype = wtypes.text
90 name = 'uuid'
92 @staticmethod
93 def validate(value):
94 if not utils.is_uuid_like(value):
95 raise exception.InvalidUUID(uuid=value)
96 return value
98 @staticmethod
99 def frombasetype(value):
100 if value is None:
101 return None
102 return UuidType.validate(value)
105class BooleanType(wtypes.UserType):
106 """A simple boolean type."""
108 basetype = wtypes.text
109 name = 'boolean'
111 @staticmethod
112 def validate(value):
113 try:
114 return strutils.bool_from_string(value, strict=True)
115 except ValueError as e:
116 # raise Invalid to return 400 (BadRequest) in the API
117 raise exception.Invalid(e)
119 @staticmethod
120 def frombasetype(value):
121 if value is None:
122 return None
123 return BooleanType.validate(value)
126class JsonType(wtypes.UserType):
127 """A simple JSON type."""
129 basetype = wtypes.text
130 name = 'json'
132 def __str__(self):
133 # These are the json serializable native types
134 return ' | '.join(map(str, (wtypes.text, int, float,
135 BooleanType, list, dict, None)))
137 @staticmethod
138 def validate(value):
139 try:
140 jsonutils.dumps(value, default=None)
141 except TypeError:
142 raise exception.Invalid(_('%s is not JSON serializable') % value)
143 else:
144 return value
146 @staticmethod
147 def frombasetype(value):
148 return JsonType.validate(value)
151uuid = UuidType()
152boolean = BooleanType()
153jsontype = JsonType()
156class MultiType(wtypes.UserType):
157 """A complex type that represents one or more types.
159 Used for validating that a value is an instance of one of the types.
161 :param types: Variable-length list of types.
163 """
165 def __init__(self, *types):
166 self.types = types
168 def __str__(self):
169 return ' | '.join(map(str, self.types))
171 def validate(self, value):
172 for t in self.types:
173 if t is wsme.types.text and isinstance(value, wsme.types.bytes):
174 value = value.decode()
175 if isinstance(value, t):
176 return value
177 else:
178 raise ValueError(
179 _("Wrong type. Expected '%(type)s', got '%(value)s'"),
180 type=self.types, value=type(value)
181 )
184class JsonPatchType(wtypes.Base):
185 """A complex type that represents a single json-patch operation."""
187 path = wtypes.wsattr(wtypes.StringType(pattern=r'^(/[\w-]+)+$'),
188 mandatory=True)
189 op = wtypes.wsattr(wtypes.Enum(str, 'add', 'replace', 'remove'),
190 mandatory=True)
191 value = wsme.wsattr(jsontype, default=wtypes.Unset)
193 @staticmethod
194 def internal_attrs():
195 """Returns a list of internal attributes.
197 Internal attributes can't be added, replaced or removed. This
198 method may be overwritten by derived class.
200 """
201 return ['/created_at', '/id', '/links', '/updated_at',
202 '/deleted_at', '/uuid']
204 @staticmethod
205 def mandatory_attrs():
206 """Returns a list of mandatory attributes.
208 Mandatory attributes can't be removed from the document. This
209 method should be overwritten by derived class.
211 """
212 return []
214 @staticmethod
215 def validate(patch):
216 _path = '/{0}'.format(patch.path.split('/')[1])
217 if _path in patch.internal_attrs():
218 msg = _("'%s' is an internal attribute and can not be updated")
219 raise wsme.exc.ClientSideError(msg % patch.path)
221 if patch.path in patch.mandatory_attrs() and patch.op == 'remove':
222 msg = _("'%s' is a mandatory attribute and can not be removed")
223 raise wsme.exc.ClientSideError(msg % patch.path)
225 if patch.op != 'remove':
226 if patch.value is wsme.Unset:
227 msg = _("'add' and 'replace' operations needs value")
228 raise wsme.exc.ClientSideError(msg)
230 ret = {'path': patch.path, 'op': patch.op}
231 if patch.value is not wsme.Unset:
232 ret['value'] = patch.value
233 return ret