Class: OpenapiFirst::RequestValidation

Inherits:
Object
  • Object
show all
Defined in:
lib/openapi_first/request_validation.rb

Instance Method Summary collapse

Constructor Details

#initialize(app, allow_unknown_query_parameters: false) ⇒ RequestValidation

Returns a new instance of RequestValidation.



11
12
13
14
# File 'lib/openapi_first/request_validation.rb', line 11

def initialize(app, allow_unknown_query_parameters: false)
  @app = app
  @allow_unknown_query_parameters = allow_unknown_query_parameters
end

Instance Method Details

#allowed_params(json_schema, params) ⇒ Object



115
116
117
118
119
120
121
122
123
# File 'lib/openapi_first/request_validation.rb', line 115

def allowed_params(json_schema, params)
  json_schema['properties']
    .keys
    .each_with_object({}) do |parameter_name, filtered|
      next unless params.key?(parameter_name)

      filtered[parameter_name] = params[parameter_name]
    end
end

#call(env) ⇒ Object

rubocop:disable Metrics/AbcSize, Metrics/MethodLength



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/openapi_first/request_validation.rb', line 16

def call(env) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
  operation = env[OpenapiFirst::OPERATION]
  return @app.call(env) unless operation

  req = Rack::Request.new(env)
  catch(:halt) do
    validate_query_parameters!(env, operation, req.params)
    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

#default_error(status, title = ) ⇒ Object



69
70
71
72
73
74
# File 'lib/openapi_first/request_validation.rb', line 69

def default_error(status, title = Rack::Utils::HTTP_STATUS_CODES[status])
  {
    status: status.to_s,
    title: title
  }
end

#error_response(status, errors = [default_error(status)]) ⇒ Object



76
77
78
79
80
81
82
# File 'lib/openapi_first/request_validation.rb', line 76

def error_response(status, errors = [default_error(status)])
  Rack::Response.new(
    MultiJson.dump(errors: errors),
    status,
    Rack::CONTENT_TYPE => 'application/vnd.api+json'
  ).finish
end

#halt(response) ⇒ Object



34
35
36
# File 'lib/openapi_first/request_validation.rb', line 34

def halt(response)
  throw :halt, response
end

#parse_and_validate_request_body!(env, content_type, body, operation) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/openapi_first/request_validation.rb', line 38

def parse_and_validate_request_body!(env, content_type, body, operation)
  validate_request_body_presence!(body, operation)
  return if body.empty?

  schema = request_body_schema(content_type, operation)
  return unless schema

  parsed_request_body = MultiJson.load(body)
  errors = validate_json_schema(schema, parsed_request_body)
  if errors.any?
    halt(error_response(400, serialize_request_body_errors(errors)))
  end
  env[OpenapiFirst::REQUEST_BODY] = parsed_request_body
end

#request_body_schema(content_type, endpoint) ⇒ Object



84
85
86
87
88
# File 'lib/openapi_first/request_validation.rb', line 84

def request_body_schema(content_type, endpoint)
  return unless endpoint

  endpoint.request_body.content[content_type]&.fetch('schema')
end

#serialize_query_parameter_errors(validation_errors) ⇒ Object



125
126
127
128
129
130
131
132
133
# File 'lib/openapi_first/request_validation.rb', line 125

def serialize_query_parameter_errors(validation_errors)
  validation_errors.map do |error|
    {
      source: {
        parameter: File.basename(error['data_pointer'])
      }
    }.update(ValidationFormat.error_details(error))
  end
end

#serialize_request_body_errors(validation_errors) ⇒ Object



90
91
92
93
94
95
96
97
98
# File 'lib/openapi_first/request_validation.rb', line 90

def serialize_request_body_errors(validation_errors)
  validation_errors.map do |error|
    {
      source: {
        pointer: error['data_pointer']
      }
    }.update(ValidationFormat.error_details(error))
  end
end

#validate_json_schema(schema, object) ⇒ Object



65
66
67
# File 'lib/openapi_first/request_validation.rb', line 65

def validate_json_schema(schema, object)
  JSONSchemer.schema(schema).validate(object)
end

#validate_query_parameters!(env, operation, params) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/openapi_first/request_validation.rb', line 100

def validate_query_parameters!(env, operation, params)
  json_schema = QueryParameters.new(
    operation: operation,
    allow_unknown_parameters: @allow_unknown_query_parameters
  ).to_json_schema

  return unless json_schema

  errors = JSONSchemer.schema(json_schema).validate(params)
  if errors.any?
    halt error_response(400, serialize_query_parameter_errors(errors))
  end
  env[QUERY_PARAMS] = allowed_params(json_schema, params)
end

#validate_request_body_presence!(body, operation) ⇒ Object



59
60
61
62
63
# File 'lib/openapi_first/request_validation.rb', line 59

def validate_request_body_presence!(body, operation)
  return unless operation.request_body.required && body.empty?

  halt(error_response(415, 'Request body is required'))
end

#validate_request_content_type!(content_type, operation) ⇒ Object



53
54
55
56
57
# File 'lib/openapi_first/request_validation.rb', line 53

def validate_request_content_type!(content_type, operation)
  return if operation.request_body.content[content_type]

  halt(error_response(415))
end