Adrià Vilanova MartÃnez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1 | # 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 | |
| 17 | Parameter values that appear in the URL and the query string are usually |
| 18 | converted to native types before being passed to the backend. This code handles |
| 19 | that conversion and some validation. |
| 20 | """ |
| 21 | |
| 22 | # pylint: disable=g-bad-name |
| 23 | from __future__ import absolute_import |
| 24 | |
| 25 | from . import errors |
| 26 | |
| 27 | __all__ = ['transform_parameter_value'] |
| 28 | |
| 29 | |
| 30 | def _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 | |
| 58 | def _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 | |
| 87 | def _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 | |
| 126 | def _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 | |
| 150 | def 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 |