Merge branch 'main' into avm99963-monorail
Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266
GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/third_party/endpoints/parameter_converter.py b/third_party/endpoints/parameter_converter.py
new file mode 100644
index 0000000..5e2743f
--- /dev/null
+++ b/third_party/endpoints/parameter_converter.py
@@ -0,0 +1,200 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Helper that converts parameter values to the type expected by the API.
+
+Parameter values that appear in the URL and the query string are usually
+converted to native types before being passed to the backend. This code handles
+that conversion and some validation.
+"""
+
+# pylint: disable=g-bad-name
+from __future__ import absolute_import
+
+from . import errors
+
+__all__ = ['transform_parameter_value']
+
+
+def _check_enum(parameter_name, value, parameter_config):
+ """Checks if an enum value is valid.
+
+ This is called by the transform_parameter_value function and shouldn't be
+ called directly.
+
+ This verifies that the value of an enum parameter is valid.
+
+ Args:
+ parameter_name: A string containing the name of the parameter, which is
+ either just a variable name or the name with the index appended. For
+ example 'var' or 'var[2]'.
+ value: A string containing the value passed in for the parameter.
+ parameter_config: The dictionary containing information specific to the
+ parameter in question. This is retrieved from request.parameters in
+ the method config.
+
+ Raises:
+ EnumRejectionError: If the given value is not among the accepted
+ enum values in the field parameter.
+ """
+ enum_values = [enum['backendValue']
+ for enum in parameter_config['enum'].values()
+ if 'backendValue' in enum]
+ if value not in enum_values:
+ raise errors.EnumRejectionError(parameter_name, value, enum_values)
+
+
+def _check_boolean(parameter_name, value, parameter_config):
+ """Checks if a boolean value is valid.
+
+ This is called by the transform_parameter_value function and shouldn't be
+ called directly.
+
+ This checks that the string value passed in can be converted to a valid
+ boolean value.
+
+ Args:
+ parameter_name: A string containing the name of the parameter, which is
+ either just a variable name or the name with the index appended. For
+ example 'var' or 'var[2]'.
+ value: A string containing the value passed in for the parameter.
+ parameter_config: The dictionary containing information specific to the
+ parameter in question. This is retrieved from request.parameters in
+ the method config.
+
+ Raises:
+ BasicTypeParameterError: If the given value is not a valid boolean
+ value.
+ """
+ if parameter_config.get('type') != 'boolean':
+ return
+
+ if value.lower() not in ('1', 'true', '0', 'false'):
+ raise errors.BasicTypeParameterError(parameter_name, value, 'boolean')
+
+
+def _convert_boolean(value):
+ """Convert a string to a boolean value the same way the server does.
+
+ This is called by the transform_parameter_value function and shouldn't be
+ called directly.
+
+ Args:
+ value: A string value to be converted to a boolean.
+
+ Returns:
+ True or False, based on whether the value in the string would be interpreted
+ as true or false by the server. In the case of an invalid entry, this
+ returns False.
+ """
+ if value.lower() in ('1', 'true'):
+ return True
+ return False
+
+
+# Map to convert parameters from strings to their desired back-end format.
+# Anything not listed here will remain a string. Note that the server
+# keeps int64 and uint64 as strings when passed to the backend.
+# This maps a type name from the .api method configuration to a (validation
+# function, conversion function, descriptive type name) tuple. The
+# descriptive type name is only used in conversion error messages, and the
+# names here are chosen to match the error messages from the server.
+# Note that the 'enum' entry is special cased. Enums have 'type': 'string',
+# so we have special case code to recognize them and use the 'enum' map
+# entry.
+_PARAM_CONVERSION_MAP = {'boolean': (_check_boolean,
+ _convert_boolean,
+ 'boolean'),
+ 'int32': (None, int, 'integer'),
+ 'uint32': (None, int, 'integer'),
+ 'float': (None, float, 'float'),
+ 'double': (None, float, 'double'),
+ 'enum': (_check_enum, None, None)}
+
+
+def _get_parameter_conversion_entry(parameter_config):
+ """Get information needed to convert the given parameter to its API type.
+
+ Args:
+ parameter_config: The dictionary containing information specific to the
+ parameter in question. This is retrieved from request.parameters in the
+ method config.
+
+ Returns:
+ The entry from _PARAM_CONVERSION_MAP with functions/information needed to
+ validate and convert the given parameter from a string to the type expected
+ by the API.
+ """
+ entry = _PARAM_CONVERSION_MAP.get(parameter_config.get('type'))
+
+ # Special handling for enum parameters. An enum's type is 'string', so we
+ # need to detect them by the presence of an 'enum' property in their
+ # configuration.
+ if entry is None and 'enum' in parameter_config:
+ entry = _PARAM_CONVERSION_MAP['enum']
+
+ return entry
+
+
+def transform_parameter_value(parameter_name, value, parameter_config):
+ """Validates and transforms parameters to the type expected by the API.
+
+ If the value is a list this will recursively call _transform_parameter_value
+ on the values in the list. Otherwise, it checks all parameter rules for the
+ the current value and converts its type from a string to whatever format
+ the API expects.
+
+ In the list case, '[index-of-value]' is appended to the parameter name for
+ error reporting purposes.
+
+ Args:
+ parameter_name: A string containing the name of the parameter, which is
+ either just a variable name or the name with the index appended, in the
+ recursive case. For example 'var' or 'var[2]'.
+ value: A string or list of strings containing the value(s) passed in for
+ the parameter. These are the values from the request, to be validated,
+ transformed, and passed along to the backend.
+ parameter_config: The dictionary containing information specific to the
+ parameter in question. This is retrieved from request.parameters in the
+ method config.
+
+ Returns:
+ The converted parameter value(s). Not all types are converted, so this
+ may be the same string that's passed in.
+ """
+ if isinstance(value, list):
+ # We're only expecting to handle path and query string parameters here.
+ # The way path and query string parameters are passed in, they'll likely
+ # only be single values or singly-nested lists (no lists nested within
+ # lists). But even if there are nested lists, we'd want to preserve that
+ # structure. These recursive calls should preserve it and convert all
+ # parameter values. See the docstring for information about the parameter
+ # renaming done here.
+ return [transform_parameter_value('%s[%d]' % (parameter_name, index),
+ element, parameter_config)
+ for index, element in enumerate(value)]
+
+ # Validate and convert the parameter value.
+ entry = _get_parameter_conversion_entry(parameter_config)
+ if entry:
+ validation_func, conversion_func, type_name = entry
+ if validation_func:
+ validation_func(parameter_name, value, parameter_config)
+ if conversion_func:
+ try:
+ return conversion_func(value)
+ except ValueError:
+ raise errors.BasicTypeParameterError(parameter_name, value, type_name)
+
+ return value