Skip to content

Commit

Permalink
Add option to validate query params
Browse files Browse the repository at this point in the history
  • Loading branch information
mkon committed Apr 18, 2024
1 parent cbe9fde commit 678077e
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 2 deletions.
14 changes: 13 additions & 1 deletion lib/openapi_contracts/doc/parameter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ def in_path?
@in == 'path'
end

def in_query?
@in == 'query'
end

def matches?(value)
case @spec.dig('schema', 'type')
when 'integer'
Expand All @@ -25,10 +29,18 @@ def matches?(value)
end
end

def required?
@required == true
end

def schema_for_validation
@spec.navigate('schema')
end

private

def schemer
@schemer ||= Validators::SchemaValidation.validation_schemer(@spec.navigate('schema'))
@schemer ||= Validators::SchemaValidation.validation_schemer(schema_for_validation)
end

def integer_parameter_matches?(value)
Expand Down
6 changes: 5 additions & 1 deletion lib/openapi_contracts/match.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module OpenapiContracts
class Match
DEFAULT_OPTIONS = {request_body: false}.freeze
DEFAULT_OPTIONS = {
parameters: false,
request_body: false
}.freeze
MIN_REQUEST_ANCESTORS = %w(Rack::Request::Env Rack::Request::Helpers).freeze
MIN_RESPONSE_ANCESTORS = %w(Rack::Response::Helpers).freeze

Expand Down Expand Up @@ -42,6 +45,7 @@ def matchers
)
validators = Validators::ALL.dup
validators.delete(Validators::HttpStatus) unless @options[:status]
validators.delete(Validators::Parameters) unless @options[:parameters]
validators.delete(Validators::RequestBody) unless @options[:request_body]
validators.reverse
.reduce(->(err) { err }) { |s, m| m.new(s, env) }
Expand Down
2 changes: 2 additions & 0 deletions lib/openapi_contracts/validators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Validators
autoload :Documented, 'openapi_contracts/validators/documented'
autoload :Headers, 'openapi_contracts/validators/headers'
autoload :HttpStatus, 'openapi_contracts/validators/http_status'
autoload :Parameters, 'openapi_contracts/validators/parameters'
autoload :RequestBody, 'openapi_contracts/validators/request_body'
autoload :ResponseBody, 'openapi_contracts/validators/response_body'
autoload :SchemaValidation, 'openapi_contracts/validators/schema_validation'
Expand All @@ -12,6 +13,7 @@ module Validators
ALL = [
Documented,
HttpStatus,
Parameters,
RequestBody,
ResponseBody,
Headers
Expand Down
21 changes: 21 additions & 0 deletions lib/openapi_contracts/validators/parameters.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module OpenapiContracts::Validators
# Validates the input parameters, eg path/url parameters
class Parameters < Base
include SchemaValidation

private

def validate
operation.parameters.select(&:in_query?).each do |parameter|
if request.GET.key?(parameter.name)
value = request.GET[parameter.name]
unless parameter.matches?(request.GET[parameter.name])
@errors << "#{value.inspect} is not a valid value for the query parameter #{parameter.name.inspect}"
end
elsif parameter.required?
@errors << "Missing query parameter #{parameter.name.inspect}"
end
end
end
end
end
2 changes: 2 additions & 0 deletions spec/fixtures/openapi/components/schemas/Polymorphism.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Pet:
mapping:
dog: '#/Dog'
cat: '#/Cat'
required:
- type

Cat:
description: A cat
Expand Down
9 changes: 9 additions & 0 deletions spec/fixtures/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ paths:
get:
operationId: pets
summary: Pets
parameters:
- in: query
name: order
schema:
type: string
enum:
- asc
- desc
required: false
responses:
'200':
description: Ok
Expand Down
23 changes: 23 additions & 0 deletions spec/integration/rspec_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,27 @@

it { is_expected.to_not match_openapi_doc(doc, path: '/user', request_body: true).with_http_status(:ok) }
end

context 'when input parameters are validated' do
let(:path) { '/pets?order=asc' }
let(:response_json) do
[
{
type: 'cat'
},
{
type: 'dog'
}
]
end
let(:response_status) { 200 }

it { is_expected.to match_openapi_doc(doc, parameters: true) }

context 'when input parameters are not valid' do
let(:path) { '/pets?order=wrong' }

it { is_expected.to_not match_openapi_doc(doc, parameters: true) }
end
end
end
78 changes: 78 additions & 0 deletions spec/openapi_contracts/validators/parameters_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
require 'active_support/core_ext/object/json'

RSpec.describe OpenapiContracts::Validators::Parameters do
subject { described_class.new(stack, env) }

include_context 'when using GET /pets'

let(:env) { OpenapiContracts::Env.new(operation:, request:, response:) }
let(:operation) { doc.operation_for('/pets', method) }
let(:stack) { ->(errors) { errors } }
let(:doc) do
OpenapiContracts::Doc.new(
{
paths: {
'/pets': {
get: {
parameters: [
{
in: 'query',
name: 'order',
required:,
schema: {
type: 'string',
enum: %w(asc desc)
}
}
],
responses: {
'200': {
description: 'Ok',
content: {
'application/json': {}
}
}
}
}
}
}
}.as_json
)
end

context 'when optional parameters are missing' do
let(:path) { '/pets' }
let(:required) { false }

it 'has no errors' do
expect(subject.call).to be_empty
end
end

context 'when required parameters are missing' do
let(:path) { '/pets' }
let(:required) { true }

it 'has errors' do
expect(subject.call).to contain_exactly 'Missing query parameter "order"'
end
end

context 'when required parameters are present' do
let(:path) { '/pets?order=asc' }
let(:required) { true }

it 'has no errors' do
expect(subject.call).to be_empty
end
end

context 'when parameters are wrong' do
let(:path) { '/pets?order=bad' }
let(:required) { false }

it 'has errors' do
expect(subject.call).to contain_exactly '"bad" is not a valid value for the query parameter "order"'
end
end
end
30 changes: 30 additions & 0 deletions spec/support/setup_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,36 @@
let(:response_status) { 201 }
end

RSpec.shared_context 'when using GET /pets' do
let(:request) { TestRequest.build(path, method:) }
let(:response) do
TestResponse[response_status, response_headers, response_body].tap do |resp|
resp.request = request
end
end
let(:doc) { OpenapiContracts::Doc.parse(FIXTURES_PATH.join('openapi')) }
let(:method) { 'GET' }
let(:path) { '/pets' }
let(:response_body) { JSON.dump(response_json) }
let(:response_headers) do
{
'Content-Type' => 'application/json;charset=utf-8',
'X-Request-Id' => 'some-request-id'
}
end
let(:response_json) do
[
{
type: 'cat'
},
{
type: 'dog'
}
]
end
let(:response_status) { 200 }
end

RSpec.shared_context 'when using PATCH /comments/{id}' do
let(:response) do
TestResponse[response_status, response_headers, response_body].tap do |resp|
Expand Down

0 comments on commit 678077e

Please sign in to comment.