apipie-rails 0.5.8 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.rst +82 -0
- data/lib/apipie/apipie_module.rb +11 -0
- data/lib/apipie/application.rb +10 -0
- data/lib/apipie/configuration.rb +3 -1
- data/lib/apipie/errors.rb +26 -0
- data/lib/apipie/extractor/collector.rb +4 -0
- data/lib/apipie/response_description.rb +9 -3
- data/lib/apipie/response_description_adapter.rb +4 -3
- data/lib/apipie/rspec/response_validation_helper.rb +194 -0
- data/lib/apipie/swagger_generator.rb +68 -13
- data/lib/apipie/validator.rb +25 -0
- data/lib/apipie/version.rb +1 -1
- data/spec/dummy/app/controllers/pets_controller.rb +10 -1
- data/spec/dummy/config/routes.rb +16 -0
- data/spec/lib/swagger/response_validation_spec.rb +104 -0
- data/spec/lib/swagger/swagger_dsl_spec.rb +92 -2
- data/spec/lib/validator_spec.rb +6 -0
- data/spec/spec_helper.rb +3 -57
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67882facd3165a11bb36de196c2eaefd4444f698
|
4
|
+
data.tar.gz: 26ad0df6cd1f15d9f530838cf8dd9c7298dab91a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3b202e8d5774e1f669b93059df6152a7006177e04cb1ca3cde7151207aa3318310f75a374f67fa38752f3c7f53c18c3a72d0db165655460da97ec362465382f
|
7
|
+
data.tar.gz: f7541b0e319499e074b7171dd286b33f73a7b398e8e04a41bd5242b824f08f8bc83adabdadb75605e8143a40800729369f170da9e2d384f8acebea23f1aea9a1
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
Changelog
|
2
2
|
===========
|
3
3
|
|
4
|
+
v0.5.9
|
5
|
+
------
|
6
|
+
|
7
|
+
- Support response validation [\#619](https://github.com/Apipie/apipie-rails/pull/619) ([abenari](https://github.com/abenari))
|
8
|
+
- Expect :number to have type 'numeric' [\#614](https://github.com/Apipie/apipie-rails/pull/614) ([akofink](https://github.com/akofink))
|
9
|
+
- New validator - DecimalValidator [\#431](https://github.com/Apipie/apipie-rails/pull/431) ([kiddrew](https://github.com/kiddrew))
|
10
|
+
|
11
|
+
|
4
12
|
v0.5.8
|
5
13
|
------
|
6
14
|
|
data/README.rst
CHANGED
@@ -842,6 +842,77 @@ The concern needs to be included to the controller after the methods are defined
|
|
842
842
|
(either at the end of the class, or by using
|
843
843
|
``Controller.send(:include, Concerns::OauthConcern)``.
|
844
844
|
|
845
|
+
|
846
|
+
Response validation
|
847
|
+
-------------------
|
848
|
+
|
849
|
+
The swagger definitions created by Apipie can be used to auto-generate clients that access the
|
850
|
+
described APIs. Those clients will break if the responses returned from the API do not match
|
851
|
+
the declarations. As such, it is very important to include unit tests that validate the actual
|
852
|
+
responses against the swagger definitions.
|
853
|
+
|
854
|
+
The implemented mechanism provides two ways to include such validations in RSpec unit tests:
|
855
|
+
manual (using an RSpec matcher) and automated (by injecting a test into the http operations 'get', 'post',
|
856
|
+
raising an error if there is no match).
|
857
|
+
|
858
|
+
Example of the manual mechanism:
|
859
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
860
|
+
|
861
|
+
.. code:: ruby
|
862
|
+
|
863
|
+
require 'apipie/rspec/response_validation_helper'
|
864
|
+
|
865
|
+
RSpec.describe MyController, :type => :controller, :show_in_doc => true do
|
866
|
+
|
867
|
+
describe "GET stuff with response validation" do
|
868
|
+
render_views # this makes sure the 'get' operation will actually
|
869
|
+
# return the rendered view even though this is a Controller spec
|
870
|
+
|
871
|
+
it "does something" do
|
872
|
+
response = get :index, {format: :json}
|
873
|
+
|
874
|
+
# the following expectation will fail if the returned object
|
875
|
+
# does not match the 'returns' declaration in the Controller,
|
876
|
+
# or if there is no 'returns' declaration for the returned
|
877
|
+
# HTTP status code
|
878
|
+
expect(response).to match_declared_responses
|
879
|
+
end
|
880
|
+
end
|
881
|
+
end
|
882
|
+
|
883
|
+
|
884
|
+
Example of the automated mechanism:
|
885
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
886
|
+
|
887
|
+
.. code:: ruby
|
888
|
+
|
889
|
+
require 'apipie/rspec/response_validation_helper'
|
890
|
+
|
891
|
+
RSpec.describe MyController, :type => :controller, :show_in_doc => true do
|
892
|
+
|
893
|
+
describe "GET stuff with response validation" do
|
894
|
+
render_views
|
895
|
+
auto_validate_rendered_views
|
896
|
+
|
897
|
+
it "does something" do
|
898
|
+
get :index, {format: :json}
|
899
|
+
end
|
900
|
+
it "does something else" do
|
901
|
+
get :another_index, {format: :json}
|
902
|
+
end
|
903
|
+
end
|
904
|
+
|
905
|
+
describe "GET stuff without response validation" do
|
906
|
+
it "does something" do
|
907
|
+
get :index, {format: :json}
|
908
|
+
end
|
909
|
+
it "does something else" do
|
910
|
+
get :another_index, {format: :json}
|
911
|
+
end
|
912
|
+
end
|
913
|
+
end
|
914
|
+
|
915
|
+
|
845
916
|
=========================
|
846
917
|
Configuration Reference
|
847
918
|
=========================
|
@@ -1163,6 +1234,17 @@ ArrayValidator
|
|
1163
1234
|
|
1164
1235
|
Check if the parameter is an array
|
1165
1236
|
|
1237
|
+
DecimalValidator
|
1238
|
+
--------------
|
1239
|
+
|
1240
|
+
Check if the parameter is a decimal number
|
1241
|
+
|
1242
|
+
.. code:: ruby
|
1243
|
+
|
1244
|
+
param :latitude, :decimal, :desc => "Geographic latitude", :required => true
|
1245
|
+
param :longitude, :decimal, :desc => "Geographic longitude", :required => true
|
1246
|
+
|
1247
|
+
|
1166
1248
|
Additional options
|
1167
1249
|
~~~~~~~~~~~~~~~~~
|
1168
1250
|
|
data/lib/apipie/apipie_module.rb
CHANGED
@@ -18,6 +18,17 @@ module Apipie
|
|
18
18
|
app.to_swagger_json(version, resource_name, method_name, lang, clear_warnings)
|
19
19
|
end
|
20
20
|
|
21
|
+
def self.json_schema_for_method_response(controller_name, method_name, return_code, allow_nulls)
|
22
|
+
# note: this does not support versions (only the default version is queried)!
|
23
|
+
version ||= Apipie.configuration.default_version
|
24
|
+
app.json_schema_for_method_response(version, controller_name, method_name, return_code, allow_nulls)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.json_schema_for_self_describing_class(cls, allow_nulls=true)
|
28
|
+
app.json_schema_for_self_describing_class(cls, allow_nulls)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
21
32
|
# all calls delegated to Apipie::Application instance
|
22
33
|
def self.method_missing(method, *args, &block)
|
23
34
|
app.respond_to?(method) ? app.send(method, *args, &block) : super
|
data/lib/apipie/application.rb
CHANGED
@@ -255,6 +255,16 @@ module Apipie
|
|
255
255
|
@recorded_examples = nil
|
256
256
|
end
|
257
257
|
|
258
|
+
def json_schema_for_method_response(version, controller_name, method_name, return_code, allow_nulls)
|
259
|
+
method = @resource_descriptions[version][controller_name].method_description(method_name)
|
260
|
+
raise NoDocumentedMethod.new(controller_name, method_name) if method.nil?
|
261
|
+
@swagger_generator.json_schema_for_method_response(method, return_code, allow_nulls)
|
262
|
+
end
|
263
|
+
|
264
|
+
def json_schema_for_self_describing_class(cls, allow_nulls)
|
265
|
+
@swagger_generator.json_schema_for_self_describing_class(cls, allow_nulls)
|
266
|
+
end
|
267
|
+
|
258
268
|
def to_swagger_json(version, resource_name, method_name, lang, clear_warnings=false)
|
259
269
|
return unless valid_search_args?(version, resource_name, method_name)
|
260
270
|
|
data/lib/apipie/configuration.rb
CHANGED
@@ -11,13 +11,14 @@ module Apipie
|
|
11
11
|
:persist_show_in_doc, :authorize,
|
12
12
|
:swagger_include_warning_tags, :swagger_content_type_input, :swagger_json_input_uses_refs,
|
13
13
|
:swagger_suppress_warnings, :swagger_api_host, :swagger_generate_x_computed_id_field,
|
14
|
-
:swagger_allow_additional_properties_in_response
|
14
|
+
:swagger_allow_additional_properties_in_response, :swagger_responses_use_refs
|
15
15
|
|
16
16
|
alias_method :validate?, :validate
|
17
17
|
alias_method :required_by_default?, :required_by_default
|
18
18
|
alias_method :namespaced_resources?, :namespaced_resources
|
19
19
|
alias_method :swagger_include_warning_tags?, :swagger_include_warning_tags
|
20
20
|
alias_method :swagger_json_input_uses_refs?, :swagger_json_input_uses_refs
|
21
|
+
alias_method :swagger_responses_use_refs?, :swagger_responses_use_refs
|
21
22
|
alias_method :swagger_generate_x_computed_id_field?, :swagger_generate_x_computed_id_field
|
22
23
|
|
23
24
|
# matcher to be used in Dir.glob to find controllers to be reloaded e.g.
|
@@ -179,6 +180,7 @@ module Apipie
|
|
179
180
|
@swagger_api_host = "localhost:3000"
|
180
181
|
@swagger_generate_x_computed_id_field = false
|
181
182
|
@swagger_allow_additional_properties_in_response = false
|
183
|
+
@swagger_responses_use_refs = true
|
182
184
|
end
|
183
185
|
end
|
184
186
|
end
|
data/lib/apipie/errors.rb
CHANGED
@@ -57,4 +57,30 @@ module Apipie
|
|
57
57
|
"Invalid parameter '#{@param}' value #{@value.inspect}: #{@error}"
|
58
58
|
end
|
59
59
|
end
|
60
|
+
|
61
|
+
class ResponseDoesNotMatchSwaggerSchema < Error
|
62
|
+
def initialize(controller_name, method_name, response_code, error_messages, schema, returned_object)
|
63
|
+
@controller_name = controller_name
|
64
|
+
@method_name = method_name
|
65
|
+
@response_code = response_code
|
66
|
+
@error_messages = error_messages
|
67
|
+
@schema = schema
|
68
|
+
@returned_object = returned_object
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
"Response does not match swagger schema (#{@controller_name}##{@method_name} #{@response_code}): #{@error_messages}\nSchema: #{JSON(@schema)}\nReturned object: #{@returned_object}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class NoDocumentedMethod < Error
|
77
|
+
def initialize(controller_name, method_name)
|
78
|
+
@method_name = method_name
|
79
|
+
@controller_name = controller_name
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_s
|
83
|
+
"There is no documented method #{@controller_name}##{@method_name}"
|
84
|
+
end
|
85
|
+
end
|
60
86
|
end
|
@@ -75,6 +75,10 @@ module Apipie
|
|
75
75
|
if param_desc[:type].first == :number && (key.to_s !~ /id$/ || !Apipie::Validator::NumberValidator.validate(value))
|
76
76
|
param_desc[:type].shift
|
77
77
|
end
|
78
|
+
|
79
|
+
if param_desc[:type].first == :decimal && (key.to_s !~ /id$/ || !Apipie::Validator::DecimalValidator.validate(value))
|
80
|
+
param_desc[:type].shift
|
81
|
+
end
|
78
82
|
end
|
79
83
|
|
80
84
|
if value.is_a? Hash
|
@@ -5,13 +5,14 @@ module Apipie
|
|
5
5
|
include Apipie::DSL::Base
|
6
6
|
include Apipie::DSL::Param
|
7
7
|
|
8
|
-
attr_accessor :additional_properties
|
8
|
+
attr_accessor :additional_properties, :typename
|
9
9
|
|
10
|
-
def initialize(method_description, scope, block)
|
10
|
+
def initialize(method_description, scope, block, typename)
|
11
11
|
@method_description = method_description
|
12
12
|
@scope = scope
|
13
13
|
@param_group = {scope: scope}
|
14
14
|
@additional_properties = false
|
15
|
+
@typename = typename
|
15
16
|
|
16
17
|
self.instance_exec(&block) if block
|
17
18
|
|
@@ -67,6 +68,11 @@ module Apipie
|
|
67
68
|
@is_array_of != false
|
68
69
|
end
|
69
70
|
|
71
|
+
def typename
|
72
|
+
@response_object.typename
|
73
|
+
end
|
74
|
+
|
75
|
+
|
70
76
|
def initialize(method_description, code, options, scope, block, adapter)
|
71
77
|
|
72
78
|
@type_ref = options[:param_group]
|
@@ -93,7 +99,7 @@ module Apipie
|
|
93
99
|
if adapter
|
94
100
|
@response_object = adapter
|
95
101
|
else
|
96
|
-
@response_object = ResponseObject.new(method_description, scope, block)
|
102
|
+
@response_object = ResponseObject.new(method_description, scope, block, @type_ref)
|
97
103
|
end
|
98
104
|
|
99
105
|
@response_object.additional_properties ||= options[:additional_properties]
|
@@ -147,18 +147,19 @@ module Apipie
|
|
147
147
|
class ResponseDescriptionAdapter
|
148
148
|
|
149
149
|
def self.from_self_describing_class(cls)
|
150
|
-
adapter = ResponseDescriptionAdapter.new
|
150
|
+
adapter = ResponseDescriptionAdapter.new(cls.to_s)
|
151
151
|
props = cls.describe_own_properties
|
152
152
|
adapter.add_property_descriptions(props)
|
153
153
|
adapter
|
154
154
|
end
|
155
155
|
|
156
|
-
def initialize
|
156
|
+
def initialize(typename)
|
157
157
|
@property_descs = []
|
158
158
|
@additional_properties = false
|
159
|
+
@typename = typename
|
159
160
|
end
|
160
161
|
|
161
|
-
attr_accessor :additional_properties
|
162
|
+
attr_accessor :additional_properties, :typename
|
162
163
|
|
163
164
|
def allow_additional_properties
|
164
165
|
additional_properties
|
@@ -0,0 +1,194 @@
|
|
1
|
+
#----------------------------------------------------------------------------------------------
|
2
|
+
# response_validation_helper.rb:
|
3
|
+
#
|
4
|
+
# this is an rspec utility to allow validation of responses against the swagger schema generated
|
5
|
+
# from the Apipie 'returns' definition for the call.
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# to use this file in a controller rspec you should
|
9
|
+
# require 'apipie/rspec/response_validation_helper' in the spec file
|
10
|
+
#
|
11
|
+
#
|
12
|
+
# this utility provides two mechanisms: matcher-based validation and auto-validation
|
13
|
+
#
|
14
|
+
# matcher-based: an rspec matcher allowing 'expect(response).to match_declared_responses'
|
15
|
+
# auto-validation: all responses returned from 'get', 'post', etc. are automatically tested
|
16
|
+
#
|
17
|
+
# ===================================
|
18
|
+
# Matcher-based validation - example
|
19
|
+
# ===================================
|
20
|
+
# Assume the file 'my_controller_spec.rb':
|
21
|
+
#
|
22
|
+
# require 'apipie/rspec/response_validation_helper'
|
23
|
+
#
|
24
|
+
# RSpec.describe MyController, :type => :controller, :show_in_doc => true do
|
25
|
+
#
|
26
|
+
# describe "GET stuff with response validation" do
|
27
|
+
# render_views # this makes sure the 'get' operation will actually
|
28
|
+
# # return the rendered view even though this is a Controller spec
|
29
|
+
#
|
30
|
+
# it "does something" do
|
31
|
+
# response = get :index, {format: :json}
|
32
|
+
#
|
33
|
+
# # the following expectation will fail if the returned object
|
34
|
+
# # does not match the 'returns' declaration in the Controller,
|
35
|
+
# # or if there is no 'returns' declaration for the returned
|
36
|
+
# # HTTP status code
|
37
|
+
# expect(response).to match_declared_responses
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
#
|
42
|
+
# ===================================
|
43
|
+
# Auto-validation
|
44
|
+
# ===================================
|
45
|
+
# To use auto-validation, at the beginning of the block in which you want to turn on validation:
|
46
|
+
# -) turn on view rendering (by stating 'render_views')
|
47
|
+
# -) turn on response validation by stating 'auto_validate_rendered_views'
|
48
|
+
#
|
49
|
+
# For example, assume the file 'my_controller_spec.rb':
|
50
|
+
#
|
51
|
+
# require 'apipie/rspec/response_validation_helper'
|
52
|
+
#
|
53
|
+
# RSpec.describe MyController, :type => :controller, :show_in_doc => true do
|
54
|
+
#
|
55
|
+
# describe "GET stuff with response validation" do
|
56
|
+
# render_views
|
57
|
+
# auto_validate_rendered_views
|
58
|
+
#
|
59
|
+
# it "does something" do
|
60
|
+
# get :index, {format: :json}
|
61
|
+
# end
|
62
|
+
# it "does something else" do
|
63
|
+
# get :another_index, {format: :json}
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# describe "GET stuff without response validation" do
|
68
|
+
# it "does something" do
|
69
|
+
# get :index, {format: :json}
|
70
|
+
# end
|
71
|
+
# it "does something else" do
|
72
|
+
# get :another_index, {format: :json}
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
#
|
77
|
+
# Once this is done, responses from http operations ('get', 'post', 'delete', etc.)
|
78
|
+
# will fail the test if the response structure does not match the 'returns' declaration
|
79
|
+
# on the method (for the actual HTTP status code), or if there is no 'returns' declaration
|
80
|
+
# for the HTTP status code.
|
81
|
+
#----------------------------------------------------------------------------------------------
|
82
|
+
|
83
|
+
|
84
|
+
#----------------------------------------------------------------------------------------------
|
85
|
+
# Response validation: core logic (used by auto-validation and manual-validation mechanisms)
|
86
|
+
#----------------------------------------------------------------------------------------------
|
87
|
+
class ActionController::Base
|
88
|
+
module Apipie::ControllerValidationHelpers
|
89
|
+
# this method is injected into ActionController::Base in order to
|
90
|
+
# get access to the names of the current controller, current action, as well as to the response
|
91
|
+
def schema_validation_errors_for_response
|
92
|
+
unprocessed_schema = Apipie::json_schema_for_method_response(controller_name, action_name, response.code, true)
|
93
|
+
|
94
|
+
if unprocessed_schema.nil?
|
95
|
+
err = "no schema defined for #{controller_name}##{action_name}[#{response.code}]"
|
96
|
+
return [nil, [err], RuntimeError.new(err)]
|
97
|
+
end
|
98
|
+
|
99
|
+
schema = JSON.parse(JSON(unprocessed_schema))
|
100
|
+
|
101
|
+
error_list = JSON::Validator.fully_validate(schema, response.body, :strict => false, :version => :draft4, :json => true)
|
102
|
+
|
103
|
+
error_object = Apipie::ResponseDoesNotMatchSwaggerSchema.new(controller_name, action_name, response.code, error_list, schema, response.body)
|
104
|
+
|
105
|
+
[schema, error_list, error_object]
|
106
|
+
rescue Apipie::NoDocumentedMethod
|
107
|
+
[nil, [], nil]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
include Apipie::ControllerValidationHelpers
|
112
|
+
end
|
113
|
+
|
114
|
+
module Apipie
|
115
|
+
def self.print_validation_errors(validation_errors, schema, response, error_object=nil)
|
116
|
+
Rails.logger.warn(validation_errors.to_s)
|
117
|
+
if Rails.env.test?
|
118
|
+
puts "schema validation errors:"
|
119
|
+
validation_errors.each { |e| puts "--> #{e.to_s}" }
|
120
|
+
puts "schema: #{schema.nil? ? '<none>' : JSON(schema)}"
|
121
|
+
puts "response: #{response.body}"
|
122
|
+
raise error_object if error_object
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
#---------------------------------
|
128
|
+
# Manual-validation (RSpec matcher)
|
129
|
+
#---------------------------------
|
130
|
+
RSpec::Matchers.define :match_declared_responses do
|
131
|
+
match do |actual|
|
132
|
+
(schema, validation_errors) = subject.send(:schema_validation_errors_for_response)
|
133
|
+
valid = (validation_errors == [])
|
134
|
+
Apipie::print_validation_errors(validation_errors, schema, response) unless valid
|
135
|
+
|
136
|
+
valid
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
#---------------------------------
|
142
|
+
# Auto-validation logic
|
143
|
+
#---------------------------------
|
144
|
+
module RSpec::Rails::ViewRendering
|
145
|
+
# Augment the RSpec DSL
|
146
|
+
module ClassMethods
|
147
|
+
def auto_validate_rendered_views
|
148
|
+
before do
|
149
|
+
@is_response_validation_on = true
|
150
|
+
end
|
151
|
+
|
152
|
+
after do
|
153
|
+
@is_response_validation_on = false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
ActionController::TestCase::Behavior.instance_eval do
|
161
|
+
# instrument the 'process' method in ActionController::TestCase to enable response validation
|
162
|
+
module Apipie::ResponseValidationHelpers
|
163
|
+
@is_response_validation_on = false
|
164
|
+
def process(*args)
|
165
|
+
result = super(*args)
|
166
|
+
validate_response if @is_response_validation_on
|
167
|
+
|
168
|
+
result
|
169
|
+
end
|
170
|
+
|
171
|
+
def validate_response
|
172
|
+
controller.send(:validate_response_and_abort_with_info_if_errors)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
prepend Apipie::ResponseValidationHelpers
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
class ActionController::Base
|
181
|
+
module Apipie::ControllerValidationHelpers
|
182
|
+
def validate_response_and_abort_with_info_if_errors
|
183
|
+
|
184
|
+
(schema, validation_errors, error_object) = schema_validation_errors_for_response
|
185
|
+
|
186
|
+
valid = (validation_errors == [])
|
187
|
+
if !valid
|
188
|
+
Apipie::print_validation_errors(validation_errors, schema, response, error_object)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
|
@@ -24,6 +24,10 @@ module Apipie
|
|
24
24
|
Apipie.configuration.swagger_json_input_uses_refs
|
25
25
|
end
|
26
26
|
|
27
|
+
def responses_use_reference?
|
28
|
+
Apipie.configuration.swagger_responses_use_refs?
|
29
|
+
end
|
30
|
+
|
27
31
|
def include_warning_tags?
|
28
32
|
Apipie.configuration.swagger_include_warning_tags
|
29
33
|
end
|
@@ -259,6 +263,10 @@ module Apipie
|
|
259
263
|
remove_colons method.resource.controller.name + "::" + method.method
|
260
264
|
end
|
261
265
|
|
266
|
+
def swagger_id_for_typename(typename)
|
267
|
+
typename
|
268
|
+
end
|
269
|
+
|
262
270
|
def swagger_op_id_for_path(http_method, path)
|
263
271
|
# using lowercase http method, because the 'swagger-codegen' tool outputs
|
264
272
|
# strange method names if the http method is in uppercase
|
@@ -334,19 +342,42 @@ module Apipie
|
|
334
342
|
# Responses
|
335
343
|
#--------------------------------------------------------------------------
|
336
344
|
|
337
|
-
def
|
345
|
+
def json_schema_for_method_response(method, return_code, allow_nulls)
|
346
|
+
@definitions = {}
|
347
|
+
for response in method.returns
|
348
|
+
if response.code.to_s == return_code.to_s
|
349
|
+
schema = response_schema(response, allow_nulls) if response.code.to_s == return_code.to_s
|
350
|
+
schema[:definitions] = @definitions if @definitions != {}
|
351
|
+
return schema
|
352
|
+
end
|
353
|
+
end
|
354
|
+
nil
|
355
|
+
end
|
356
|
+
|
357
|
+
def json_schema_for_self_describing_class(cls, allow_nulls)
|
358
|
+
adapter = ResponseDescriptionAdapter.from_self_describing_class(cls)
|
359
|
+
response_schema(adapter, allow_nulls)
|
360
|
+
end
|
361
|
+
|
362
|
+
def response_schema(response, allow_nulls=false)
|
338
363
|
begin
|
339
364
|
# no need to warn about "missing default value for optional param" when processing response definitions
|
340
365
|
prev_value = @disable_default_value_warning
|
341
366
|
@disable_default_value_warning = true
|
342
|
-
|
367
|
+
|
368
|
+
if responses_use_reference? && response.typename
|
369
|
+
schema = {"$ref" => gen_referenced_block_from_params_array(swagger_id_for_typename(response.typename), response.params_ordered, allow_nulls)}
|
370
|
+
else
|
371
|
+
schema = json_schema_obj_from_params_array(response.params_ordered, allow_nulls)
|
372
|
+
end
|
373
|
+
|
343
374
|
ensure
|
344
375
|
@disable_default_value_warning = prev_value
|
345
376
|
end
|
346
377
|
|
347
378
|
if response.is_array? && schema
|
348
379
|
schema = {
|
349
|
-
type: "array",
|
380
|
+
type: allow_nulls ? ["array","null"] : "array",
|
350
381
|
items: schema
|
351
382
|
}
|
352
383
|
end
|
@@ -423,7 +454,7 @@ module Apipie
|
|
423
454
|
# The core routine for creating a swagger parameter definition block.
|
424
455
|
# The output is slightly different when the parameter is inside a schema block.
|
425
456
|
#--------------------------------------------------------------------------
|
426
|
-
def swagger_atomic_param(param_desc, in_schema, name)
|
457
|
+
def swagger_atomic_param(param_desc, in_schema, name, allow_nulls)
|
427
458
|
def save_field(entry, openapi_key, v, apipie_key=openapi_key, translate=false)
|
428
459
|
if v.key?(apipie_key)
|
429
460
|
if translate
|
@@ -444,7 +475,7 @@ module Apipie
|
|
444
475
|
end
|
445
476
|
|
446
477
|
if swagger_def[:type] == "array"
|
447
|
-
swagger_def[:items] = {type: "string"}
|
478
|
+
swagger_def[:items] = {type: "string"}
|
448
479
|
end
|
449
480
|
|
450
481
|
if swagger_def[:type] == "enum"
|
@@ -457,6 +488,21 @@ module Apipie
|
|
457
488
|
warn_hash_without_internal_typespec(param_desc.name)
|
458
489
|
end
|
459
490
|
|
491
|
+
if param_desc.is_array?
|
492
|
+
new_swagger_def = {
|
493
|
+
items: swagger_def,
|
494
|
+
type: 'array'
|
495
|
+
}
|
496
|
+
swagger_def = new_swagger_def
|
497
|
+
if allow_nulls
|
498
|
+
swagger_def[:type] = [swagger_def[:type], "null"]
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
if allow_nulls
|
503
|
+
swagger_def[:type] = [swagger_def[:type], "null"]
|
504
|
+
end
|
505
|
+
|
460
506
|
if !in_schema
|
461
507
|
swagger_def[:in] = param_desc.options.fetch(:in, @default_value_for_param_in)
|
462
508
|
swagger_def[:required] = param_desc.required if param_desc.required
|
@@ -487,8 +533,8 @@ module Apipie
|
|
487
533
|
end
|
488
534
|
|
489
535
|
|
490
|
-
def json_schema_obj_from_params_array(params_array)
|
491
|
-
(param_defs, required_params) = json_schema_param_defs_from_params_array(params_array)
|
536
|
+
def json_schema_obj_from_params_array(params_array, allow_nulls = false)
|
537
|
+
(param_defs, required_params) = json_schema_param_defs_from_params_array(params_array, allow_nulls)
|
492
538
|
|
493
539
|
result = {type: "object"}
|
494
540
|
result[:properties] = param_defs
|
@@ -498,17 +544,17 @@ module Apipie
|
|
498
544
|
param_defs.length > 0 ? result : nil
|
499
545
|
end
|
500
546
|
|
501
|
-
def gen_referenced_block_from_params_array(name, params_array)
|
547
|
+
def gen_referenced_block_from_params_array(name, params_array, allow_nulls=false)
|
502
548
|
return ref_to(:name) if @definitions.key(:name)
|
503
549
|
|
504
|
-
schema_obj = json_schema_obj_from_params_array(params_array)
|
550
|
+
schema_obj = json_schema_obj_from_params_array(params_array, allow_nulls)
|
505
551
|
return nil if schema_obj.nil?
|
506
552
|
|
507
553
|
@definitions[name.to_sym] = schema_obj
|
508
554
|
ref_to(name.to_sym)
|
509
555
|
end
|
510
556
|
|
511
|
-
def json_schema_param_defs_from_params_array(params_array)
|
557
|
+
def json_schema_param_defs_from_params_array(params_array, allow_nulls = false)
|
512
558
|
param_defs = {}
|
513
559
|
required_params = []
|
514
560
|
|
@@ -526,7 +572,7 @@ module Apipie
|
|
526
572
|
param_type = swagger_param_type(param_desc)
|
527
573
|
|
528
574
|
if param_type == "object" && param_desc.validator.params_ordered
|
529
|
-
schema = json_schema_obj_from_params_array(param_desc.validator.params_ordered)
|
575
|
+
schema = json_schema_obj_from_params_array(param_desc.validator.params_ordered, allow_nulls)
|
530
576
|
if param_desc.additional_properties
|
531
577
|
schema[:additionalProperties] = true
|
532
578
|
end
|
@@ -539,9 +585,18 @@ module Apipie
|
|
539
585
|
schema = new_schema
|
540
586
|
end
|
541
587
|
|
588
|
+
if allow_nulls
|
589
|
+
# ideally we would write schema[:type] = ["object", "null"]
|
590
|
+
# but due to a bug in the json-schema gem, we need to use anyOf
|
591
|
+
# see https://github.com/ruby-json-schema/json-schema/issues/404
|
592
|
+
new_schema = {
|
593
|
+
anyOf: [schema, {type: "null"}]
|
594
|
+
}
|
595
|
+
schema = new_schema
|
596
|
+
end
|
542
597
|
param_defs[param_desc.name.to_sym] = schema if !schema.nil?
|
543
598
|
else
|
544
|
-
param_defs[param_desc.name.to_sym] = swagger_atomic_param(param_desc, true, nil)
|
599
|
+
param_defs[param_desc.name.to_sym] = swagger_atomic_param(param_desc, true, nil, allow_nulls)
|
545
600
|
end
|
546
601
|
end
|
547
602
|
|
@@ -619,7 +674,7 @@ module Apipie
|
|
619
674
|
warn_param_ignored_in_form_data(desc.name)
|
620
675
|
end
|
621
676
|
else
|
622
|
-
param_entry = swagger_atomic_param(desc, false, name)
|
677
|
+
param_entry = swagger_atomic_param(desc, false, name, false)
|
623
678
|
if param_entry[:required]
|
624
679
|
swagger_params_array.unshift(param_entry)
|
625
680
|
else
|
data/lib/apipie/validator.rb
CHANGED
@@ -398,6 +398,27 @@ module Apipie
|
|
398
398
|
end
|
399
399
|
end
|
400
400
|
|
401
|
+
class DecimalValidator < BaseValidator
|
402
|
+
|
403
|
+
def validate(value)
|
404
|
+
self.class.validate(value)
|
405
|
+
end
|
406
|
+
|
407
|
+
def self.build(param_description, argument, options, block)
|
408
|
+
if argument == :decimal
|
409
|
+
self.new(param_description)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
def description
|
414
|
+
"Must be a decimal number."
|
415
|
+
end
|
416
|
+
|
417
|
+
def self.validate(value)
|
418
|
+
value.to_s =~ /\A^[-+]?[0-9]+([,.][0-9]+)?\Z$/
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
401
422
|
class NumberValidator < BaseValidator
|
402
423
|
|
403
424
|
def validate(value)
|
@@ -414,6 +435,10 @@ module Apipie
|
|
414
435
|
"Must be a number."
|
415
436
|
end
|
416
437
|
|
438
|
+
def expected_type
|
439
|
+
'numeric'
|
440
|
+
end
|
441
|
+
|
417
442
|
def self.validate(value)
|
418
443
|
value.to_s =~ /\A(0|[1-9]\d*)\Z$/
|
419
444
|
end
|
data/lib/apipie/version.rb
CHANGED
@@ -140,6 +140,10 @@ class PetsController < ApplicationController
|
|
140
140
|
param_group :pet_history
|
141
141
|
end
|
142
142
|
end
|
143
|
+
returns :code => 204 do
|
144
|
+
property :int_array, :array_of => Integer
|
145
|
+
property :enum_array, :array_of => ['v1','v2','v3']
|
146
|
+
end
|
143
147
|
returns :code => :unprocessable_entity, :desc => "Fleas were discovered on the pet" do
|
144
148
|
param_group :pet
|
145
149
|
property :num_fleas, Integer, :desc => "Number of fleas on this pet"
|
@@ -244,6 +248,12 @@ class PetsController < ApplicationController
|
|
244
248
|
render :json => result
|
245
249
|
end
|
246
250
|
|
251
|
+
#-----------------------------------------------------------
|
252
|
+
# A method with no documentation
|
253
|
+
#-----------------------------------------------------------
|
254
|
+
def undocumented_method
|
255
|
+
render :json => {:result => "ok"}
|
256
|
+
end
|
247
257
|
|
248
258
|
#-----------------------------------------------------------
|
249
259
|
# A method which has a response with a missing field
|
@@ -335,7 +345,6 @@ class PetsController < ApplicationController
|
|
335
345
|
render :json => result
|
336
346
|
end
|
337
347
|
|
338
|
-
|
339
348
|
#=======================================================================
|
340
349
|
# Methods for testing array field responses
|
341
350
|
#=======================================================================
|
data/spec/dummy/config/routes.rb
CHANGED
@@ -26,6 +26,22 @@ Dummy::Application.routes.draw do
|
|
26
26
|
get :contributors
|
27
27
|
end
|
28
28
|
end
|
29
|
+
|
30
|
+
get "/pets/return_and_validate_expected_response" => "pets#return_and_validate_expected_response"
|
31
|
+
get "/pets/return_and_validate_expected_array_response" => "pets#return_and_validate_expected_array_response"
|
32
|
+
get "/pets/return_and_validate_type_mismatch" => "pets#return_and_validate_type_mismatch"
|
33
|
+
get "/pets/return_and_validate_missing_field" => "pets#return_and_validate_missing_field"
|
34
|
+
get "/pets/return_and_validate_extra_property" => "pets#return_and_validate_extra_property"
|
35
|
+
get "/pets/return_and_validate_allowed_extra_property" => "pets#return_and_validate_allowed_extra_property"
|
36
|
+
get "/pets/sub_object_invalid_extra_property" => "pets#sub_object_invalid_extra_property"
|
37
|
+
get "/pets/sub_object_allowed_extra_property" => "pets#sub_object_allowed_extra_property"
|
38
|
+
get "/pets/return_and_validate_unexpected_array_response" => "pets#return_and_validate_unexpected_array_response"
|
39
|
+
get "/pets/return_and_validate_expected_response_with_null" => "pets#return_and_validate_expected_response_with_null"
|
40
|
+
get "/pets/return_and_validate_expected_response_with_null_object" => "pets#return_and_validate_expected_response_with_null_object"
|
41
|
+
|
42
|
+
get "/pets/returns_response_with_valid_array" => "pets#returns_response_with_valid_array"
|
43
|
+
get "/pets/returns_response_with_invalid_array" => "pets#returns_response_with_invalid_array"
|
44
|
+
get "/pets/undocumented_method" => "pets#undocumented_method"
|
29
45
|
end
|
30
46
|
|
31
47
|
apipie
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rack/utils'
|
3
|
+
require 'rspec/expectations'
|
4
|
+
require 'apipie/rspec/response_validation_helper'
|
5
|
+
require "json-schema"
|
6
|
+
|
7
|
+
RSpec.describe PetsController, :type => :controller do
|
8
|
+
before :each do
|
9
|
+
Apipie.configuration.swagger_allow_additional_properties_in_response = false
|
10
|
+
end
|
11
|
+
|
12
|
+
it "does not raise error when rendered output matches the described response" do
|
13
|
+
response = get :return_and_validate_expected_response, {format: :json}
|
14
|
+
expect(response).to match_declared_responses
|
15
|
+
end
|
16
|
+
|
17
|
+
it "does not raise error when rendered output (array) matches the described response" do
|
18
|
+
response = get :return_and_validate_expected_array_response, {format: :json}
|
19
|
+
expect(response).to match_declared_responses
|
20
|
+
end
|
21
|
+
|
22
|
+
it "does not raises error when rendered output includes null in the response" do
|
23
|
+
response = get :return_and_validate_expected_response_with_null, {format: :json}
|
24
|
+
expect(response).to match_declared_responses
|
25
|
+
end
|
26
|
+
|
27
|
+
it "does not raise error when rendered output includes null (instead of an object) in the response" do
|
28
|
+
response = get :return_and_validate_expected_response_with_null_object, {format: :json}
|
29
|
+
expect(response).to match_declared_responses
|
30
|
+
end
|
31
|
+
|
32
|
+
it "raises error when a response field has the wrong type" do
|
33
|
+
response = get :return_and_validate_type_mismatch, {format: :json}
|
34
|
+
expect(response).not_to match_declared_responses
|
35
|
+
end
|
36
|
+
|
37
|
+
it "raises error when a response has a missing field" do
|
38
|
+
response = get :return_and_validate_missing_field, {format: :json}
|
39
|
+
expect(response).not_to match_declared_responses
|
40
|
+
end
|
41
|
+
|
42
|
+
it "raises error when a response has an extra property and 'swagger_allow_additional_properties_in_response' is false" do
|
43
|
+
response = get :return_and_validate_extra_property, {format: :json}
|
44
|
+
expect(response).not_to match_declared_responses
|
45
|
+
end
|
46
|
+
|
47
|
+
it "raises error when a response has is array instead of object" do
|
48
|
+
# note: this action returns HTTP 201, not HTTP 200!
|
49
|
+
response = get :return_and_validate_unexpected_array_response, {format: :json}
|
50
|
+
expect(response).not_to match_declared_responses
|
51
|
+
end
|
52
|
+
|
53
|
+
it "does not raise error when a response has an extra property and 'swagger_allow_additional_properties_in_response' is true" do
|
54
|
+
Apipie.configuration.swagger_allow_additional_properties_in_response = true
|
55
|
+
response = get :return_and_validate_extra_property, {format: :json}
|
56
|
+
expect(response).to match_declared_responses
|
57
|
+
end
|
58
|
+
|
59
|
+
it "does not raise error when a response has an extra field and 'additional_properties' is specified in the response" do
|
60
|
+
Apipie.configuration.swagger_allow_additional_properties_in_response = false
|
61
|
+
response = get :return_and_validate_allowed_extra_property, {format: :json}
|
62
|
+
expect(response).to match_declared_responses
|
63
|
+
end
|
64
|
+
|
65
|
+
it "raises error when a response sub-object has an extra field and 'additional_properties' is not specified on it, but specified on the top level of the response" do
|
66
|
+
Apipie.configuration.swagger_allow_additional_properties_in_response = false
|
67
|
+
response = get :sub_object_invalid_extra_property, {format: :json}
|
68
|
+
expect(response).not_to match_declared_responses
|
69
|
+
end
|
70
|
+
|
71
|
+
it "does not rais error when a response sub-object has an extra field and 'additional_properties' is specified on it" do
|
72
|
+
Apipie.configuration.swagger_allow_additional_properties_in_response = false
|
73
|
+
response = get :sub_object_allowed_extra_property, {format: :json}
|
74
|
+
expect(response).to match_declared_responses
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "auto validation" do
|
78
|
+
auto_validate_rendered_views
|
79
|
+
it "raises exception when a response field has the wrong type and auto validation is turned on" do
|
80
|
+
expect { get :return_and_validate_type_mismatch, {format: :json} }.to raise_error(Apipie::ResponseDoesNotMatchSwaggerSchema)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "does not raise an exception when calling an undocumented method" do
|
84
|
+
expect { get :undocumented_method, {format: :json} }.not_to raise_error
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
describe "with array field" do
|
91
|
+
it "no error for valid response" do
|
92
|
+
response = get :returns_response_with_valid_array, {format: :json}
|
93
|
+
expect(response).to match_declared_responses
|
94
|
+
end
|
95
|
+
|
96
|
+
it "error if type of element in the array is wrong" do
|
97
|
+
response = get :returns_response_with_invalid_array, {format: :json}
|
98
|
+
expect(response).not_to match_declared_responses
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
end
|
@@ -12,8 +12,22 @@ describe "Swagger Responses" do
|
|
12
12
|
|
13
13
|
let(:controller_class ) { described_class }
|
14
14
|
|
15
|
+
def get_ref(ref)
|
16
|
+
name = ref.split('#/definitions/')[1].to_sym
|
17
|
+
swagger[:definitions][name]
|
18
|
+
end
|
19
|
+
|
20
|
+
def resolve_refs(schema)
|
21
|
+
if schema['$ref']
|
22
|
+
return get_ref(schema['$ref'])
|
23
|
+
end
|
24
|
+
schema
|
25
|
+
end
|
26
|
+
|
15
27
|
def swagger_response_for(path, code=200, method='get')
|
16
|
-
swagger[:paths][path][method][:responses][code]
|
28
|
+
response = swagger[:paths][path][method][:responses][code]
|
29
|
+
response[:schema] = resolve_refs(response[:schema])
|
30
|
+
response
|
17
31
|
end
|
18
32
|
|
19
33
|
def swagger_params_for(path, method='get')
|
@@ -32,6 +46,64 @@ describe "Swagger Responses" do
|
|
32
46
|
|
33
47
|
|
34
48
|
|
49
|
+
|
50
|
+
#
|
51
|
+
# Matcher to validate the hierarchy of fields described in an internal 'returns' object (without checking their type)
|
52
|
+
#
|
53
|
+
# For example, code such as:
|
54
|
+
# returns_obj = Apipie.get_resource_description(...)._methods.returns.detect{|e| e.code=200})
|
55
|
+
# expect(returns_obj).to match_param_structure([:pet_name, :animal_type, :pet_measurements => [:weight, :height]])
|
56
|
+
#
|
57
|
+
# will verify that the payload structure described for the response of return code 200 is:
|
58
|
+
# {
|
59
|
+
# "pet_name": <any>,
|
60
|
+
# "animal_type": <any>,
|
61
|
+
# "pet_measurements": {
|
62
|
+
# "weight": <any>,
|
63
|
+
# "height": <any>
|
64
|
+
# }
|
65
|
+
# }
|
66
|
+
#
|
67
|
+
#
|
68
|
+
RSpec::Matchers.define :match_field_structure do |expected|
|
69
|
+
@last_message = nil
|
70
|
+
|
71
|
+
match do |actual|
|
72
|
+
deep_match?(actual, expected)
|
73
|
+
end
|
74
|
+
|
75
|
+
def deep_match?(actual, expected, breadcrumb=[])
|
76
|
+
num = 0
|
77
|
+
for pdesc in expected do
|
78
|
+
if pdesc.is_a? Symbol
|
79
|
+
return false unless fields_match?(actual.params_ordered[num], pdesc, breadcrumb)
|
80
|
+
elsif pdesc.is_a? Hash
|
81
|
+
return false unless fields_match?(actual.params_ordered[num], pdesc.keys[0], breadcrumb)
|
82
|
+
return false unless deep_match?(actual.params_ordered[num].validator, pdesc.values[0], breadcrumb + [pdesc.keys[0]])
|
83
|
+
end
|
84
|
+
num+=1
|
85
|
+
end
|
86
|
+
@fail_message = "expected property count#{breadcrumb == [] ? '' : ' of ' + (breadcrumb).join('.')} (#{actual.params_ordered.count}) to be #{num}"
|
87
|
+
actual.params_ordered.count == num
|
88
|
+
end
|
89
|
+
|
90
|
+
def fields_match?(param, expected_name, breadcrumb)
|
91
|
+
return false unless have_field?(param, expected_name, breadcrumb)
|
92
|
+
@fail_message = "expected #{(breadcrumb + [param.name]).join('.')} to eq #{(breadcrumb + [expected_name]).join('.')}"
|
93
|
+
param.name.to_s == expected_name.to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
def have_field?(field, expected_name, breadcrumb)
|
97
|
+
@fail_message = "expected property #{(breadcrumb+[expected_name]).join('.')}"
|
98
|
+
!field.nil?
|
99
|
+
end
|
100
|
+
|
101
|
+
failure_message do |actual|
|
102
|
+
@fail_message
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
|
35
107
|
describe PetsController do
|
36
108
|
|
37
109
|
|
@@ -57,7 +129,7 @@ describe "Swagger Responses" do
|
|
57
129
|
schema = response[:schema]
|
58
130
|
expect(schema[:type]).to eq("array")
|
59
131
|
|
60
|
-
a_schema = schema[:items]
|
132
|
+
a_schema = resolve_refs(schema[:items])
|
61
133
|
expect(a_schema).to have_field(:pet_name, 'string', {:description => 'Name of pet', :required => false})
|
62
134
|
expect(a_schema).to have_field(:animal_type, 'string', {:description => 'Type of pet', :enum => ['dog','cat','iguana','kangaroo']})
|
63
135
|
end
|
@@ -320,6 +392,24 @@ describe "Swagger Responses" do
|
|
320
392
|
expect(pai_schema).to have_field(:avg_meals_per_day, 'number')
|
321
393
|
end
|
322
394
|
|
395
|
+
it "should return code 204 with array of integer" do
|
396
|
+
returns_obj = subject.returns.detect{|e| e.code == 204 }
|
397
|
+
|
398
|
+
puts returns_obj.to_json
|
399
|
+
expect(returns_obj.code).to eq(204)
|
400
|
+
expect(returns_obj.is_array?).to eq(false)
|
401
|
+
|
402
|
+
expect(returns_obj).to match_field_structure([:int_array, :enum_array])
|
403
|
+
end
|
404
|
+
it 'should have the 204 response described in the swagger' do
|
405
|
+
response = swagger_response_for('/pets/{id}/extra_info', 204)
|
406
|
+
|
407
|
+
schema = response[:schema]
|
408
|
+
expect(schema).to have_field(:int_array, 'array', {items: {type: 'number'}})
|
409
|
+
expect(schema).to have_field(:enum_array, 'array', {items: {type: 'string', enum: ['v1','v2','v3']}})
|
410
|
+
end
|
411
|
+
|
412
|
+
|
323
413
|
it "should return code matching :unprocessable_entity (422) with spread out 'pet' and 'num_fleas'" do
|
324
414
|
returns_obj = subject.returns.detect{|e| e.code == 422 }
|
325
415
|
|
data/spec/lib/validator_spec.rb
CHANGED
@@ -42,6 +42,12 @@ describe Apipie::Validator do
|
|
42
42
|
|
43
43
|
end
|
44
44
|
|
45
|
+
describe 'NumberValidator' do
|
46
|
+
it 'should expect a Numeric type' do
|
47
|
+
validator = Apipie::Validator::BaseValidator.find(params_desc, :number, nil, nil)
|
48
|
+
expect(validator.expected_type).to eq('numeric')
|
49
|
+
end
|
50
|
+
end
|
45
51
|
end
|
46
52
|
|
47
53
|
describe 'ArrayClassValidator' do
|
data/spec/spec_helper.rb
CHANGED
@@ -34,62 +34,6 @@ module Rails4Compatibility
|
|
34
34
|
end
|
35
35
|
|
36
36
|
|
37
|
-
#
|
38
|
-
# Matcher to validate the hierarchy of fields described in an internal 'returns' object (without checking their type)
|
39
|
-
#
|
40
|
-
# For example, code such as:
|
41
|
-
# returns_obj = Apipie.get_resource_description(...)._methods.returns.detect{|e| e.code=200})
|
42
|
-
# expect(returns_obj).to match_param_structure([:pet_name, :animal_type, :pet_measurements => [:weight, :height]])
|
43
|
-
#
|
44
|
-
# will verify that the payload structure described for the response of return code 200 is:
|
45
|
-
# {
|
46
|
-
# "pet_name": <any>,
|
47
|
-
# "animal_type": <any>,
|
48
|
-
# "pet_measurements": {
|
49
|
-
# "weight": <any>,
|
50
|
-
# "height": <any>
|
51
|
-
# }
|
52
|
-
# }
|
53
|
-
#
|
54
|
-
#
|
55
|
-
RSpec::Matchers.define :match_field_structure do |expected|
|
56
|
-
@last_message = nil
|
57
|
-
|
58
|
-
match do |actual|
|
59
|
-
deep_match?(actual, expected)
|
60
|
-
end
|
61
|
-
|
62
|
-
def deep_match?(actual, expected, breadcrumb=[])
|
63
|
-
num = 0
|
64
|
-
expected.each do |pdesc|
|
65
|
-
if pdesc.is_a? Symbol
|
66
|
-
return false unless matching_param(actual.params_ordered, pdesc, breadcrumb)
|
67
|
-
elsif pdesc.is_a? Hash
|
68
|
-
param = matching_param(actual.params_ordered, pdesc.keys[0], breadcrumb)
|
69
|
-
return false unless param
|
70
|
-
return false unless deep_match?(param.validator, pdesc.values[0], breadcrumb + [pdesc.keys[0]])
|
71
|
-
end
|
72
|
-
num+=1
|
73
|
-
end
|
74
|
-
@fail_message = "expected property count#{breadcrumb == [] ? '' : ' of ' + (breadcrumb).join('.')} (#{actual.params_ordered.count}) to be #{num}"
|
75
|
-
actual.params_ordered.count == num
|
76
|
-
end
|
77
|
-
|
78
|
-
def matching_param(params, expected_name, breadcrumb)
|
79
|
-
param = params.find { |p| p.name.to_s == expected_name.to_s }
|
80
|
-
unless param
|
81
|
-
@fail_message = "expected [#{ params.map(&:name).join(', ') }] to include #{(breadcrumb + [expected_name]).join('.')}"
|
82
|
-
end
|
83
|
-
param
|
84
|
-
end
|
85
|
-
|
86
|
-
failure_message do |actual|
|
87
|
-
@fail_message
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
37
|
#
|
94
38
|
# Matcher to validate the properties (name, type and options) of a single field in the
|
95
39
|
# internal representation of a swagger schema
|
@@ -112,12 +56,14 @@ RSpec::Matchers.define :have_field do |name, type, opts={}|
|
|
112
56
|
@fail_message
|
113
57
|
end
|
114
58
|
|
115
|
-
match do |
|
59
|
+
match do |unresolved|
|
60
|
+
actual = resolve_refs(unresolved)
|
116
61
|
return fail("expected schema to have type 'object' (got '#{actual[:type]}')") if (actual[:type]) != 'object'
|
117
62
|
return fail("expected schema to include param named '#{name}' (got #{actual[:properties].keys})") if (prop = actual[:properties][name]).nil?
|
118
63
|
return fail("expected param '#{name}' to have type '#{type}' (got '#{prop[:type]}')") if prop[:type] != type
|
119
64
|
return fail("expected param '#{name}' to have description '#{opts[:description]}' (got '#{prop[:description]}')") if opts[:description] && prop[:description] != opts[:description]
|
120
65
|
return fail("expected param '#{name}' to have enum '#{opts[:enum]}' (got #{prop[:enum]})") if opts[:enum] && prop[:enum] != opts[:enum]
|
66
|
+
return fail("expected param '#{name}' to have items '#{opts[:items]}' (got #{prop[:items]})") if opts[:items] && prop[:items] != opts[:items]
|
121
67
|
if !opts.include?(:required) || opts[:required] == true
|
122
68
|
return fail("expected param '#{name}' to be required") unless actual[:required].include?(name)
|
123
69
|
else
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apipie-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pavel Pokorny
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-
|
12
|
+
date: 2018-06-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -225,6 +225,7 @@ files:
|
|
225
225
|
- lib/apipie/response_description_adapter.rb
|
226
226
|
- lib/apipie/routes_formatter.rb
|
227
227
|
- lib/apipie/routing.rb
|
228
|
+
- lib/apipie/rspec/response_validation_helper.rb
|
228
229
|
- lib/apipie/see_description.rb
|
229
230
|
- lib/apipie/static_dispatcher.rb
|
230
231
|
- lib/apipie/swagger_generator.rb
|
@@ -302,6 +303,7 @@ files:
|
|
302
303
|
- spec/lib/resource_description_spec.rb
|
303
304
|
- spec/lib/swagger/openapi_2_0_schema.json
|
304
305
|
- spec/lib/swagger/rake_swagger_spec.rb
|
306
|
+
- spec/lib/swagger/response_validation_spec.rb
|
305
307
|
- spec/lib/swagger/swagger_dsl_spec.rb
|
306
308
|
- spec/lib/validator_spec.rb
|
307
309
|
- spec/lib/validators/array_validator_spec.rb
|
@@ -395,6 +397,7 @@ test_files:
|
|
395
397
|
- spec/lib/resource_description_spec.rb
|
396
398
|
- spec/lib/swagger/openapi_2_0_schema.json
|
397
399
|
- spec/lib/swagger/rake_swagger_spec.rb
|
400
|
+
- spec/lib/swagger/response_validation_spec.rb
|
398
401
|
- spec/lib/swagger/swagger_dsl_spec.rb
|
399
402
|
- spec/lib/validator_spec.rb
|
400
403
|
- spec/lib/validators/array_validator_spec.rb
|