apipie-rails 0.5.8 → 0.5.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|