def parse_key_schema(cls, key_def_list_json): hash_key_attr_name = None range_key_attr_name = None for key_def in key_def_list_json: key_attr_name_json = key_def.pop(Props.ATTRIBUTE_NAME, None) validation.validate_attr_name(key_attr_name_json) key_type_json = key_def.pop(Props.KEY_TYPE, None) if key_type_json == Values.KEY_TYPE_HASH: if hash_key_attr_name is not None: raise exception.ValidationError( _("Only one 'HASH' key is allowed")) hash_key_attr_name = key_attr_name_json elif key_type_json == Values.KEY_TYPE_RANGE: if range_key_attr_name is not None: raise exception.ValidationError( _("Only one 'RANGE' key is allowed")) range_key_attr_name = key_attr_name_json else: raise exception.ValidationError(_( "Only 'RANGE' or 'HASH' key types are allowed, but " "'%(key_type)s' is found"), key_type=key_type_json) validation.validate_unexpected_props(key_def, "key_definition") if hash_key_attr_name is None: raise exception.ValidationError(_("HASH key is missing")) if range_key_attr_name: return (hash_key_attr_name, range_key_attr_name) return (hash_key_attr_name, )
def __validate_type(value, property_name, py_type, json_type): if value is None: raise exception.ValidationError(_( "Required property '%(property_name)s' wasn't found " "or it's value is null"), property_name=property_name) if not isinstance(value, py_type): raise exception.ValidationError(WRONG_TYPE_MSG, property_name=property_name, json_type=json_type, prop_value=json.dumps(value)) return value
def parse_attribute_condition(cls, condition_type, condition_args, condition_class=models.IndexedCondition): actual_args_count = (len(condition_args) if condition_args is not None else 0) if condition_type == Values.BETWEEN: if actual_args_count != 2: raise exception.ValidationError( _("%(type)s condition type requires exactly 2 arguments, " "but %(actual_args_count)s given"), type=condition_type, actual_args_count=actual_args_count) if condition_args[0].attr_type != condition_args[1].attr_type: raise exception.ValidationError( _("%(type)s condition type requires arguments of the " "same type, but different types given"), type=condition_type, ) return [ condition_class.ge(condition_args[0]), condition_class.le(condition_args[1]) ] if condition_type == Values.BEGINS_WITH: first_condition = condition_class( condition_class.CONDITION_TYPE_GREATER_OR_EQUAL, condition_args) condition_arg = first_condition.arg if condition_arg.is_number: raise exception.ValidationError( _("%(condition_type)s condition type is not allowed for" "argument of the %(argument_type)s type"), condition_type=condition_type, argument_type=condition_arg.attr_type.type) first_value = condition_arg.decoded_value chr_fun = unichr if isinstance(first_value, unicode) else chr second_value = first_value[:-1] + chr_fun(ord(first_value[-1]) + 1) second_condition = condition_class.le( models.AttributeValue(condition_arg.attr_type, decoded_value=second_value)) return [first_condition, second_condition] return [condition_class(condition_type, condition_args)]
def __init__(self, attribute_type_map, key_attributes, index_def_map=None): """ :param attribute_type_map: attribute name to AttributeType mapping :param key_attrs: list of key attribute names, contains partition key (the first in list, required) attribute name and extra key attribute names (the second and other list items, not required) :param index_def_map: index name to IndexDefinition mapping """ if index_def_map is None: index_def_map = {} for key_attr in key_attributes: if key_attr not in attribute_type_map: raise exception.ValidationError( "Definition for attribute['%(attr_name)s'] wasn't found", attr_name=key_attr ) # Validate the primary hash and range key types, make sure them are # scalar types attr_type = attribute_type_map.get(key_attr, None) if attr_type is not None and attr_type.collection_type is not None: raise exception.ValidationError( _("Type '%(prop_value)s' is not a scalar type"), prop_value=attr_type['type']) if len(key_attr) < 2 and index_def_map: raise exception.ValidationError("Local secondary indexes are not " "allowed for tables with hash " "key only") for index_name, index_def in index_def_map.iteritems(): if index_def.alt_hash_key_attr != key_attributes[0]: msg = _("Hash key of index '%(index_name)s' must " "be the same as primary key's hash key.") raise exception.ValidationError(msg, index_name=index_name) if index_def.alt_range_key_attr not in attribute_type_map: raise exception.ValidationError( "Definition for attribute['%(attr_name)s'] wasn't found", attr_name=index_def.alt_range_key_attr ) super(TableSchema, self).__init__( attribute_type_map=attribute_type_map, key_attributes=key_attributes, index_def_map=index_def_map)
def validate_index_name(value): validate_string(value, "index name") if not INDEX_NAME_PATTERN.match(value): raise exception.ValidationError( _("Wrong index name '%(prop_value)s' found"), prop_value=value) return value
def validate_table_name(value): validate_string(value, "table name") if not TABLE_NAME_PATTERN.match(value): raise exception.ValidationError( _("Wrong table name '%(prop_value)s' found"), prop_value=value) return value
def __decode_value(cls, attr_type, encoded_value): decoded_value = None if decoded_value is not None: return decoded_value collection_type = attr_type.collection_type if collection_type is None: decoded_value = cls.__decode_single_value(attr_type.type, encoded_value) elif collection_type == AttributeType.COLLECTION_TYPE_MAP: if isinstance(encoded_value, dict): res_dict = dict() key_type = attr_type.key_type value_type = attr_type.value_type for key, value in encoded_value.iteritems(): res_dict[cls.__decode_single_value(key_type, key)] = ( cls.__decode_single_value(value_type, value) ) decoded_value = res_dict elif collection_type == AttributeType.COLLECTION_TYPE_SET: element_type = attr_type.element_type res = blist.sortedset() for val in encoded_value: res.add(cls.__decode_single_value(element_type, val)) decoded_value = res if decoded_value is None: raise exception.ValidationError( _("Can't recognize attribute value '%(value)s'" "of type %(type)s"), type=attr_type, value=json.dumps(encoded_value) ) return decoded_value
def parse_batch_write_request_items(cls, request_items_json): request_map = {} for table_name, request_list_json in request_items_json.iteritems(): validation.validate_table_name(table_name) validation.validate_list_of_objects(request_list_json, table_name) request_list_for_table = [] for request_json in request_list_json: for request_type, request_body in request_json.iteritems(): validation.validate_string(request_type, "request_type") if request_type == Props.REQUEST_PUT: validation.validate_object(request_body, request_type) item = request_body.pop(Props.ITEM, None) validation.validate_object(item, Props.ITEM) validation.validate_unexpected_props( request_body, request_type) request_list_for_table.append( models.WriteItemRequest.put( cls.parse_item_attributes(item))) elif request_type == Props.REQUEST_DELETE: validation.validate_object(request_body, request_type) key = request_body.pop(Props.KEY, None) validation.validate_object(key, Props.KEY) validation.validate_unexpected_props( request_body, request_type) request_list_for_table.append( models.WriteItemRequest.delete( cls.parse_item_attributes(key))) else: raise exception.ValidationError( _("Unsupported request type found: " "%(request_type)s"), request_type=request_type) request_map[table_name] = request_list_for_table return request_map
def parse_typed_attr_value(cls, typed_attr_value_json): if len(typed_attr_value_json) != 1: raise exception.ValidationError( _("Can't recognize attribute typed value format: '%(attr)s'"), attr=json.dumps(typed_attr_value_json)) (attr_type_json, attr_value_json) = (typed_attr_value_json.popitem()) return models.AttributeValue(attr_type_json, attr_value_json)
def __init__(self, select_type, attributes=None): if select_type not in self._allowed_types: raise exception.ValidationError( _("Select type '%(select_type)s' isn't allowed"), select_type=select_type ) if attributes is not None: if select_type != self.SELECT_TYPE_SPECIFIC: raise exception.ValidationError( _("Attribute list is only expected with select_type " "'%(select_type)s'"), select_type=self.SELECT_TYPE_SPECIFIC ) super(SelectType, self).__init__(type=select_type, attributes=attributes)
def validate(self, attr_type): if not isinstance(attr_type, basestring): raise exception.ValidationError(self.VALIDATION_ERROR_PATTERN, type=attr_type) if len(attr_type) == 1: if attr_type not in self._allowed_primitive_types: raise exception.ValidationError(self.VALIDATION_ERROR_PATTERN, type=attr_type) return collection_type = attr_type[-1] if collection_type not in self._allowed_collection_types: raise exception.ValidationError(self.VALIDATION_ERROR_PATTERN, type=attr_type) if collection_type == self.COLLECTION_TYPE_MAP: if len(attr_type) != 3: raise exception.ValidationError(self.VALIDATION_ERROR_PATTERN, type=attr_type) key_type = attr_type[0] value_type = attr_type[1] if (key_type not in self._allowed_primitive_types or value_type not in self._allowed_primitive_types): raise exception.ValidationError(self.VALIDATION_ERROR_PATTERN, type=attr_type) return if len(attr_type) != 2: raise exception.ValidationError(self.VALIDATION_ERROR_PATTERN, type=attr_type) element_type = attr_type[0] if element_type not in self._allowed_primitive_types: raise exception.ValidationError(self.VALIDATION_ERROR_PATTERN, type=attr_type)
def _validate_table_is_active(table_info): if table_info.status != models.TableMeta.TABLE_STATUS_ACTIVE: raise exception.ValidationError( _("Can't execute request: " "Table '%(table_name)s' status '%(table_status)s' " "isn't %(expected_status)s"), table_name=table_info.name, table_status=table_info.status, expected_status=models.TableMeta.TABLE_STATUS_ACTIVE)
def execute_write_batch(self, context, write_request_map): write_request_list_to_send = [] for table_name, write_request_list in write_request_map.iteritems(): table_info = self._table_info_repo.get(context, table_name) requested_keys = set() for req in write_request_list: self._validate_table_is_active(table_info) if req.is_put: self._validate_table_schema(table_info, req.attribute_map, keys_only=False) else: self._validate_table_schema(table_info, req.attribute_map) key_values = self._key_values(table_info, req.attribute_map) keys = tuple(key_values) if keys in requested_keys: raise exception.ValidationError(_( "Can't execute request: " "More than one operation requested" " for item with keys %(keys)s" " in table '%(table_name)s'"), table_name=table_info.name, keys=keys) requested_keys.add(keys) write_request_list_to_send.append((table_info, req)) future_result_list = [] for i in xrange(0, len(write_request_list_to_send), self._batch_chunk_size): req_list = (write_request_list_to_send[i:i + self._batch_chunk_size]) future_result_list.append( self._batch_write_async(context, req_list)) unprocessed_items = {} for future_result in future_result_list: unprocessed_request_list = future_result.result() for (table_info, write_request) in unprocessed_request_list: table_name = table_info.name tables_unprocessed_items = (unprocessed_items.get( table_name, None)) if tables_unprocessed_items is None: tables_unprocessed_items = [] unprocessed_items[table_name] = tables_unprocessed_items tables_unprocessed_items.append(write_request) return unprocessed_items
def validate_list_of_objects(value, property_name): validate_list(value, property_name) for item in value: if not isinstance(item, dict): raise exception.ValidationError(WRONG_TYPE_MSG, property_name=property_name, json_type="List of Objects", prop_value=json.dumps(value)) return value
def parse_local_secondary_index(cls, local_secondary_index_json): key_attrs_json = local_secondary_index_json.pop(Props.KEY_SCHEMA, None) validation.validate_list(key_attrs_json, Props.KEY_SCHEMA) key_attrs_for_projection = cls.parse_key_schema(key_attrs_json) hash_key = key_attrs_for_projection[0] try: range_key = key_attrs_for_projection[1] except IndexError: raise exception.ValidationError( _("Range key in index wasn't specified")) index_name = local_secondary_index_json.pop(Props.INDEX_NAME, None) validation.validate_index_name(index_name) projection_json = local_secondary_index_json.pop( Props.PROJECTION, None) validation.validate_object(projection_json, Props.PROJECTION) validation.validate_unexpected_props(local_secondary_index_json, "local_secondary_index") projection_type = projection_json.pop(Props.PROJECTION_TYPE, Values.PROJECTION_TYPE_INCLUDE) if projection_type == Values.PROJECTION_TYPE_ALL: projected_attrs = None elif projection_type == Values.PROJECTION_TYPE_KEYS_ONLY: projected_attrs = tuple() elif projection_type == Values.PROJECTION_TYPE_INCLUDE: projected_attrs = projection_json.pop(Props.NON_KEY_ATTRIBUTES, None) else: raise exception.ValidationError( _("Only '%(pt_all)', '%(pt_ko)' of '%(pt_incl)' projection " "types are allowed, but '%(projection_type)s' is found"), pt_all=Values.PROJECTION_TYPE_ALL, pt_ko=Values.PROJECTION_TYPE_KEYS_ONLY, pt_incl=Values.PROJECTION_TYPE_INCLUDE, projection_type=projection_type) validation.validate_unexpected_props(projection_json, Props.PROJECTION) return index_name, models.IndexDefinition(hash_key, range_key, projected_attrs)
def test_process_request_validation_error(self): """Test the case where a ValidationError exception occurs. """ expected_message = "A validation error occurred" validation_ex = exception.ValidationError(expected_message) side_effect = self._side_effect_generator([validation_ex, "use args"]) self.request.get_response = mock.Mock(side_effect=side_effect) response = self.fault_wrapper.process_request(self.request) message = response[0].error['error']['message'] self.assertEqual(expected_message, message)
def test_validation_error_message(self, mock_list_tables): """Test the error message received when a ValidationError occurs. """ expected_message = 'There was some validation error' mock_list_tables.side_effect = \ exception.ValidationError(expected_message) (code, message) = self._get_api_call_error() self.assertEqual(expected_message, message) self.assertEqual(400, code)
def validate_set(value, property_name): validate_list(value, property_name) value_set = frozenset(value) if len(value_set) < len(value): raise exception.ValidationError(WRONG_TYPE_MSG, property_name=property_name, json_type="List of unique values", prop_value=json.dumps(value)) return value_set
def __init__(self, type, args): allowed_arg_count = self._allowed_types_to_arg_count_map.get(type, None) if allowed_arg_count is None: raise exception.ValidationError( _("%(condition_class)s of type['%(type)s'] is not allowed"), condition_class=self.__class__.__name__, type=type) actual_arg_count = len(args) if args is not None else 0 if (actual_arg_count < allowed_arg_count[0] or actual_arg_count > allowed_arg_count[1]): if allowed_arg_count[0] == allowed_arg_count[1]: raise exception.ValidationError( _("%(condition_class)s of type['%(type)s'] requires " "exactly %(allowed_arg_count)s arguments, " "but %(actual_arg_count)s found"), condition_class=self.__class__.__name__, type=type, allowed_arg_count=allowed_arg_count[0], actual_arg_count=actual_arg_count ) else: raise exception.ValidationError( _("%(condition_class)s of type['%(type)s'] requires from " "%(min_args_allowed)s to %(max_args_allowed)s arguments " "provided, but %(actual_arg_count)s found"), condition_class=self.__class__.__name__, type=type, min_args_allowed=allowed_arg_count[0], max_args_allowed=allowed_arg_count[1], actual_arg_count=actual_arg_count ) if args is not None and type in self._types_with_only_primitive_arg: for arg in args: if arg.attr_type.collection_type is not None: raise exception.ValidationError( _("%(condition_class)s of type['%(type)s'] allows " "only primitive arguments"), condition_class=self.__class__.__name__, type=type ) super(Condition, self).__init__(type=type, args=args)
def __init__(self, type): """ :param type: one of available return values type """ if type not in self._allowed_types: raise exception.ValidationError( _("Return values type '%(type)s' isn't allowed"), type=type ) super(DeleteReturnValuesType, self).__init__(type=type)
def parse_local_secondary_indexes(cls, local_secondary_index_list_json): res = {} for index_json in local_secondary_index_list_json: index_name, index_def = ( cls.parse_local_secondary_index(index_json)) res[index_name] = index_def if len(res) < len(local_secondary_index_list_json): raise exception.ValidationError( _("Two or more indexes with the same name")) return res
def _validate_table_schema(table_info, attribute_map, keys_only=True, index_name=None): schema_key_attributes = table_info.schema.key_attributes schema_attribute_type_map = table_info.schema.attribute_type_map key_attribute_names_to_find = set(schema_key_attributes) if index_name is not None: key_attribute_names_to_find.add( table_info.schema.index_def_map[index_name].alt_range_key_attr) if keys_only and (len(key_attribute_names_to_find) != len(attribute_map)): raise exception.ValidationError( _("Specified key: %(key_attributes)s doesn't match expected " "key attributes set: %(expected_key_attributes)s"), key_attributes=attribute_map, expected_key_attributes=key_attribute_names_to_find) for attr_name, typed_attr_value in attribute_map.iteritems(): schema_attr_type = schema_attribute_type_map.get(attr_name, None) if schema_attr_type is None: continue key_attribute_names_to_find.discard(attr_name) if schema_attr_type != typed_attr_value.attr_type: raise exception.ValidationError( _("Attribute: '%(attr_name)s' of type: '%(attr_type)s' " "doesn't match table schema expected attribute type: " "'%(expected_attr_type)s'"), attr_name=attr_name, attr_type=typed_attr_value.attr_type.type, expected_attr_type=schema_attr_type.type) if key_attribute_names_to_find: raise exception.ValidationError( _("Couldn't find expected key attributes: " "'%(expected_key_attributes)s'"), expected_key_attributes=key_attribute_names_to_find)
def __init__(self, action, value): """ :param action: one of available action names :param value: AttributeValue instance, parameter for action """ if action not in self._allowed_actions: raise exception.ValidationError( _("Update action '%(action)s' isn't allowed"), action=action ) super(UpdateItemAction, self).__init__(action=action, value=value)
def validate_unexpected_props(value, property_name): if len(value) > 0: if isinstance(value, dict): value_str = json.dumps(value) else: value_str = str(value) raise exception.ValidationError(_( "Unexpected properties were found for '%(property_name)s': " "%(unexpected_props)s"), property_name=property_name, unexpected_props=value_str) return value
def parse_expected_attribute_conditions( cls, expected_attribute_conditions_json): expected_attribute_conditions = {} for (attr_name_json, condition_json) in ( expected_attribute_conditions_json.iteritems()): validation.validate_attr_name(attr_name_json) validation.validate_object(condition_json, attr_name_json) if len(condition_json) != 1: raise exception.ValidationError( _("Can't recognize attribute expected condition format: " "'%(attr)s'"), attr=json.dumps(condition_json)) (condition_type, condition_value) = condition_json.popitem() validation.validate_string(condition_type, "condition type") if condition_type == Props.VALUE: validation.validate_object(condition_value, Props.VALUE) expected_attribute_conditions[attr_name_json] = [ models.ExpectedCondition.eq( cls.parse_typed_attr_value(condition_value)) ] elif condition_type == Props.EXISTS: validation.validate_boolean(condition_value, Props.EXISTS) expected_attribute_conditions[attr_name_json] = [ models.ExpectedCondition.not_null() if condition_value else models.ExpectedCondition.null() ] else: raise exception.ValidationError( _("Unsupported condition type found: %(condition_type)s"), condition_type=condition_type) return expected_attribute_conditions
def validate_integer(value, property_name, min_val=None, max_val=None): if isinstance(value, basestring): try: value = int(value) except ValueError: pass value = __validate_type(value, property_name, (int, long), "Integer") if min_val is not None and value < min_val: raise exception.ValidationError(_( "'%(property_name)s' property value[%(property_value)s] is less " "then min_value[%(min_value)s]."), property_name=property_name, property_value=value, min_value=min_val) if max_val is not None and value > max_val: raise exception.ValidationError(_( "'%(property_name)s' property value[%(property_value)s] is more " "then max_value[%(max_value)s]."), property_name=property_name, property_value=value, max_value=max_val) return value
def __init__(self, type, attribute_map): """ :param type: one of available type names :param attribute_map: map of attribute name to AttributeValue instance, represents item to put or key to delete """ if type not in self._allowed_types: raise exception.ValidationError( _("Write request_type '%(type)s' isn't allowed"), type=type ) super(WriteItemRequest, self).__init__( type=type, attribute_map=attribute_map )
def query(self, context, table_name, indexed_condition_map, select_type, index_name=None, limit=None, exclusive_start_key=None, consistent=True, order_type=None): table_info = self._table_info_repo.get(context, table_name) self._validate_table_is_active(table_info) schema_attribute_type_map = table_info.schema.attribute_type_map condition_map = indexed_condition_map.copy() # validate hash key condition hash_key_name = table_info.schema.hash_key_name hash_key_condition_list = condition_map.pop(hash_key_name, None) if not hash_key_condition_list: self._raise_condition_schema_mismatch(indexed_condition_map, table_info) if (len(hash_key_condition_list) != 1 or hash_key_condition_list[0].type != models.IndexedCondition.CONDITION_TYPE_EQUAL): raise exception.ValidationError( _("Only equality condition is allowed for HASH key attribute " "'%(hash_key_name)s'"), hash_key_name=hash_key_name, ) hash_key_type = schema_attribute_type_map[hash_key_name] if hash_key_condition_list[0].arg.attr_type != hash_key_type: self._raise_condition_schema_mismatch(indexed_condition_map, table_info) # validate range key conditions range_key_name = table_info.schema.range_key_name if index_name is not None: index_def = table_info.schema.index_def_map.get(index_name) if index_def is None: raise exception.ValidationError(_( "Index '%(index_name)s' doesn't exist for table " "'%(table_name)s'"), index_name=index_name, table_name=table_name) range_key_name = index_def.alt_range_key_attr range_condition_list = condition_map.pop(range_key_name, None) if range_key_name: range_key_type = schema_attribute_type_map[range_key_name] range_condition_list = range_condition_list or [] for range_condition in range_condition_list: if range_condition.arg.attr_type != range_key_type: self._raise_condition_schema_mismatch( indexed_condition_map, table_info) # validate extra conditions if len(condition_map) > 0: self._raise_condition_schema_mismatch(indexed_condition_map, table_info) # validate exclusive start key if exclusive_start_key is not None: self._validate_table_schema(table_info, exclusive_start_key, index_name=index_name) with self.__task_semaphore: result = self._storage_driver.select_item( context, table_info, hash_key_condition_list, range_condition_list, select_type, index_name, limit, exclusive_start_key, consistent, order_type) return result
def _raise_condition_schema_mismatch(condition_map, table_info): raise exception.ValidationError(_( "Specified query conditions %(indexed_condition_map)s " "don't match table schema: %(table_schema)s"), indexed_condition_map=condition_map, table_schema=table_info.schema)
def validate_attr_name(value): validate_string(value, "attribute name") if not ATTRIBUTE_NAME_PATTERN.match(value): raise exception.ValidationError( _("Wrong attribute name '%(prop_value)s' found"), prop_value=value)