def validate_api_call(schema, raw_request, raw_response): """ Validate the request/response cycle of an api call against a swagger schema. Request/Response objects from the `requests` and `urllib` library are supported. """ request = normalize_request(raw_request) response = normalize_response(raw_response) with ErrorCollection() as errors: try: validate_request( request=request, schema=schema, ) except ValidationError as err: errors['request'].add_error(err.messages or getattr(err, 'detail')) return try: validate_response( response=response, request_method=request.method, schema=schema, ) except ValidationError as err: errors['response'].add_error(err.messages or getattr(err, 'detail'))
def host_validator(value, **kwargs): """ From: http://stackoverflow.com/questions/2532053/validate-a-hostname-string According to: http://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names """ scheme, hostname, port, path = decompose_hostname(value) if len(hostname) > 255: return False if hostname[-1] == ".": hostname = hostname[: -1] # strip exactly one dot from the right, if present allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE) with ErrorCollection() as errors: if not all(allowed.match(x) for x in hostname.split(".")): errors.add_error( 'invalid', MESSAGES['host']['invalid'].format(value), ) if path: errors.add_error( 'path', MESSAGES['host']['may_not_include_path'].format(value), ) if scheme: errors.add_error( 'scheme', MESSAGES['host']['may_not_include_scheme'].format(value), )
def validate_operation(request, validators, **kwargs): with ErrorCollection() as errors: for key, validator in validators.items(): try: validator(request, **kwargs) except ValidationError as err: errors[key].add_error(err.detail)
def validate_parameters(parameter_values, parameters, context): validators = construct_multi_parameter_validators(parameters, context=context) with ErrorCollection() as errors: for key, validator in validators.items(): try: validator(parameter_values.get(key, EMPTY)) except ValidationError as err: errors[key].add_error(err.detail)
def validate_response(response, request_method, schema): """ Response validation involves the following steps. 4. validate that the response status_code is in the allowed responses for the request method. 5. validate that the response content validates against any provided schemas for the responses. 6. headers, content-types, etc..., ??? """ with ErrorCollection() as errors: # 1 # TODO: tests try: api_path = validate_path_to_api_path( path=response.path, **schema ) except ValidationError as err: errors['path'].extend(list(err.messages)) return # this causes an exception to be raised since errors is no longer falsy. path_definition = schema['paths'][api_path] or {} # TODO: tests try: operation_definition = validate_request_method_to_operation( request_method=request_method, path_definition=path_definition, ) except ValidationError as err: errors['method'].append(err.message) return # 4 try: response_definition = validate_status_code_to_response_definition( response=response, operation_definition=operation_definition, ) except ValidationError as err: errors['status_code'].add_error(err.detail) else: # 5 response_validator = generate_response_validator( api_path, operation_definition=operation_definition, path_definition=path_definition, response_definition=response_definition, context=schema, ) try: response_validator(response, context=schema) except ValidationError as err: errors['body'].add_error(err.detail)
def validate_path_items(paths, **kwargs): with ErrorCollection() as errors: for path, path_definition in paths.items(): # TODO: move this to its own validation function that validates the keys. if not path.startswith('/'): errors.add_error(path, MESSAGES['path']['must_start_with_slash']) try: path_item_validator(path_definition, **kwargs) except ValidationError as err: errors.add_error(path, err.detail)
def validate_request(request, schema): """ Request validation does the following steps. 1. validate that the path matches one of the defined paths in the schema. 2. validate that the request method conforms to a supported methods for the given path. 3. validate that the request parameters conform to the parameter definitions for the operation definition. """ with ErrorCollection() as errors: # 1 try: api_path = validate_path_to_api_path(path=request.path, context=schema, **schema) except ValidationError as err: errors['path'].add_error(err.detail) return # this causes an exception to be raised since errors is no longer falsy. path_definition = schema['paths'][api_path] or {} if not path_definition: # TODO: is it valid to not have a definition for a path? return # 2 try: operation_definition = validate_request_method_to_operation( request_method=request.method, path_definition=path_definition, ) except ValidationError as err: errors['method'].add_error(err.detail) return if operation_definition is None: # TODO: is this compliant with swagger, can path operations have a null # definition? return # 3 operation_validators = construct_operation_validators( api_path=api_path, path_definition=path_definition, operation_definition=operation_definition, context=schema, ) try: validate_operation(request, operation_validators, context=schema) except ValidationError as err: errors['method'].add_error(err.detail)
def validate_api_request(schema, raw_request): request = normalize_request(raw_request) with ErrorCollection(): validate_request(request=request, schema=schema)