Skip to content

Commit 31a7e80

Browse files
committed
Support multiple response schemas for OpenAPI 2
1 parent 02968af commit 31a7e80

File tree

5 files changed

+61
-39
lines changed

5 files changed

+61
-39
lines changed

lib/committee/drivers/open_api_2/driver.rb

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -91,18 +91,18 @@ def schema_class
9191

9292
def find_best_fit_response(link_data)
9393
if response_data = link_data["responses"]["200"] || response_data = link_data["responses"][200]
94-
[200, response_data]
94+
200
9595
elsif response_data = link_data["responses"]["201"] || response_data = link_data["responses"][201]
96-
[201, response_data]
96+
201
9797
else
9898
# Sort responses so that we can try to prefer any 3-digit status code.
9999
# If there are none, we'll just take anything from the list.
100100
ordered_responses = link_data["responses"].
101101
select { |k, v| k.to_s =~ /[0-9]{3}/ }
102102
if first = ordered_responses.first
103-
[first[0].to_i, first[1]]
103+
first[0].to_i
104104
else
105-
[nil, nil]
105+
nil
106106
end
107107
end
108108
end
@@ -174,19 +174,16 @@ def parse_routes!(data, schema, store)
174174
schemas_data["properties"][href]["properties"][method] = schema_data
175175
end
176176

177-
# Arbitrarily pick one response for the time being. Prefers in order:
178-
# a 200, 201, any 3-digit numerical response, then anything at all.
179-
status, response_data = find_best_fit_response(link_data)
180-
if status
181-
link.status_success = status
182-
183-
# A link need not necessarily specify a target schema.
184-
if response_data["schema"]
185-
target_schemas_data["properties"][href]["properties"][method] =
186-
response_data["schema"]
187-
end
177+
target_schemas_data["properties"][href]["properties"][method] ||= {"properties"=> {}}
178+
link_data["responses"].each do |key, response_data|
179+
status = key.to_i
180+
next unless response_data["schema"]
181+
182+
target_schemas_data["properties"][href]["properties"][method]["properties"][status] = response_data["schema"]
188183
end
189184

185+
link.status_success = find_best_fit_response(link_data)
186+
190187
rx = %r{^#{href_to_regex(link.href)}$}
191188
Committee.log_debug "Created route: #{link.method} #{link.href} (regex #{rx})"
192189

@@ -218,8 +215,10 @@ def parse_routes!(data, schema, store)
218215
end
219216

220217
# response
221-
link.target_schema =
222-
target_schemas.properties[link.href].properties[method]
218+
link.target_schemas = {}
219+
target_schemas.properties[link.href].properties[method].properties.each do |status, schema|
220+
link.target_schemas[status] = schema
221+
end
223222
end
224223
end
225224

lib/committee/drivers/open_api_2/link.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,20 @@ class Link
2323

2424
# The link's output schema. i.e. How we validate an endpoint's response
2525
# data.
26-
attr_accessor :target_schema
26+
attr_accessor :target_schemas
2727

2828
attr_accessor :header_schema
2929

3030
def rel
3131
raise "Committee: rel not implemented for OpenAPI"
3232
end
33+
34+
def target_schema
35+
target_schemas[status_success] ||
36+
target_schemas[200] ||
37+
target_schemas[201] ||
38+
target_schemas.values.first
39+
end
3340
end
3441
end
3542
end

lib/committee/schema_validator/hyper_schema/response_validator.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ def initialize(link, options = {})
1111
@validate_success_only = options[:validate_success_only]
1212
@allow_blank_structures = options[:allow_blank_structures]
1313

14-
@validator = JsonSchema::Validator.new(target_schema(link))
14+
@validators = {}
15+
if link.is_a? Drivers::OpenAPI2::Link
16+
link.target_schemas.each do |status, schema|
17+
@validators[status] = JsonSchema::Validator.new(target_schema(link))
18+
end
19+
else
20+
@validators[link.status_success] = JsonSchema::Validator.new(target_schema(link))
21+
end
1522
end
1623

1724
def call(status, headers, data)
@@ -45,9 +52,12 @@ def call(status, headers, data)
4552
end
4653

4754
begin
48-
if Committee::Middleware::ResponseValidation.validate?(status, validate_success_only) && !@validator.validate(data)
49-
errors = JsonSchema::SchemaError.aggregate(@validator.errors).join("\n")
50-
raise InvalidResponse, "Invalid response.\n\n#{errors}"
55+
if Committee::Middleware::ResponseValidation.validate?(status, validate_success_only)
56+
raise InvalidResponse, "Invalid response.#{@link.href} status code #{status} definition does not exist" if @validators[status].nil?
57+
if !@validators[status].validate(data)
58+
errors = JsonSchema::SchemaError.aggregate(@validators[status].errors).join("\n")
59+
raise InvalidResponse, "Invalid response.\n\n#{errors}"
60+
end
5161
end
5262
rescue => e
5363
raise InvalidResponse, "Invalid response.\n\nschema is undefined" if /undefined method .all_of. for nil/ =~ e.message

test/drivers/open_api_2/link_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
@link.method = "GET"
1212
@link.status_success = 200
1313
@link.schema = { "title" => "input" }
14-
@link.target_schema = { "title" => "target" }
14+
@link.target_schemas = {200 => { "title" => "target" }}
1515
end
1616

1717
it "uses set #enc_type" do

test/schema_validator/hyper_schema/response_generator_test.rb

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,62 +74,68 @@
7474

7575
it "generates first enum value for a schema with enum" do
7676
link = Committee::Drivers::OpenAPI2::Link.new
77-
link.target_schema = JsonSchema::Schema.new
78-
link.target_schema.enum = ["foo"]
79-
link.target_schema.type = ["string"]
77+
target_schema = JsonSchema::Schema.new
78+
target_schema.enum = ["foo"]
79+
target_schema.type = ["string"]
80+
link.target_schemas = {200 => target_schema}
8081
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
8182
assert_equal("foo", data)
8283
end
8384

8485
it "generates basic types" do
8586
link = Committee::Drivers::OpenAPI2::Link.new
86-
link.target_schema = JsonSchema::Schema.new
87+
target_schema = JsonSchema::Schema.new
88+
link.target_schemas = {200 => target_schema}
8789

88-
link.target_schema.type = ["integer"]
90+
target_schema.type = ["integer"]
8991
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
9092
assert_equal 0, data
9193

92-
link.target_schema.type = ["null"]
94+
target_schema.type = ["null"]
9395
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
9496
assert_nil data
9597

96-
link.target_schema.type = ["string"]
98+
target_schema.type = ["string"]
9799
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
98100
assert_equal "", data
99101
end
100102

101103
it "generates an empty array for an array type" do
102104
link = Committee::Drivers::OpenAPI2::Link.new
103-
link.target_schema = JsonSchema::Schema.new
104-
link.target_schema.type = ["array"]
105+
target_schema = JsonSchema::Schema.new
106+
link.target_schemas = {200 => target_schema}
107+
target_schema.type = ["array"]
105108
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
106109
assert_equal([], data)
107110
end
108111

109112
it "generates an empty object for an object with no fields" do
110113
link = Committee::Drivers::OpenAPI2::Link.new
111-
link.target_schema = JsonSchema::Schema.new
112-
link.target_schema.type = ["object"]
114+
target_schema = JsonSchema::Schema.new
115+
link.target_schemas = {200 => target_schema}
116+
target_schema.type = ["object"]
113117
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
114118
assert_equal({}, data)
115119
end
116120

117121
it "prefers an example to a built-in value" do
118122
link = Committee::Drivers::OpenAPI2::Link.new
119-
link.target_schema = JsonSchema::Schema.new
123+
target_schema = JsonSchema::Schema.new
124+
link.target_schemas = {200 => target_schema}
120125

121-
link.target_schema.data = { "example" => 123 }
122-
link.target_schema.type = ["integer"]
126+
target_schema.data = { "example" => 123 }
127+
target_schema.type = ["integer"]
123128

124129
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
125130
assert_equal 123, data
126131
end
127132

128133
it "prefers non-null types to null types" do
129134
link = Committee::Drivers::OpenAPI2::Link.new
130-
link.target_schema = JsonSchema::Schema.new
135+
target_schema = JsonSchema::Schema.new
136+
link.target_schemas = {200 => target_schema}
131137

132-
link.target_schema.type = ["null", "integer"]
138+
target_schema.type = ["null", "integer"]
133139
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
134140
assert_equal 0, data
135141
end

0 commit comments

Comments
 (0)