grape-swagger 1.1.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +14 -0
- data/.github/workflows/rubocop.yml +26 -0
- data/.github/workflows/ruby.yml +32 -0
- data/.rubocop.yml +65 -2
- data/.rubocop_todo.yml +1 -1
- data/CHANGELOG.md +50 -0
- data/Gemfile +8 -3
- data/README.md +182 -13
- data/UPGRADING.md +34 -0
- data/grape-swagger.gemspec +4 -4
- data/lib/grape-swagger.rb +7 -4
- data/lib/grape-swagger/doc_methods.rb +65 -62
- data/lib/grape-swagger/doc_methods/build_model_definition.rb +53 -2
- data/lib/grape-swagger/doc_methods/data_type.rb +4 -4
- data/lib/grape-swagger/doc_methods/format_data.rb +2 -2
- data/lib/grape-swagger/doc_methods/operation_id.rb +2 -2
- data/lib/grape-swagger/doc_methods/parse_params.rb +20 -4
- data/lib/grape-swagger/endpoint.rb +83 -32
- data/lib/grape-swagger/errors.rb +2 -0
- data/lib/grape-swagger/model_parsers.rb +2 -2
- data/lib/grape-swagger/rake/oapi_tasks.rb +2 -0
- data/lib/grape-swagger/version.rb +1 -1
- data/spec/issues/427_entity_as_string_spec.rb +1 -1
- data/spec/issues/430_entity_definitions_spec.rb +7 -5
- data/spec/issues/537_enum_values_spec.rb +1 -0
- data/spec/issues/776_multiple_presents_spec.rb +59 -0
- data/spec/issues/809_utf8_routes_spec.rb +55 -0
- data/spec/lib/data_type_spec.rb +12 -0
- data/spec/lib/format_data_spec.rb +24 -0
- data/spec/lib/move_params_spec.rb +2 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/support/empty_model_parser.rb +3 -2
- data/spec/support/mock_parser.rb +1 -2
- data/spec/support/model_parsers/entity_parser.rb +8 -8
- data/spec/support/model_parsers/mock_parser.rb +24 -8
- data/spec/support/model_parsers/representable_parser.rb +8 -8
- data/spec/support/namespace_tags.rb +3 -0
- data/spec/swagger_v2/api_swagger_v2_hide_param_spec.rb +1 -1
- data/spec/swagger_v2/api_swagger_v2_mounted_spec.rb +1 -0
- data/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb +2 -2
- data/spec/swagger_v2/api_swagger_v2_response_with_headers_spec.rb +4 -2
- data/spec/swagger_v2/api_swagger_v2_response_with_models_spec.rb +53 -0
- data/spec/swagger_v2/api_swagger_v2_spec.rb +1 -0
- data/spec/swagger_v2/boolean_params_spec.rb +1 -0
- data/spec/swagger_v2/float_api_spec.rb +1 -0
- data/spec/swagger_v2/inheritance_and_discriminator_spec.rb +57 -0
- data/spec/swagger_v2/namespace_tags_prefix_spec.rb +1 -0
- data/spec/swagger_v2/param_multi_type_spec.rb +2 -0
- data/spec/swagger_v2/param_type_spec.rb +3 -0
- data/spec/swagger_v2/param_values_spec.rb +6 -0
- data/spec/swagger_v2/{params_array_collection_fromat_spec.rb → params_array_collection_format_spec.rb} +0 -0
- data/spec/swagger_v2/params_example_spec.rb +40 -0
- data/spec/swagger_v2/reference_entity_spec.rb +74 -29
- data/spec/swagger_v2/security_requirement_spec.rb +2 -2
- data/spec/swagger_v2/simple_mounted_api_spec.rb +3 -0
- metadata +27 -13
- data/.travis.yml +0 -35
data/UPGRADING.md
CHANGED
@@ -1,5 +1,39 @@
|
|
1
1
|
## Upgrading Grape-swagger
|
2
2
|
|
3
|
+
### Upgrading to >= 1.4.0
|
4
|
+
|
5
|
+
- Official support for ruby < 2.5 removed, ruby 2.5 only in testing mode, but no support.
|
6
|
+
|
7
|
+
### Upgrading to >= 1.3.0
|
8
|
+
|
9
|
+
- The model (entity) description no longer comes from the route description. It will have a default value: `<<EntityName>> model`.
|
10
|
+
|
11
|
+
### Upgrading to >= 1.2.0
|
12
|
+
|
13
|
+
- The entity_name class method is now called on parent classes for inherited entities. Now you can do this
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
module Some::Long::Module
|
17
|
+
class Base < Grape::Entity
|
18
|
+
# ... other shared logic
|
19
|
+
def self.entity_name
|
20
|
+
"V2::#{self.to_s.demodulize}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def MyEntity < Base
|
25
|
+
# ....
|
26
|
+
end
|
27
|
+
|
28
|
+
def OtherEntity < Base
|
29
|
+
# revert back to the default behavior by hiding the method
|
30
|
+
private_class_method :entity_name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
- Full class name is modified to use `_` separator (e.g. `A_B_C` instead of `A::B::C`).
|
36
|
+
|
3
37
|
### Upgrading to >= 1.1.0
|
4
38
|
|
5
39
|
Full class name is used for referencing entity by default (e.g. `A::B::C` instead of just `C`). `Entity` and `Entities` suffixes and prefixes are omitted (e.g. if entity name is `Entities::SomeScope::MyFavourite::Entity` only `SomeScope::MyFavourite` will be used).
|
data/grape-swagger.gemspec
CHANGED
@@ -7,14 +7,14 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.name = 'grape-swagger'
|
8
8
|
s.version = GrapeSwagger::VERSION
|
9
9
|
s.platform = Gem::Platform::RUBY
|
10
|
-
s.authors = ['Tim Vandecasteele']
|
11
|
-
s.email = ['tim.vandecasteele@gmail.com']
|
10
|
+
s.authors = ['LeFnord', 'Tim Vandecasteele']
|
11
|
+
s.email = ['pscholz.le@gmail.com', 'tim.vandecasteele@gmail.com']
|
12
12
|
s.homepage = 'https://github.com/ruby-grape/grape-swagger'
|
13
13
|
s.summary = 'Add auto generated documentation to your Grape API that can be displayed with Swagger.'
|
14
14
|
s.license = 'MIT'
|
15
15
|
|
16
|
-
s.required_ruby_version = '>= 2.
|
17
|
-
s.add_runtime_dependency 'grape', '~> 1.3
|
16
|
+
s.required_ruby_version = '>= 2.5'
|
17
|
+
s.add_runtime_dependency 'grape', '~> 1.3'
|
18
18
|
|
19
19
|
s.files = `git ls-files`.split("\n")
|
20
20
|
s.test_files = `git ls-files -- {test,spec}/*`.split("\n")
|
data/lib/grape-swagger.rb
CHANGED
@@ -29,7 +29,10 @@ module SwaggerRouting
|
|
29
29
|
route_match = route_path.split(/^.*?#{route.prefix}/).last
|
30
30
|
next unless route_match
|
31
31
|
|
32
|
-
|
32
|
+
# want to match emojis … ;)
|
33
|
+
# route_match = route_match
|
34
|
+
# .match('\/([\p{Alnum}|\p{Emoji}|\-|\_]*?)[\.\/\(]') || route_match.match('\/([\p{Alpha}|\p{Emoji}|\-|\_]*)$')
|
35
|
+
route_match = route_match.match('\/([\p{Alnum}|\-|\_]*?)[\.\/\(]') || route_match.match('\/([\p{Alpha}|\-|\_]*)$')
|
33
36
|
next unless route_match
|
34
37
|
|
35
38
|
resource = route_match.captures.first
|
@@ -85,7 +88,7 @@ module SwaggerRouting
|
|
85
88
|
route_name = name.match(%r{^/?([^/]*).*$})[1]
|
86
89
|
return route_name unless route_name.include? ':'
|
87
90
|
|
88
|
-
matches = name.match(
|
91
|
+
matches = name.match(/\/\p{Alpha}+/)
|
89
92
|
matches.nil? ? route_name : matches[0].delete('/')
|
90
93
|
end
|
91
94
|
|
@@ -107,8 +110,8 @@ module SwaggerRouting
|
|
107
110
|
end
|
108
111
|
|
109
112
|
module SwaggerDocumentationAdder
|
110
|
-
attr_accessor :combined_namespaces, :combined_namespace_identifiers
|
111
|
-
|
113
|
+
attr_accessor :combined_namespaces, :combined_namespace_identifiers, :combined_routes, :combined_namespace_routes
|
114
|
+
|
112
115
|
include SwaggerRouting
|
113
116
|
|
114
117
|
def add_swagger_documentation(options = {})
|
@@ -17,6 +17,61 @@ require 'grape-swagger/doc_methods/version'
|
|
17
17
|
|
18
18
|
module GrapeSwagger
|
19
19
|
module DocMethods
|
20
|
+
DEFAULTS =
|
21
|
+
{
|
22
|
+
info: {},
|
23
|
+
models: [],
|
24
|
+
doc_version: '0.0.1',
|
25
|
+
target_class: nil,
|
26
|
+
mount_path: '/swagger_doc',
|
27
|
+
host: nil,
|
28
|
+
base_path: nil,
|
29
|
+
add_base_path: false,
|
30
|
+
add_version: true,
|
31
|
+
add_root: false,
|
32
|
+
hide_documentation_path: true,
|
33
|
+
format: :json,
|
34
|
+
authorizations: nil,
|
35
|
+
security_definitions: nil,
|
36
|
+
security: nil,
|
37
|
+
api_documentation: { desc: 'Swagger compatible API description' },
|
38
|
+
specific_api_documentation: { desc: 'Swagger compatible API description for specific API' },
|
39
|
+
endpoint_auth_wrapper: nil,
|
40
|
+
swagger_endpoint_guard: nil,
|
41
|
+
token_owner: nil
|
42
|
+
}.freeze
|
43
|
+
|
44
|
+
FORMATTER_METHOD = %i[format default_format default_error_formatter].freeze
|
45
|
+
|
46
|
+
def self.output_path_definitions(combi_routes, endpoint, target_class, options)
|
47
|
+
output = endpoint.swagger_object(
|
48
|
+
target_class,
|
49
|
+
endpoint.request,
|
50
|
+
options
|
51
|
+
)
|
52
|
+
|
53
|
+
paths, definitions = endpoint.path_and_definition_objects(combi_routes, options)
|
54
|
+
tags = tags_from(paths, options)
|
55
|
+
|
56
|
+
output[:tags] = tags unless tags.empty? || paths.blank?
|
57
|
+
output[:paths] = paths unless paths.blank?
|
58
|
+
output[:definitions] = definitions unless definitions.blank?
|
59
|
+
|
60
|
+
output
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.tags_from(paths, options)
|
64
|
+
tags = GrapeSwagger::DocMethods::TagNameDescription.build(paths)
|
65
|
+
|
66
|
+
if options[:tags]
|
67
|
+
names = options[:tags].map { |t| t[:name] }
|
68
|
+
tags.reject! { |t| names.include?(t[:name]) }
|
69
|
+
tags += options[:tags]
|
70
|
+
end
|
71
|
+
|
72
|
+
tags
|
73
|
+
end
|
74
|
+
|
20
75
|
def hide_documentation_path
|
21
76
|
@@hide_documentation_path
|
22
77
|
end
|
@@ -26,54 +81,32 @@ module GrapeSwagger
|
|
26
81
|
end
|
27
82
|
|
28
83
|
def setup(options)
|
29
|
-
options =
|
84
|
+
options = DEFAULTS.merge(options)
|
30
85
|
|
31
86
|
# options could be set on #add_swagger_documentation call,
|
32
87
|
# for available options see #defaults
|
33
88
|
target_class = options[:target_class]
|
34
89
|
guard = options[:swagger_endpoint_guard]
|
35
|
-
formatter = options[:format]
|
36
90
|
api_doc = options[:api_documentation].dup
|
37
91
|
specific_api_doc = options[:specific_api_documentation].dup
|
38
92
|
|
39
93
|
class_variables_from(options)
|
40
94
|
|
41
|
-
|
42
|
-
%i[format default_format default_error_formatter].each do |method|
|
43
|
-
send(method, formatter)
|
44
|
-
end
|
45
|
-
end
|
95
|
+
setup_formatter(options[:format])
|
46
96
|
|
47
97
|
desc api_doc.delete(:desc), api_doc
|
48
98
|
|
49
|
-
output_path_definitions = proc do |combi_routes, endpoint|
|
50
|
-
output = endpoint.swagger_object(
|
51
|
-
target_class,
|
52
|
-
endpoint.request,
|
53
|
-
options
|
54
|
-
)
|
55
|
-
|
56
|
-
paths, definitions = endpoint.path_and_definition_objects(combi_routes, options)
|
57
|
-
tags = tags_from(paths, options)
|
58
|
-
|
59
|
-
output[:tags] = tags unless tags.empty? || paths.blank?
|
60
|
-
output[:paths] = paths unless paths.blank?
|
61
|
-
output[:definitions] = definitions unless definitions.blank?
|
62
|
-
|
63
|
-
output
|
64
|
-
end
|
65
|
-
|
66
99
|
instance_eval(guard) unless guard.nil?
|
67
100
|
|
68
101
|
get mount_path do
|
69
102
|
header['Access-Control-Allow-Origin'] = '*'
|
70
103
|
header['Access-Control-Request-Method'] = '*'
|
71
104
|
|
72
|
-
|
105
|
+
GrapeSwagger::DocMethods
|
106
|
+
.output_path_definitions(target_class.combined_namespace_routes, self, target_class, options)
|
73
107
|
end
|
74
108
|
|
75
|
-
desc specific_api_doc.delete(:desc), { params:
|
76
|
-
specific_api_doc.delete(:params) || {} }.merge(specific_api_doc)
|
109
|
+
desc specific_api_doc.delete(:desc), { params: specific_api_doc.delete(:params) || {}, **specific_api_doc }
|
77
110
|
|
78
111
|
params do
|
79
112
|
requires :name, type: String, desc: 'Resource name of mounted API'
|
@@ -88,51 +121,21 @@ module GrapeSwagger
|
|
88
121
|
combined_routes = target_class.combined_namespace_routes[params[:name]]
|
89
122
|
error!({ error: 'named resource not exist' }, 400) if combined_routes.nil?
|
90
123
|
|
91
|
-
|
124
|
+
GrapeSwagger::DocMethods
|
125
|
+
.output_path_definitions({ params[:name] => combined_routes }, self, target_class, options)
|
92
126
|
end
|
93
127
|
end
|
94
128
|
|
95
|
-
def defaults
|
96
|
-
{
|
97
|
-
info: {},
|
98
|
-
models: [],
|
99
|
-
doc_version: '0.0.1',
|
100
|
-
target_class: nil,
|
101
|
-
mount_path: '/swagger_doc',
|
102
|
-
host: nil,
|
103
|
-
base_path: nil,
|
104
|
-
add_base_path: false,
|
105
|
-
add_version: true,
|
106
|
-
add_root: false,
|
107
|
-
hide_documentation_path: true,
|
108
|
-
format: :json,
|
109
|
-
authorizations: nil,
|
110
|
-
security_definitions: nil,
|
111
|
-
security: nil,
|
112
|
-
api_documentation: { desc: 'Swagger compatible API description' },
|
113
|
-
specific_api_documentation: { desc: 'Swagger compatible API description for specific API' },
|
114
|
-
endpoint_auth_wrapper: nil,
|
115
|
-
swagger_endpoint_guard: nil,
|
116
|
-
token_owner: nil
|
117
|
-
}
|
118
|
-
end
|
119
|
-
|
120
129
|
def class_variables_from(options)
|
121
130
|
@@mount_path = options[:mount_path]
|
122
131
|
@@class_name = options[:class_name] || options[:mount_path].delete('/')
|
123
132
|
@@hide_documentation_path = options[:hide_documentation_path]
|
124
133
|
end
|
125
134
|
|
126
|
-
def
|
127
|
-
|
135
|
+
def setup_formatter(formatter)
|
136
|
+
return unless formatter
|
128
137
|
|
129
|
-
|
130
|
-
names = options[:tags].map { |t| t[:name] }
|
131
|
-
tags.reject! { |t| names.include?(t[:name]) }
|
132
|
-
tags += options[:tags]
|
133
|
-
end
|
134
|
-
|
135
|
-
tags
|
138
|
+
FORMATTER_METHOD.each { |method| send(method, formatter) }
|
136
139
|
end
|
137
140
|
end
|
138
141
|
end
|
@@ -4,8 +4,8 @@ module GrapeSwagger
|
|
4
4
|
module DocMethods
|
5
5
|
class BuildModelDefinition
|
6
6
|
class << self
|
7
|
-
def build(model, properties, required)
|
8
|
-
definition = { type: 'object', properties: properties }
|
7
|
+
def build(model, properties, required, other_def_properties = {})
|
8
|
+
definition = { type: 'object', properties: properties }.merge(other_def_properties)
|
9
9
|
|
10
10
|
if required.nil?
|
11
11
|
required_attrs = required_attributes(model)
|
@@ -17,6 +17,57 @@ module GrapeSwagger
|
|
17
17
|
definition
|
18
18
|
end
|
19
19
|
|
20
|
+
def parse_params_from_model(parsed_response, model, model_name)
|
21
|
+
if parsed_response.is_a?(Hash) && parsed_response.keys.first == :allOf
|
22
|
+
refs_or_models = parsed_response[:allOf]
|
23
|
+
parsed = parse_refs_and_models(refs_or_models, model)
|
24
|
+
|
25
|
+
{
|
26
|
+
allOf: parsed
|
27
|
+
}
|
28
|
+
else
|
29
|
+
properties, required = parsed_response
|
30
|
+
unless properties&.any?
|
31
|
+
raise GrapeSwagger::Errors::SwaggerSpec,
|
32
|
+
"Empty model #{model_name}, swagger 2.0 doesn't support empty definitions."
|
33
|
+
end
|
34
|
+
properties, other_def_properties = parse_properties(properties)
|
35
|
+
|
36
|
+
build(
|
37
|
+
model, properties, required, other_def_properties
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_properties(properties)
|
43
|
+
other_properties = {}
|
44
|
+
|
45
|
+
discriminator_key, discriminator_value =
|
46
|
+
properties.find do |_key, value|
|
47
|
+
value[:documentation].try(:[], :is_discriminator)
|
48
|
+
end
|
49
|
+
|
50
|
+
if discriminator_key
|
51
|
+
discriminator_value.delete(:documentation)
|
52
|
+
properties[discriminator_key] = discriminator_value
|
53
|
+
|
54
|
+
other_properties[:discriminator] = discriminator_key
|
55
|
+
end
|
56
|
+
|
57
|
+
[properties, other_properties]
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_refs_and_models(refs_or_models, model)
|
61
|
+
refs_or_models.map do |ref_or_models|
|
62
|
+
if ref_or_models.is_a?(Hash) && ref_or_models.keys.first == '$ref'
|
63
|
+
ref_or_models
|
64
|
+
else
|
65
|
+
properties, required = ref_or_models
|
66
|
+
GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
20
71
|
private
|
21
72
|
|
22
73
|
def required_attributes(model)
|
@@ -48,14 +48,14 @@ module GrapeSwagger
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def parse_entity_name(model)
|
51
|
-
if model.
|
51
|
+
if model.respond_to?(:entity_name)
|
52
52
|
model.entity_name
|
53
53
|
elsif model.to_s.end_with?('::Entity', '::Entities')
|
54
|
-
model.to_s.split('::')[0..-2].join('
|
54
|
+
model.to_s.split('::')[0..-2].join('_')
|
55
55
|
elsif model.to_s.start_with?('Entity::', 'Entities::', 'Representable::')
|
56
|
-
model.to_s.split('::')[1..-1].join('
|
56
|
+
model.to_s.split('::')[1..-1].join('_')
|
57
57
|
else
|
58
|
-
model.to_s
|
58
|
+
model.to_s.split('::').join('_')
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -7,7 +7,7 @@ module GrapeSwagger
|
|
7
7
|
def to_format(parameters)
|
8
8
|
parameters.reject { |parameter| parameter[:in] == 'body' }.each do |b|
|
9
9
|
related_parameters = parameters.select do |p|
|
10
|
-
p[:name] != b[:name] && p[:name].to_s.
|
10
|
+
p[:name] != b[:name] && p[:name].to_s.start_with?("#{b[:name].to_s.gsub(/\[\]\z/, '')}[")
|
11
11
|
end
|
12
12
|
parameters.reject! { |p| p[:name] == b[:name] } if move_down(b, related_parameters)
|
13
13
|
end
|
@@ -30,7 +30,7 @@ module GrapeSwagger
|
|
30
30
|
|
31
31
|
def add_braces(parameter, related_parameters)
|
32
32
|
param_name = parameter[:name].gsub(/\A(.*)\[\]\z/, '\1')
|
33
|
-
related_parameters.each { |p| p[:name] = p[:name].gsub(param_name, param_name
|
33
|
+
related_parameters.each { |p| p[:name] = p[:name].gsub(param_name, "#{param_name}[]") }
|
34
34
|
end
|
35
35
|
|
36
36
|
def add_array(parameter, related_parameters)
|
@@ -16,8 +16,8 @@ module GrapeSwagger
|
|
16
16
|
|
17
17
|
def manipulate(path)
|
18
18
|
operation = path.split('/').map(&:capitalize).join
|
19
|
-
operation.gsub!(
|
20
|
-
operation.gsub!(
|
19
|
+
operation.gsub!(/-(\w)/, &:upcase).delete!('-') if operation[/-(\w)/]
|
20
|
+
operation.gsub!(/_(\w)/, &:upcase).delete!('_') if operation.include?('_')
|
21
21
|
operation.gsub!(/\.(\w)/, &:upcase).delete!('.') if operation[/\.(\w)/]
|
22
22
|
if path.include?('{')
|
23
23
|
operation.gsub!(/\{(\w)/, &:upcase)
|
@@ -27,6 +27,7 @@ module GrapeSwagger
|
|
27
27
|
document_required(settings)
|
28
28
|
document_additional_properties(settings)
|
29
29
|
document_add_extensions(settings)
|
30
|
+
document_example(settings)
|
30
31
|
|
31
32
|
@parsed_param
|
32
33
|
end
|
@@ -77,6 +78,19 @@ module GrapeSwagger
|
|
77
78
|
|
78
79
|
param_type ||= value_type[:param_type]
|
79
80
|
|
81
|
+
array_items = parse_array_item(
|
82
|
+
definitions,
|
83
|
+
type,
|
84
|
+
value_type
|
85
|
+
)
|
86
|
+
|
87
|
+
@parsed_param[:in] = param_type || 'formData'
|
88
|
+
@parsed_param[:items] = array_items
|
89
|
+
@parsed_param[:type] = 'array'
|
90
|
+
@parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
|
91
|
+
end
|
92
|
+
|
93
|
+
def parse_array_item(definitions, type, value_type)
|
80
94
|
array_items = {}
|
81
95
|
if definitions[value_type[:data_type]]
|
82
96
|
array_items['$ref'] = "#/definitions/#{@parsed_param[:type]}"
|
@@ -91,10 +105,7 @@ module GrapeSwagger
|
|
91
105
|
|
92
106
|
array_items[:default] = value_type[:default] if value_type[:default].present?
|
93
107
|
|
94
|
-
|
95
|
-
@parsed_param[:items] = array_items
|
96
|
-
@parsed_param[:type] = 'array'
|
97
|
-
@parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
|
108
|
+
array_items
|
98
109
|
end
|
99
110
|
|
100
111
|
def document_additional_properties(settings)
|
@@ -102,6 +113,11 @@ module GrapeSwagger
|
|
102
113
|
@parsed_param[:additionalProperties] = additional_properties if additional_properties
|
103
114
|
end
|
104
115
|
|
116
|
+
def document_example(settings)
|
117
|
+
example = settings[:example]
|
118
|
+
@parsed_param[:example] = example if example
|
119
|
+
end
|
120
|
+
|
105
121
|
def param_type(value_type)
|
106
122
|
param_type = value_type[:param_type] || value_type[:in]
|
107
123
|
if value_type[:path].include?("{#{value_type[:param_name]}}")
|
@@ -11,7 +11,7 @@ module Grape
|
|
11
11
|
|
12
12
|
if content_types.empty?
|
13
13
|
formats = [target_class.format, target_class.default_format].compact.uniq
|
14
|
-
formats = Grape::Formatter.formatters({}).keys if formats.empty?
|
14
|
+
formats = Grape::Formatter.formatters(**{}).keys if formats.empty?
|
15
15
|
content_types = Grape::ContentTypes::CONTENT_TYPES.select do |content_type, _mime_type|
|
16
16
|
formats.include? content_type
|
17
17
|
end.values
|
@@ -78,11 +78,11 @@ module Grape
|
|
78
78
|
def path_and_definition_objects(namespace_routes, options)
|
79
79
|
@paths = {}
|
80
80
|
@definitions = {}
|
81
|
+
add_definitions_from options[:models]
|
81
82
|
namespace_routes.each_value do |routes|
|
82
83
|
path_item(routes, options)
|
83
84
|
end
|
84
85
|
|
85
|
-
add_definitions_from options[:models]
|
86
86
|
[@paths, @definitions]
|
87
87
|
end
|
88
88
|
|
@@ -175,15 +175,18 @@ module Grape
|
|
175
175
|
end
|
176
176
|
|
177
177
|
def params_object(route, options, path)
|
178
|
-
parameters =
|
178
|
+
parameters = build_request_params(route, options).each_with_object([]) do |(param, value), memo|
|
179
|
+
next if hidden_parameter?(value)
|
180
|
+
|
179
181
|
value = { required: false }.merge(value) if value.is_a?(Hash)
|
180
182
|
_, value = default_type([[param, value]]).first if value == ''
|
183
|
+
|
181
184
|
if value.dig(:documentation, :type)
|
182
185
|
expose_params(value[:documentation][:type])
|
183
186
|
elsif value[:type]
|
184
187
|
expose_params(value[:type])
|
185
188
|
end
|
186
|
-
GrapeSwagger::DocMethods::ParseParams.call(param, value, path, route, @definitions)
|
189
|
+
memo << GrapeSwagger::DocMethods::ParseParams.call(param, value, path, route, @definitions)
|
187
190
|
end
|
188
191
|
|
189
192
|
if GrapeSwagger::DocMethods::MoveParams.can_be_moved?(route.request_method, parameters)
|
@@ -196,35 +199,38 @@ module Grape
|
|
196
199
|
end
|
197
200
|
|
198
201
|
def response_object(route, options)
|
199
|
-
codes
|
200
|
-
codes.map! { |x| x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x }
|
201
|
-
|
202
|
-
codes.each_with_object({}) do |value, memo|
|
202
|
+
codes(route).each_with_object({}) do |value, memo|
|
203
203
|
value[:message] ||= ''
|
204
|
-
memo[value[:code]] = { description: value[:message] }
|
205
|
-
|
204
|
+
memo[value[:code]] = { description: value[:message] ||= '' } unless memo[value[:code]].present?
|
206
205
|
memo[value[:code]][:headers] = value[:headers] if value[:headers]
|
207
206
|
|
208
207
|
next build_file_response(memo[value[:code]]) if file_response?(value[:model])
|
209
208
|
|
210
|
-
response_model = @item
|
211
|
-
response_model = expose_params_from_model(value[:model]) if value[:model]
|
212
|
-
|
213
209
|
if memo.key?(200) && route.request_method == 'DELETE' && value[:model].nil?
|
214
210
|
memo[204] = memo.delete(200)
|
215
211
|
value[:code] = 204
|
212
|
+
next
|
216
213
|
end
|
217
214
|
|
218
|
-
|
219
|
-
next
|
215
|
+
# Explicitly request no model with { model: '' }
|
216
|
+
next if value[:model] == ''
|
220
217
|
|
221
|
-
|
218
|
+
response_model = value[:model] ? expose_params_from_model(value[:model]) : @item
|
219
|
+
next unless @definitions[response_model]
|
220
|
+
next if response_model.start_with?('Swagger_doc')
|
222
221
|
|
223
|
-
|
222
|
+
@definitions[response_model][:description] ||= "#{response_model} model"
|
223
|
+
build_memo_schema(memo, route, value, response_model, options)
|
224
224
|
memo[value[:code]][:examples] = value[:examples] if value[:examples]
|
225
225
|
end
|
226
226
|
end
|
227
227
|
|
228
|
+
def codes(route)
|
229
|
+
http_codes_from_route(route).map do |x|
|
230
|
+
x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
228
234
|
def success_code?(code)
|
229
235
|
status = code.is_a?(Array) ? code.first : code[:code]
|
230
236
|
status.between?(200, 299)
|
@@ -250,7 +256,7 @@ module Grape
|
|
250
256
|
|
251
257
|
def tag_object(route, path)
|
252
258
|
version = GrapeSwagger::DocMethods::Version.get(route)
|
253
|
-
version =
|
259
|
+
version = Array(version)
|
254
260
|
prefix = route.prefix.to_s.split('/').reject(&:empty?)
|
255
261
|
Array(
|
256
262
|
path.split('{')[0].split('/').reject(&:empty?).delete_if do |i|
|
@@ -261,15 +267,52 @@ module Grape
|
|
261
267
|
|
262
268
|
private
|
263
269
|
|
270
|
+
def build_memo_schema(memo, route, value, response_model, options)
|
271
|
+
if memo[value[:code]][:schema] && value[:as]
|
272
|
+
memo[value[:code]][:schema][:properties].merge!(build_reference(route, value, response_model, options))
|
273
|
+
|
274
|
+
if value[:required]
|
275
|
+
memo[value[:code]][:schema][:required] ||= []
|
276
|
+
memo[value[:code]][:schema][:required] << value[:as].to_s
|
277
|
+
end
|
278
|
+
|
279
|
+
elsif value[:as]
|
280
|
+
memo[value[:code]][:schema] = {
|
281
|
+
type: :object,
|
282
|
+
properties: build_reference(route, value, response_model, options)
|
283
|
+
}
|
284
|
+
memo[value[:code]][:schema][:required] = [value[:as].to_s] if value[:required]
|
285
|
+
else
|
286
|
+
memo[value[:code]][:schema] = build_reference(route, value, response_model, options)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
264
290
|
def build_reference(route, value, response_model, settings)
|
265
291
|
# TODO: proof that the definition exist, if model isn't specified
|
266
|
-
reference =
|
292
|
+
reference = if value.key?(:as)
|
293
|
+
{ value[:as] => build_reference_hash(response_model) }
|
294
|
+
else
|
295
|
+
build_reference_hash(response_model)
|
296
|
+
end
|
267
297
|
return reference unless value[:code] < 300
|
268
298
|
|
269
|
-
|
299
|
+
if value.key?(:as) && value.key?(:is_array)
|
300
|
+
reference[value[:as]] = build_reference_array(reference[value[:as]])
|
301
|
+
elsif route.options[:is_array]
|
302
|
+
reference = build_reference_array(reference)
|
303
|
+
end
|
304
|
+
|
270
305
|
build_root(route, reference, response_model, settings)
|
271
306
|
end
|
272
307
|
|
308
|
+
def build_reference_hash(response_model)
|
309
|
+
{ '$ref' => "#/definitions/#{response_model}" }
|
310
|
+
end
|
311
|
+
|
312
|
+
def build_reference_array(reference)
|
313
|
+
{ type: 'array', items: reference }
|
314
|
+
end
|
315
|
+
|
273
316
|
def build_root(route, reference, response_model, settings)
|
274
317
|
default_root = response_model.underscore
|
275
318
|
default_root = default_root.pluralize if route.options[:is_array]
|
@@ -286,23 +329,20 @@ module Grape
|
|
286
329
|
end
|
287
330
|
|
288
331
|
def file_response?(value)
|
289
|
-
value.to_s.casecmp('file').zero?
|
332
|
+
value.to_s.casecmp('file').zero?
|
290
333
|
end
|
291
334
|
|
292
335
|
def build_file_response(memo)
|
293
336
|
memo['schema'] = { type: 'file' }
|
294
337
|
end
|
295
338
|
|
296
|
-
def
|
297
|
-
declared_params = route.settings[:declared_params] if route.settings[:declared_params].present?
|
339
|
+
def build_request_params(route, settings)
|
298
340
|
required = merge_params(route)
|
299
341
|
required = GrapeSwagger::DocMethods::Headers.parse(route) + required unless route.headers.nil?
|
300
342
|
|
301
343
|
default_type(required)
|
302
344
|
|
303
|
-
request_params =
|
304
|
-
GrapeSwagger::Endpoint::ParamsParser.parse_request_params(required, settings, self)
|
305
|
-
end || {}
|
345
|
+
request_params = GrapeSwagger::Endpoint::ParamsParser.parse_request_params(required, settings, self)
|
306
346
|
|
307
347
|
request_params.empty? ? required : request_params
|
308
348
|
end
|
@@ -340,12 +380,10 @@ module Grape
|
|
340
380
|
parser = GrapeSwagger.model_parsers.find(model)
|
341
381
|
raise GrapeSwagger::Errors::UnregisteredParser, "No parser registered for #{model_name}." unless parser
|
342
382
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
end
|
348
|
-
@definitions[model_name] = GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
|
383
|
+
parsed_response = parser.new(model, self).call
|
384
|
+
|
385
|
+
@definitions[model_name] =
|
386
|
+
GrapeSwagger::DocMethods::BuildModelDefinition.parse_params_from_model(parsed_response, model, model_name)
|
349
387
|
|
350
388
|
model_name
|
351
389
|
end
|
@@ -362,6 +400,16 @@ module Grape
|
|
362
400
|
options[:token_owner] ? route_hidden.call(send(options[:token_owner].to_sym)) : route_hidden.call
|
363
401
|
end
|
364
402
|
|
403
|
+
def hidden_parameter?(value)
|
404
|
+
return false if value[:required]
|
405
|
+
|
406
|
+
if value.dig(:documentation, :hidden).is_a?(Proc)
|
407
|
+
value.dig(:documentation, :hidden).call
|
408
|
+
else
|
409
|
+
value.dig(:documentation, :hidden)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
365
413
|
def success_code_from_entity(route, entity)
|
366
414
|
default_code = GrapeSwagger::DocMethods::StatusCodes.get[route.request_method.downcase.to_sym]
|
367
415
|
if entity.is_a?(Hash)
|
@@ -370,6 +418,9 @@ module Grape
|
|
370
418
|
default_code[:message] = entity[:message] || route.description || default_code[:message].sub('{item}', @item)
|
371
419
|
default_code[:examples] = entity[:examples] if entity[:examples]
|
372
420
|
default_code[:headers] = entity[:headers] if entity[:headers]
|
421
|
+
default_code[:as] = entity[:as] if entity[:as]
|
422
|
+
default_code[:is_array] = entity[:is_array] if entity[:is_array]
|
423
|
+
default_code[:required] = entity[:required] if entity[:required]
|
373
424
|
else
|
374
425
|
default_code = GrapeSwagger::DocMethods::StatusCodes.get[route.request_method.downcase.to_sym]
|
375
426
|
default_code[:model] = entity if entity
|