grape_ape_rails 0.5.0

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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +29 -0
  9. data/Rakefile +1 -0
  10. data/grape_ape_rails.gemspec +46 -0
  11. data/lib/generators/setup_generator.rb +59 -0
  12. data/lib/generators/templates/initializer.rb +22 -0
  13. data/lib/grape_ape_rails/api.rb +88 -0
  14. data/lib/grape_ape_rails/base.rb +27 -0
  15. data/lib/grape_ape_rails/handlers/formatters.rb +134 -0
  16. data/lib/grape_ape_rails/handlers/header_versioning.rb +27 -0
  17. data/lib/grape_ape_rails/handlers/locale.rb +24 -0
  18. data/lib/grape_ape_rails/handlers/rails_logging.rb +33 -0
  19. data/lib/grape_ape_rails/handlers/responses.rb +32 -0
  20. data/lib/grape_ape_rails/handlers.rb +22 -0
  21. data/lib/grape_ape_rails/railtie.rb +9 -0
  22. data/lib/grape_ape_rails/version.rb +3 -0
  23. data/lib/grape_ape_rails.rb +55 -0
  24. data/lib/swagger/grape_swagger_modified.rb +470 -0
  25. data/lib/tasks/routes.rake +13 -0
  26. data/spec/dummy/.rspec +1 -0
  27. data/spec/dummy/README.rdoc +28 -0
  28. data/spec/dummy/Rakefile +15 -0
  29. data/spec/dummy/app/assets/images/.keep +0 -0
  30. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  31. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  32. data/spec/dummy/app/controllers/api/base.rb +18 -0
  33. data/spec/dummy/app/controllers/api/v1/monkeys.rb +17 -0
  34. data/spec/dummy/app/controllers/api/v1/widgets.rb +57 -0
  35. data/spec/dummy/app/controllers/api/v2/widgets.rb +18 -0
  36. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  37. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  38. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  39. data/spec/dummy/app/mailers/.keep +0 -0
  40. data/spec/dummy/app/models/.keep +0 -0
  41. data/spec/dummy/app/models/concerns/.keep +0 -0
  42. data/spec/dummy/app/models/monkey.rb +3 -0
  43. data/spec/dummy/app/models/monkey_serializer.rb +7 -0
  44. data/spec/dummy/app/models/widget.rb +6 -0
  45. data/spec/dummy/app/views/api/v1/base_widget.rabl +1 -0
  46. data/spec/dummy/app/views/api/v1/widget.rabl +2 -0
  47. data/spec/dummy/app/views/api/v1/widgets.rabl +2 -0
  48. data/spec/dummy/app/views/api/v2/base_widget.rabl +2 -0
  49. data/spec/dummy/app/views/api/v2/widget.rabl +2 -0
  50. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  51. data/spec/dummy/bin/bundle +3 -0
  52. data/spec/dummy/bin/rails +4 -0
  53. data/spec/dummy/bin/rake +4 -0
  54. data/spec/dummy/config/application.rb +28 -0
  55. data/spec/dummy/config/boot.rb +5 -0
  56. data/spec/dummy/config/database.yml +25 -0
  57. data/spec/dummy/config/environment.rb +5 -0
  58. data/spec/dummy/config/environments/development.rb +29 -0
  59. data/spec/dummy/config/environments/production.rb +80 -0
  60. data/spec/dummy/config/environments/test.rb +36 -0
  61. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  62. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  63. data/spec/dummy/config/initializers/grape_ape_rails.rb +23 -0
  64. data/spec/dummy/config/initializers/inflections.rb +16 -0
  65. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  66. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  67. data/spec/dummy/config/initializers/session_store.rb +3 -0
  68. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  69. data/spec/dummy/config/locales/en.yml +23 -0
  70. data/spec/dummy/config/routes.rb +5 -0
  71. data/spec/dummy/config.ru +4 -0
  72. data/spec/dummy/db/development.sqlite3 +0 -0
  73. data/spec/dummy/db/migrate/20140714154355_create_widgets.rb +10 -0
  74. data/spec/dummy/db/migrate/20140804173544_create_monkeys.rb +9 -0
  75. data/spec/dummy/db/schema.rb +30 -0
  76. data/spec/dummy/db/test.sqlite3 +0 -0
  77. data/spec/dummy/lib/assets/.keep +0 -0
  78. data/spec/dummy/public/404.html +58 -0
  79. data/spec/dummy/public/422.html +58 -0
  80. data/spec/dummy/public/500.html +57 -0
  81. data/spec/dummy/public/favicon.ico +0 -0
  82. data/spec/requests/v1/monkeys_spec.rb +22 -0
  83. data/spec/requests/v1/widgets_spec.rb +125 -0
  84. data/spec/requests/v2/widgets_spec.rb +41 -0
  85. data/spec/spec_helper.rb +102 -0
  86. metadata +524 -0
@@ -0,0 +1,27 @@
1
+ module GrapeApeRails
2
+ module Handlers
3
+ module HeaderVersioning
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ gar_resource = self.name.split('::').last.underscore
8
+ gar_version = self.name.split('::')[-2].underscore.gsub('_','.')
9
+ cascades = GrapeApeRails::API.api_version_cascades_map
10
+ gar_version = cascades[gar_version] if cascades[gar_version].present?
11
+ gar_appname = GrapeApeRails.configuration.app_name
12
+ gar_organization = GrapeApeRails.configuration.organization_name
13
+ version gar_version, using: :header, vendor: "#{gar_organization}.#{gar_appname}", strict: true
14
+
15
+ before do
16
+ req = Rack::Request.new(env)
17
+ api_key = version ? GrapeApeRails::API.api_keys_map[version] : nil
18
+ if api_key.nil?
19
+ msg = "Cannot determine API version from header info."
20
+ error!({ code: "UNAUTHORIZED", message: msg}, 401)
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ module GrapeApeRails
2
+ module Handlers
3
+ module Locale
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ I18n.config.enforce_available_locales = true
9
+ I18n.config.load_path += Dir['./config/locales/*.yml']
10
+ I18n.config.available_locales = GrapeApeRails.configuration.available_locales
11
+ I18n.config.default_locale = :en
12
+
13
+ before do
14
+ # set the locale based on Accept-Language or params[:locale]
15
+ req = Rack::Request.new(env)
16
+ params_locale = (req.params['locale'] && I18n.available_locales.include?(req.params['locale'].downcase.to_sym)) ? req.params['locale'] : nil
17
+ I18n.locale = env.http_accept_language.compatible_language_from(I18n.available_locales) || params_locale || :en
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,33 @@
1
+ module GrapeApeRails
2
+ module Handlers
3
+ class RailsLogging
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ api_version = env['rack.routing_args'][:route_info].route_version rescue nil
10
+ payload = {
11
+ remote_addr: env['REMOTE_ADDR'],
12
+ request_method: env['REQUEST_METHOD'],
13
+ request_path: env['PATH_INFO'],
14
+ request_query: env['QUERY_STRING'],
15
+ api_version: api_version
16
+ }
17
+ req = Rack::Request.new(env)
18
+ payload[:params] = req.params
19
+ ActiveSupport::Notifications.instrument "grape.request", payload do
20
+ @app.call(env).tap do |response|
21
+ status, headers, body = *response
22
+ payload[:params].merge!(env["api.endpoint"].params.to_hash)
23
+ payload[:params].delete("route_info")
24
+ payload[:params].delete("format")
25
+ payload[:response_status] = status
26
+ end
27
+ end
28
+ rescue
29
+ @app.call(env)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ module GrapeApeRails
2
+ module Handlers
3
+ module Responses
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ format :json
8
+ formatter :json, Grape::Formatter::GarRabl
9
+ error_formatter :json, Grape::Formatter::GarError
10
+
11
+ before do
12
+ # ...
13
+ end
14
+
15
+ rescue_from Grape::Exceptions::ValidationErrors do |e|
16
+ status = 422
17
+ hash_err = { error: {
18
+ code: 'RESOURCE_FAILED_VALIDATION',
19
+ message: '[RESOURCE_FAILED_VALIDATION] Resource failed validation per API parameter requirements' }
20
+ }
21
+ log_msg = hash_err[:error].clone
22
+ hash_err[:error].merge!({ data: e.errors }) if e.errors.present?
23
+ if defined?(::Rails) && Rails.respond_to?(:logger)
24
+ api_version = "[#{env['rack.routing_args'][:route_info].route_version}]" rescue nil
25
+ Rails.logger.warn "[API]#{api_version} Responding with #{status} #{log_msg}"
26
+ end
27
+ Rack::Response.new(MultiJson.dump(hash_err), status)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ module GrapeApeRails
2
+ module Handlers
3
+ module All
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # middleware for every API
8
+ use ::HttpAcceptLanguage::Middleware
9
+ use ::GrapeApeRails::Handlers::RailsLogging
10
+
11
+ # third-party Grape tools
12
+ include Grape::Kaminari
13
+ include Grape::Rails::Cache
14
+
15
+ # Gar-specific handlers
16
+ include GrapeApeRails::Handlers::HeaderVersioning
17
+ include GrapeApeRails::Handlers::Responses
18
+ include GrapeApeRails::Handlers::Locale
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ require 'grape_ape_rails'
2
+ require 'rails'
3
+ module GrapeApeRails
4
+ class Railtie < Rails::Railtie
5
+ rake_tasks do
6
+ load "tasks/routes.rake"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module GrapeApeRails
2
+ VERSION = "0.5.0"
3
+ end
@@ -0,0 +1,55 @@
1
+ require "grape_ape_rails/version"
2
+
3
+ require 'active_support'
4
+ require 'grape'
5
+ require 'swagger/grape_swagger_modified'
6
+ require 'grape_ape_rails/railtie' if defined?(Rails)
7
+ require 'grape_ape_rails/api'
8
+ require 'grape_ape_rails/base'
9
+ require 'grape_ape_rails/handlers/header_versioning'
10
+ require 'grape_ape_rails/handlers/responses'
11
+ require 'grape_ape_rails/handlers/formatters'
12
+ require 'grape_ape_rails/handlers/locale'
13
+ require 'grape_ape_rails/handlers'
14
+
15
+
16
+ module GrapeApeRails
17
+
18
+ class << self
19
+ attr_accessor :configuration
20
+ end
21
+
22
+ def self.configure
23
+ self.configuration ||= Configuration.new
24
+ yield(configuration)
25
+ raise_configuration_errors!
26
+ self
27
+ end
28
+
29
+ def self.raise_configuration_errors!
30
+ if self.configuration.api_header_security_enabled
31
+ %i[ app_name organization_name api_secret_key ].each do |setting|
32
+ if self.configuration.send(setting).nil?
33
+ raise "You must set #{setting} in a configuration block in your initializer!"
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ class Configuration
40
+ attr_accessor :app_name, :organization_name,
41
+ :api_secret_key, :api_header_security_enabled,
42
+ :available_locales, :security_envelope_debug
43
+
44
+ def initialize
45
+ @app_name = nil
46
+ @organization_name = nil
47
+ @api_secret_key = nil
48
+ @api_header_security_enabled = true
49
+ @api_security_algorithm = "sha256"
50
+ @available_locales = [ :en ]
51
+ # ...
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,470 @@
1
+ require 'kramdown'
2
+ module Grape
3
+ class API
4
+ class << self
5
+ attr_reader :combined_routes, :combined_namespaces
6
+
7
+ def add_swagger_documentation(options = {})
8
+ documentation_class = create_documentation_class
9
+ documentation_class.setup({ target_class: self }.merge(options))
10
+ mount(documentation_class)
11
+
12
+ @combined_routes = {}
13
+ routes.each do |route|
14
+ route_match = route.route_path.split(route.route_prefix).last.match('\/([\w|-]*?)[\.\/\(]')
15
+ next if route_match.nil?
16
+ resource = route_match.captures.first
17
+ next if resource.empty?
18
+ resource.downcase!
19
+ @combined_routes[resource] ||= []
20
+ next if @@hide_documentation_path && route.route_path.include?(@@mount_path)
21
+ @combined_routes[resource] << route
22
+ end
23
+
24
+ @combined_namespaces = {}
25
+ combine_namespaces(self)
26
+ end
27
+
28
+ private
29
+
30
+ def combine_namespaces(app)
31
+ app.endpoints.each do |endpoint|
32
+ ns = endpoint.settings.stack.last[:namespace]
33
+ @combined_namespaces[ns.space] = ns if ns
34
+
35
+ combine_namespaces(endpoint.options[:app]) if endpoint.options[:app]
36
+ end
37
+ end
38
+
39
+ def create_documentation_class
40
+ Class.new(Grape::API) do
41
+ class << self
42
+ def name
43
+ @@class_name
44
+ end
45
+ end
46
+
47
+ def self.setup(options)
48
+ defaults = {
49
+ target_class: nil,
50
+ mount_path: '/swagger_doc',
51
+ mount_with_version: false,
52
+ base_path: nil,
53
+ api_version: '0.1',
54
+ markdown: false,
55
+ hide_documentation_path: false,
56
+ hide_format: false,
57
+ format: nil,
58
+ models: [],
59
+ info: {},
60
+ authorizations: nil,
61
+ root_base_path: true,
62
+ api_documentation: { desc: 'Swagger compatible API description' },
63
+ specific_api_documentation: { desc: 'Swagger compatible API description for specific API' }
64
+ }
65
+
66
+ options = defaults.merge(options)
67
+
68
+ target_class = options[:target_class]
69
+ api_version = options[:api_version]
70
+ @@mount_path = options[:mount_path] + ((options[:mount_with_version] and api_version.present?) ? "/#{api_version}" : '')
71
+ @@class_name = options[:class_name] || options[:mount_path].gsub('/', '')
72
+ @@markdown = options[:markdown]
73
+ @@hide_format = options[:hide_format]
74
+ base_path = options[:base_path]
75
+ authorizations = options[:authorizations]
76
+ root_base_path = options[:root_base_path]
77
+ extra_info = options[:info]
78
+ api_doc = options[:api_documentation].dup
79
+ specific_api_doc = options[:specific_api_documentation].dup
80
+ @@models = options[:models] || []
81
+ @@hide_documentation_path = options[:hide_documentation_path]
82
+
83
+ if options[:format]
84
+ [:format, :default_format, :default_error_formatter].each do |method|
85
+ send(method, options[:format])
86
+ end
87
+ end
88
+
89
+ desc api_doc.delete(:desc), params: api_doc.delete(:params)
90
+ @last_description.merge!(api_doc)
91
+ get @@mount_path do
92
+ header['Access-Control-Allow-Origin'] = '*'
93
+ header['Access-Control-Request-Method'] = '*'
94
+
95
+ routes = target_class.combined_routes
96
+ namespaces = target_class.combined_namespaces
97
+
98
+ if @@hide_documentation_path
99
+ routes.reject! { |route, _value| "/#{route}/".index(parse_path(@@mount_path, nil) << '/') == 0 }
100
+ end
101
+
102
+ routes_array = routes.keys.map do |local_route|
103
+ next if routes[local_route].all?(&:route_hidden)
104
+
105
+ url_format = '.{format}' unless @@hide_format
106
+
107
+ description = namespaces[local_route] && namespaces[local_route].options[:desc]
108
+ description ||= "Operations about #{local_route.pluralize}"
109
+
110
+ {
111
+ path: "/#{local_route}#{url_format}",
112
+ description: description
113
+ }
114
+ end.compact
115
+
116
+ output = {
117
+ apiVersion: api_version,
118
+ swaggerVersion: '1.2',
119
+ produces: content_types_for(target_class),
120
+ apis: routes_array,
121
+ info: parse_info(extra_info)
122
+ }
123
+
124
+ output[:authorizations] = authorizations unless authorizations.nil? || authorizations.empty?
125
+
126
+ output
127
+ end
128
+
129
+ desc specific_api_doc.delete(:desc), params: {
130
+ 'name' => {
131
+ desc: 'Resource name of mounted API',
132
+ type: 'string',
133
+ required: true
134
+ }
135
+ }.merge(specific_api_doc.delete(:params) || {})
136
+ @last_description.merge!(specific_api_doc)
137
+ get "#{@@mount_path}/:name" do
138
+ header['Access-Control-Allow-Origin'] = '*'
139
+ header['Access-Control-Request-Method'] = '*'
140
+
141
+ models = []
142
+ routes = target_class.combined_routes[params[:name]]
143
+ error!('Not Found', 404) unless routes
144
+
145
+ ops = routes.reject(&:route_hidden).group_by do |route|
146
+ parse_path(route.route_path, api_version)
147
+ end
148
+
149
+ error!('Not Found', 404) unless ops.any?
150
+
151
+ apis = []
152
+
153
+ ops.each do |path, op_routes|
154
+ operations = op_routes.map do |route|
155
+ notes = as_markdown(route.route_notes)
156
+
157
+ http_codes = parse_http_codes(route.route_http_codes, models)
158
+
159
+ models << @@models if @@models.present?
160
+
161
+ models << route.route_entity if route.route_entity.present?
162
+
163
+ models = models_with_included_presenters(models.flatten.compact)
164
+
165
+ operation = {
166
+ notes: notes.to_s,
167
+ summary: route.route_description || '',
168
+ nickname: route.route_nickname || (route.route_method + route.route_path.gsub(/[\/:\(\)\.]/, '-')),
169
+ method: route.route_method,
170
+ parameters: parse_header_params(route.route_headers) + parse_params(route.route_params, route.route_path, route.route_method),
171
+ type: 'void'
172
+ }
173
+ operation[:authorizations] = route.route_authorizations unless route.route_authorizations.nil? || route.route_authorizations.empty?
174
+ if operation[:parameters].any? { | param | param[:type] == 'File' }
175
+ operation.merge!(consumes: ['multipart/form-data'])
176
+ end
177
+ operation.merge!(responseMessages: http_codes) unless http_codes.empty?
178
+
179
+ if route.route_entity
180
+ type = parse_entity_name(route.route_entity)
181
+ if route.instance_variable_get(:@options)[:is_array]
182
+ operation.merge!(
183
+ 'type' => 'array',
184
+ 'items' => generate_typeref(type)
185
+ )
186
+ else
187
+ operation.merge!('type' => type)
188
+ end
189
+ end
190
+
191
+ operation[:nickname] = route.route_nickname if route.route_nickname
192
+ operation
193
+ end.compact
194
+ apis << {
195
+ path: path,
196
+ operations: operations
197
+ }
198
+ end
199
+
200
+ api_description = {
201
+ apiVersion: api_version,
202
+ swaggerVersion: '1.2',
203
+ resourcePath: "/#{params[:name]}",
204
+ produces: content_types_for(target_class),
205
+ apis: apis
206
+ }
207
+
208
+ base_path = parse_base_path(base_path, request)
209
+ api_description[:basePath] = base_path if base_path && base_path.size > 0 && root_base_path != false
210
+ api_description[:models] = parse_entity_models(models) unless models.empty?
211
+ api_description[:authorizations] = authorizations if authorizations
212
+
213
+ api_description
214
+ end
215
+ end
216
+
217
+ helpers do
218
+
219
+ def as_markdown(description)
220
+ description && @@markdown ? Kramdown::Document.new(strip_heredoc(description), input: 'GFM', enable_coderay: false).to_html : description
221
+ end
222
+
223
+ def parse_params(params, path, method)
224
+ params ||= []
225
+ params.map do |param, value|
226
+ value[:type] = 'File' if value.is_a?(Hash) && value[:type] == 'Rack::Multipart::UploadedFile'
227
+ items = {}
228
+
229
+ raw_data_type = value.is_a?(Hash) ? (value[:type] || 'string').to_s : 'string'
230
+ data_type = case raw_data_type
231
+ when 'Boolean', 'Date', 'Integer', 'String'
232
+ raw_data_type.downcase
233
+ when 'BigDecimal'
234
+ 'long'
235
+ when 'DateTime'
236
+ 'dateTime'
237
+ when 'Numeric'
238
+ 'double'
239
+ else
240
+ parse_entity_name(raw_data_type)
241
+ end
242
+ description = value.is_a?(Hash) ? value[:desc] || value[:description] : ''
243
+ required = value.is_a?(Hash) ? !!value[:required] : false
244
+ default_value = value.is_a?(Hash) ? value[:default] : nil
245
+ is_array = value.is_a?(Hash) ? (value[:is_array] || false) : false
246
+ enum_values = value.is_a?(Hash) ? value[:values] : nil
247
+ enum_values = enum_values.call if enum_values && enum_values.is_a?(Proc)
248
+
249
+ if value.is_a?(Hash) && value.key?(:param_type)
250
+ param_type = value[:param_type]
251
+ if is_array
252
+ items = { '$ref' => data_type }
253
+ data_type = 'array'
254
+ end
255
+ else
256
+ param_type = case
257
+ when path.include?(":#{param}")
258
+ 'path'
259
+ when %w(POST PUT PATCH).include?(method)
260
+ if is_primitive?(data_type)
261
+ 'form'
262
+ else
263
+ 'body'
264
+ end
265
+ else
266
+ 'query'
267
+ end
268
+ end
269
+ name = (value.is_a?(Hash) && value[:full_name]) || param
270
+
271
+ parsed_params = {
272
+ paramType: param_type,
273
+ name: name,
274
+ description: as_markdown(description),
275
+ type: data_type,
276
+ required: required,
277
+ allowMultiple: is_array
278
+ }
279
+ parsed_params.merge!(format: 'int32') if data_type == 'integer'
280
+ parsed_params.merge!(format: 'int64') if data_type == 'long'
281
+ parsed_params.merge!(items: items) if items.present?
282
+ parsed_params.merge!(defaultValue: default_value) if default_value
283
+ parsed_params.merge!(enum: enum_values) if enum_values
284
+ parsed_params
285
+ end
286
+ end
287
+
288
+ def content_types_for(target_class)
289
+ content_types = (target_class.settings[:content_types] || {}).values
290
+
291
+ if content_types.empty?
292
+ formats = [target_class.settings[:format], target_class.settings[:default_format]].compact.uniq
293
+ formats = Grape::Formatter::Base.formatters({}).keys if formats.empty?
294
+ content_types = Grape::ContentTypes::CONTENT_TYPES.select { |content_type, _mime_type| formats.include? content_type }.values
295
+ end
296
+
297
+ content_types.uniq
298
+ end
299
+
300
+ def parse_info(info)
301
+ {
302
+ contact: info[:contact],
303
+ description: as_markdown(info[:description]),
304
+ license: info[:license],
305
+ licenseUrl: info[:license_url],
306
+ termsOfServiceUrl: info[:terms_of_service_url],
307
+ title: info[:title]
308
+ }.delete_if { |_, value| value.blank? }
309
+ end
310
+
311
+ def parse_header_params(params)
312
+ params ||= []
313
+
314
+ params.map do |param, value|
315
+ data_type = 'String'
316
+ description = value.is_a?(Hash) ? value[:description] : ''
317
+ required = value.is_a?(Hash) ? !!value[:required] : false
318
+ default_value = value.is_a?(Hash) ? value[:default] : nil
319
+ param_type = 'header'
320
+
321
+ parsed_params = {
322
+ paramType: param_type,
323
+ name: param,
324
+ description: as_markdown(description),
325
+ type: data_type,
326
+ required: required
327
+ }
328
+
329
+ parsed_params.merge!(defaultValue: default_value) if default_value
330
+
331
+ parsed_params
332
+ end
333
+ end
334
+
335
+ def parse_path(path, version)
336
+ # adapt format to swagger format
337
+ parsed_path = path.gsub('(.:format)', @@hide_format ? '' : '.{format}')
338
+ # This is attempting to emulate the behavior of
339
+ # Rack::Mount::Strexp. We cannot use Strexp directly because
340
+ # all it does is generate regular expressions for parsing URLs.
341
+ # TODO: Implement a Racc tokenizer to properly generate the
342
+ # parsed path.
343
+ parsed_path = parsed_path.gsub(/:([a-zA-Z_]\w*)/, '{\1}')
344
+ # add the version
345
+ version ? parsed_path.gsub('{version}', version) : parsed_path
346
+ end
347
+
348
+ def parse_entity_name(model)
349
+ if model.respond_to?(:entity_name)
350
+ model.entity_name
351
+ else
352
+ name = model.to_s
353
+ entity_parts = name.split('::')
354
+ entity_parts.reject! { |p| p == 'Entity' || p == 'Entities' }
355
+ entity_parts.join('::')
356
+ end
357
+ end
358
+
359
+ def parse_entity_models(models)
360
+ result = {}
361
+ models.each do |model|
362
+ name = parse_entity_name(model)
363
+ properties = {}
364
+ required = []
365
+
366
+ model.documentation.each do |property_name, property_info|
367
+ p = property_info.dup
368
+
369
+ required << property_name.to_s if p.delete(:required)
370
+
371
+ if p.delete(:is_array)
372
+ p[:items] = generate_typeref(p[:type])
373
+ p[:type] = 'array'
374
+ else
375
+ p.merge! generate_typeref(p.delete(:type))
376
+ end
377
+
378
+ # rename Grape Entity's "desc" to "description"
379
+ property_description = p.delete(:desc)
380
+ p[:description] = property_description if property_description
381
+
382
+ # rename Grape's 'values' to 'enum'
383
+ select_values = p.delete(:values)
384
+ if select_values
385
+ select_values = select_values.call if select_values.is_a?(Proc)
386
+ p[:enum] = select_values
387
+ end
388
+
389
+ properties[property_name] = p
390
+
391
+ end
392
+
393
+ result[name] = {
394
+ id: model.instance_variable_get(:@root) || name,
395
+ properties: properties
396
+ }
397
+ result[name].merge!(required: required) unless required.empty?
398
+ end
399
+
400
+ result
401
+ end
402
+
403
+ def models_with_included_presenters(models)
404
+ all_models = models
405
+
406
+ models.each do |model|
407
+ # get model references from exposures with a documentation
408
+ additional_models = model.exposures.map do |_, config|
409
+ config[:using] if config.key?(:documentation)
410
+ end.compact
411
+
412
+ all_models += additional_models
413
+ end
414
+
415
+ all_models
416
+ end
417
+
418
+ def is_primitive?(type)
419
+ %w(integer long float double string byte boolean date dateTime).include? type
420
+ end
421
+
422
+ def generate_typeref(type)
423
+ if is_primitive? type
424
+ { 'type' => type }
425
+ else
426
+ { '$ref' => type }
427
+ end
428
+ end
429
+
430
+ def parse_http_codes(codes, models)
431
+ codes ||= {}
432
+ codes.map do |k, v, m|
433
+ models << m if m
434
+ http_code_hash = {
435
+ code: k,
436
+ message: v
437
+ }
438
+ http_code_hash[:responseModel] = parse_entity_name(m) if m
439
+ http_code_hash
440
+ end
441
+ end
442
+
443
+ def try(*args, &block)
444
+ if args.empty? && block_given?
445
+ yield self
446
+ elsif respond_to?(args.first)
447
+ public_send(*args, &block)
448
+ end
449
+ end
450
+
451
+ def strip_heredoc(string)
452
+ indent = string.scan(/^[ \t]*(?=\S)/).min.try(:size) || 0
453
+ string.gsub(/^[ \t]{#{indent}}/, '')
454
+ end
455
+
456
+ def parse_base_path(base_path, request)
457
+ if base_path.is_a?(Proc)
458
+ base_path.call(request)
459
+ elsif base_path.is_a?(String)
460
+ URI(base_path).relative? ? URI.join(request.base_url, base_path).to_s : base_path
461
+ else
462
+ request.base_url
463
+ end
464
+ end
465
+ end
466
+ end
467
+ end
468
+ end
469
+ end
470
+ end