blob: 5e2743fa4ee6607b17f4b73e54f280a770247fe4 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 Google Inc. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Helper that converts parameter values to the type expected by the API.
16
17Parameter values that appear in the URL and the query string are usually
18converted to native types before being passed to the backend. This code handles
19that conversion and some validation.
20"""
21
22# pylint: disable=g-bad-name
23from __future__ import absolute_import
24
25from . import errors
26
27__all__ = ['transform_parameter_value']
28
29
30def _check_enum(parameter_name, value, parameter_config):
31 """Checks if an enum value is valid.
32
33 This is called by the transform_parameter_value function and shouldn't be
34 called directly.
35
36 This verifies that the value of an enum parameter is valid.
37
38 Args:
39 parameter_name: A string containing the name of the parameter, which is
40 either just a variable name or the name with the index appended. For
41 example 'var' or 'var[2]'.
42 value: A string containing the value passed in for the parameter.
43 parameter_config: The dictionary containing information specific to the
44 parameter in question. This is retrieved from request.parameters in
45 the method config.
46
47 Raises:
48 EnumRejectionError: If the given value is not among the accepted
49 enum values in the field parameter.
50 """
51 enum_values = [enum['backendValue']
52 for enum in parameter_config['enum'].values()
53 if 'backendValue' in enum]
54 if value not in enum_values:
55 raise errors.EnumRejectionError(parameter_name, value, enum_values)
56
57
58def _check_boolean(parameter_name, value, parameter_config):
59 """Checks if a boolean value is valid.
60
61 This is called by the transform_parameter_value function and shouldn't be
62 called directly.
63
64 This checks that the string value passed in can be converted to a valid
65 boolean value.
66
67 Args:
68 parameter_name: A string containing the name of the parameter, which is
69 either just a variable name or the name with the index appended. For
70 example 'var' or 'var[2]'.
71 value: A string containing the value passed in for the parameter.
72 parameter_config: The dictionary containing information specific to the
73 parameter in question. This is retrieved from request.parameters in
74 the method config.
75
76 Raises:
77 BasicTypeParameterError: If the given value is not a valid boolean
78 value.
79 """
80 if parameter_config.get('type') != 'boolean':
81 return
82
83 if value.lower() not in ('1', 'true', '0', 'false'):
84 raise errors.BasicTypeParameterError(parameter_name, value, 'boolean')
85
86
87def _convert_boolean(value):
88 """Convert a string to a boolean value the same way the server does.
89
90 This is called by the transform_parameter_value function and shouldn't be
91 called directly.
92
93 Args:
94 value: A string value to be converted to a boolean.
95
96 Returns:
97 True or False, based on whether the value in the string would be interpreted
98 as true or false by the server. In the case of an invalid entry, this
99 returns False.
100 """
101 if value.lower() in ('1', 'true'):
102 return True
103 return False
104
105
106# Map to convert parameters from strings to their desired back-end format.
107# Anything not listed here will remain a string. Note that the server
108# keeps int64 and uint64 as strings when passed to the backend.
109# This maps a type name from the .api method configuration to a (validation
110# function, conversion function, descriptive type name) tuple. The
111# descriptive type name is only used in conversion error messages, and the
112# names here are chosen to match the error messages from the server.
113# Note that the 'enum' entry is special cased. Enums have 'type': 'string',
114# so we have special case code to recognize them and use the 'enum' map
115# entry.
116_PARAM_CONVERSION_MAP = {'boolean': (_check_boolean,
117 _convert_boolean,
118 'boolean'),
119 'int32': (None, int, 'integer'),
120 'uint32': (None, int, 'integer'),
121 'float': (None, float, 'float'),
122 'double': (None, float, 'double'),
123 'enum': (_check_enum, None, None)}
124
125
126def _get_parameter_conversion_entry(parameter_config):
127 """Get information needed to convert the given parameter to its API type.
128
129 Args:
130 parameter_config: The dictionary containing information specific to the
131 parameter in question. This is retrieved from request.parameters in the
132 method config.
133
134 Returns:
135 The entry from _PARAM_CONVERSION_MAP with functions/information needed to
136 validate and convert the given parameter from a string to the type expected
137 by the API.
138 """
139 entry = _PARAM_CONVERSION_MAP.get(parameter_config.get('type'))
140
141 # Special handling for enum parameters. An enum's type is 'string', so we
142 # need to detect them by the presence of an 'enum' property in their
143 # configuration.
144 if entry is None and 'enum' in parameter_config:
145 entry = _PARAM_CONVERSION_MAP['enum']
146
147 return entry
148
149
150def transform_parameter_value(parameter_name, value, parameter_config):
151 """Validates and transforms parameters to the type expected by the API.
152
153 If the value is a list this will recursively call _transform_parameter_value
154 on the values in the list. Otherwise, it checks all parameter rules for the
155 the current value and converts its type from a string to whatever format
156 the API expects.
157
158 In the list case, '[index-of-value]' is appended to the parameter name for
159 error reporting purposes.
160
161 Args:
162 parameter_name: A string containing the name of the parameter, which is
163 either just a variable name or the name with the index appended, in the
164 recursive case. For example 'var' or 'var[2]'.
165 value: A string or list of strings containing the value(s) passed in for
166 the parameter. These are the values from the request, to be validated,
167 transformed, and passed along to the backend.
168 parameter_config: The dictionary containing information specific to the
169 parameter in question. This is retrieved from request.parameters in the
170 method config.
171
172 Returns:
173 The converted parameter value(s). Not all types are converted, so this
174 may be the same string that's passed in.
175 """
176 if isinstance(value, list):
177 # We're only expecting to handle path and query string parameters here.
178 # The way path and query string parameters are passed in, they'll likely
179 # only be single values or singly-nested lists (no lists nested within
180 # lists). But even if there are nested lists, we'd want to preserve that
181 # structure. These recursive calls should preserve it and convert all
182 # parameter values. See the docstring for information about the parameter
183 # renaming done here.
184 return [transform_parameter_value('%s[%d]' % (parameter_name, index),
185 element, parameter_config)
186 for index, element in enumerate(value)]
187
188 # Validate and convert the parameter value.
189 entry = _get_parameter_conversion_entry(parameter_config)
190 if entry:
191 validation_func, conversion_func, type_name = entry
192 if validation_func:
193 validation_func(parameter_name, value, parameter_config)
194 if conversion_func:
195 try:
196 return conversion_func(value)
197 except ValueError:
198 raise errors.BasicTypeParameterError(parameter_name, value, type_name)
199
200 return value