class OpenapiFirst::RequestValidation
Public Class Methods
new(app, raise_error: false)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 14 def initialize(app, raise_error: false) @app = app @raise = raise_error end
Public Instance Methods
call(env)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 19 def call(env) # rubocop:disable Metrics/AbcSize operation = env[OpenapiFirst::OPERATION] return @app.call(env) unless operation env[INBOX] = Inbox.new(env) catch(:halt) do validate_query_parameters!(env, operation, env[PARAMETERS]) req = Rack::Request.new(env) content_type = req.content_type return @app.call(env) unless operation.request_body validate_request_content_type!(content_type, operation) body = req.body.read req.body.rewind parse_and_validate_request_body!(env, content_type, body, operation) @app.call(env) end end
Private Instance Methods
default_error(status, title = Rack::Utils::HTTP_STATUS_CODES[status])
click to toggle source
# File lib/openapi_first/request_validation.rb, line 77 def default_error(status, title = Rack::Utils::HTTP_STATUS_CODES[status]) { status: status.to_s, title: title } end
filtered_params(json_schema, params)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 115 def filtered_params(json_schema, params) json_schema['properties'] .each_with_object({}) do |key_value, result| parameter_name = key_value[0].to_sym schema = key_value[1] next unless params.key?(parameter_name) value = params[parameter_name] result[parameter_name] = parse_parameter(value, schema) end end
halt(response)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 40 def halt(response) throw :halt, response end
halt_with_error(status, errors = [default_error(status)])
click to toggle source
# File lib/openapi_first/request_validation.rb, line 84 def halt_with_error(status, errors = [default_error(status)]) raise RequestInvalidError, errors if @raise halt Rack::Response.new( MultiJson.dump(errors: errors), status, Rack::CONTENT_TYPE => 'application/vnd.api+json' ).finish end
parse_and_validate_request_body!(env, content_type, body, operation)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 44 def parse_and_validate_request_body!(env, content_type, body, operation) validate_request_body_presence!(body, operation) return if body.empty? schema = operation&.request_body_schema(content_type) return unless schema parsed_request_body = parse_request_body!(body) errors = schema.validate(parsed_request_body) halt_with_error(400, serialize_request_body_errors(errors)) if errors.any? env[INBOX].merge! env[REQUEST_BODY] = Utils.deep_symbolize(parsed_request_body) end
parse_array_parameter(value, schema)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 144 def parse_array_parameter(value, schema) array = value.is_a?(Array) ? value : value.split(',') return array unless schema['items'] array.map! { |e| parse_simple_value(e, schema['items']) } end
parse_parameter(value, schema)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 136 def parse_parameter(value, schema) return filtered_params(schema, value) if schema['properties'] return parse_array_parameter(value, schema) if schema['type'] == 'array' parse_simple_value(value, schema) end
parse_request_body!(body)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 57 def parse_request_body!(body) MultiJson.load(body) rescue MultiJson::ParseError => e err = { title: 'Failed to parse body as JSON' } err[:detail] = e.cause unless ENV['RACK_ENV'] == 'production' halt_with_error(400, [err]) end
parse_simple_value(value, schema)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 151 def parse_simple_value(value, schema) return to_boolean(value) if schema['type'] == 'boolean' begin return Integer(value, 10) if schema['type'] == 'integer' return Float(value) if schema['type'] == 'number' rescue ArgumentError value end value end
serialize_query_parameter_errors(validation_errors)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 127 def serialize_query_parameter_errors(validation_errors) validation_errors.map do |error| pointer = error['data_pointer'][1..].to_s { source: { parameter: pointer } }.update(ValidationFormat.error_details(error)) end end
serialize_request_body_errors(validation_errors)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 94 def serialize_request_body_errors(validation_errors) validation_errors.map do |error| { source: { pointer: error['data_pointer'] } }.update(ValidationFormat.error_details(error)) end end
to_boolean(value)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 163 def to_boolean(value) return true if value == 'true' return false if value == 'false' value end
validate_query_parameters!(env, operation, params)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 104 def validate_query_parameters!(env, operation, params) schema = operation.parameters_schema return unless schema params = filtered_params(schema.raw_schema, params) errors = schema.validate(Utils.deep_stringify(params)) halt_with_error(400, serialize_query_parameter_errors(errors)) if errors.any? env[PARAMETERS] = params env[INBOX].merge! params end
validate_request_body_presence!(body, operation)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 71 def validate_request_body_presence!(body, operation) return unless operation.request_body['required'] && body.empty? halt_with_error(415, 'Request body is required') end
validate_request_content_type!(content_type, operation)
click to toggle source
# File lib/openapi_first/request_validation.rb, line 65 def validate_request_content_type!(content_type, operation) return if operation.request_body.dig('content', content_type) halt_with_error(415) end