blob: 5e2743fa4ee6607b17f4b73e54f280a770247fe4 [file] [log] [blame]
# 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