grape_ape_rails 0.5.0

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