Module: GrapeSwagger::DocMethods
- Defined in:
- lib/grape-swagger/doc_methods.rb
Constant Summary collapse
- PRIMITIVE_MAPPINGS =
{ 'integer' => %w(integer int32), 'long' => %w(integer int64), 'float' => %w(number float), 'double' => %w(number double), 'byte' => %w(string byte), 'date' => %w(string date), 'dateTime' => %w(string date-time) }
Instance Method Summary collapse
- #as_markdown(description) ⇒ Object
- #content_types_for(target_class) ⇒ Object
- #generate_typeref(type) ⇒ Object
- #hide_documentation_path ⇒ Object
- #is_primitive?(type) ⇒ Boolean
- #models_with_included_presenters(models) ⇒ Object
- #mount_path ⇒ Object
- #name ⇒ Object
- #parse_base_path(base_path, request) ⇒ Object
- #parse_entity_models(models) ⇒ Object
- #parse_entity_name(model) ⇒ Object
- #parse_header_params(params) ⇒ Object
- #parse_http_codes(codes, models) ⇒ Object
- #parse_info(info) ⇒ Object
- #parse_params(params, path, method) ⇒ Object
- #parse_path(path, version) ⇒ Object
- #setup(options) ⇒ Object
- #strip_heredoc(string) ⇒ Object
Instance Method Details
#as_markdown(description) ⇒ Object
17 18 19 |
# File 'lib/grape-swagger/doc_methods.rb', line 17 def as_markdown(description) description && @@markdown ? @@markdown.as_markdown(strip_heredoc(description)) : description end |
#content_types_for(target_class) ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/grape-swagger/doc_methods.rb', line 121 def content_types_for(target_class) content_types = (target_class.content_types || {}).values if content_types.empty? formats = [target_class.format, target_class.default_format].compact.uniq formats = Grape::Formatter::Base.formatters({}).keys if formats.empty? content_types = Grape::ContentTypes::CONTENT_TYPES.select { |content_type, _mime_type| formats.include? content_type }.values end content_types.uniq end |
#generate_typeref(type) ⇒ Object
275 276 277 278 279 280 281 282 |
# File 'lib/grape-swagger/doc_methods.rb', line 275 def generate_typeref(type) type_s = type.to_s.sub(/^[A-Z]/) { |f| f.downcase } if is_primitive? type_s { 'type' => type_s } else { '$ref' => parse_entity_name(type) } end end |
#hide_documentation_path ⇒ Object
312 313 314 |
# File 'lib/grape-swagger/doc_methods.rb', line 312 def hide_documentation_path @@hide_documentation_path end |
#is_primitive?(type) ⇒ Boolean
271 272 273 |
# File 'lib/grape-swagger/doc_methods.rb', line 271 def is_primitive?(type) %w(object integer long float double string byte boolean date dateTime).include? type end |
#models_with_included_presenters(models) ⇒ Object
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/grape-swagger/doc_methods.rb', line 248 def models_with_included_presenters(models) all_models = models models.each do |model| # get model references from exposures with a documentation nested_models = model.exposures.map do |_, config| if config.key?(:documentation) model = config[:using] model.respond_to?(:constantize) ? model.constantize : model end end.compact # get all nested models recursively additional_models = nested_models.map do |nested_model| models_with_included_presenters([nested_model]) end.flatten all_models += additional_models end all_models end |
#mount_path ⇒ Object
316 317 318 |
# File 'lib/grape-swagger/doc_methods.rb', line 316 def mount_path @@mount_path end |
#name ⇒ Object
13 14 15 |
# File 'lib/grape-swagger/doc_methods.rb', line 13 def name @@class_name end |
#parse_base_path(base_path, request) ⇒ Object
302 303 304 305 306 307 308 309 310 |
# File 'lib/grape-swagger/doc_methods.rb', line 302 def parse_base_path(base_path, request) if base_path.is_a?(Proc) base_path.call(request) elsif base_path.is_a?(String) URI(base_path).relative? ? URI.join(request.base_url, base_path).to_s : base_path else request.base_url end end |
#parse_entity_models(models) ⇒ Object
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/grape-swagger/doc_methods.rb', line 194 def parse_entity_models(models) result = {} models.each do |model| name = (model.instance_variable_get(:@root) || parse_entity_name(model)) properties = {} required = [] model.documentation.each do |property_name, property_info| p = property_info.dup required << property_name.to_s if p.delete(:required) type = if p[:type] p.delete(:type) else exposure = model.exposures[property_name] parse_entity_name(exposure[:using]) if exposure end if p.delete(:is_array) p[:items] = generate_typeref(type) p[:type] = 'array' else p.merge! generate_typeref(type) end # rename Grape Entity's "desc" to "description" property_description = p.delete(:desc) p[:description] = property_description if property_description # rename Grape's 'values' to 'enum' select_values = p.delete(:values) if select_values select_values = select_values.call if select_values.is_a?(Proc) p[:enum] = select_values end if PRIMITIVE_MAPPINGS.key?(p['type']) p['type'], p['format'] = PRIMITIVE_MAPPINGS[p['type']] end properties[property_name] = p end result[name] = { id: name, properties: properties } result[name].merge!(required: required) unless required.empty? end result end |
#parse_entity_name(model) ⇒ Object
183 184 185 186 187 188 189 190 191 192 |
# File 'lib/grape-swagger/doc_methods.rb', line 183 def parse_entity_name(model) if model.respond_to?(:entity_name) model.entity_name else name = model.to_s entity_parts = name.split('::') entity_parts.reject! { |p| p == 'Entity' || p == 'Entities' } entity_parts.join('::') end end |
#parse_header_params(params) ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/grape-swagger/doc_methods.rb', line 144 def parse_header_params(params) params ||= [] params.map do |param, value| data_type = 'string' description = value.is_a?(Hash) ? value[:description] : '' required = value.is_a?(Hash) ? !!value[:required] : false default_value = value.is_a?(Hash) ? value[:default] : nil param_type = 'header' parsed_params = { paramType: param_type, name: param, description: as_markdown(description), type: data_type, required: required } parsed_params.merge!(defaultValue: default_value) if default_value parsed_params end end |
#parse_http_codes(codes, models) ⇒ Object
284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/grape-swagger/doc_methods.rb', line 284 def parse_http_codes(codes, models) codes ||= {} codes.map do |k, v, m| models << m if m http_code_hash = { code: k, message: v } http_code_hash[:responseModel] = parse_entity_name(m) if m http_code_hash end end |
#parse_info(info) ⇒ Object
133 134 135 136 137 138 139 140 141 142 |
# File 'lib/grape-swagger/doc_methods.rb', line 133 def parse_info(info) { contact: info[:contact], description: as_markdown(info[:description]), license: info[:license], licenseUrl: info[:license_url], termsOfServiceUrl: info[:terms_of_service_url], title: info[:title] }.delete_if { |_, value| value.blank? } end |
#parse_params(params, path, method) ⇒ Object
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/grape-swagger/doc_methods.rb', line 21 def parse_params(params, path, method) params ||= [] parsed_array_params = parse_array_params(params) non_nested_parent_params = get_non_nested_params(parsed_array_params) non_nested_parent_params.map do |param, value| items = {} raw_data_type = value[:type] if value.is_a?(Hash) raw_data_type ||= 'string' data_type = case raw_data_type.to_s when 'Hash' 'object' when 'Rack::Multipart::UploadedFile' 'File' when 'Virtus::Attribute::Boolean' 'boolean' when 'Boolean', 'Date', 'Integer', 'String', 'Float' raw_data_type.to_s.downcase when 'BigDecimal' 'long' when 'DateTime' 'dateTime' when 'Numeric' 'double' when 'Symbol' 'string' when /^\[(?<type>.*)\]$/ items[:type] = Regexp.last_match[:type].downcase if PRIMITIVE_MAPPINGS.key?(items[:type]) items[:type], items[:format] = PRIMITIVE_MAPPINGS[items[:type]] end 'array' else @@documentation_class.parse_entity_name(raw_data_type) end additional_documentation = value.is_a?(Hash) ? value[:documentation] : nil if additional_documentation && value.is_a?(Hash) value = additional_documentation.merge(value) end description = value.is_a?(Hash) ? value[:desc] || value[:description] : '' required = value.is_a?(Hash) ? !!value[:required] : false default_value = value.is_a?(Hash) ? value[:default] : nil example = value.is_a?(Hash) ? value[:example] : nil is_array = value.is_a?(Hash) ? (value[:is_array] || false) : false values = value.is_a?(Hash) ? value[:values] : nil enum_or_range_values = parse_enum_or_range_values(values) if value.is_a?(Hash) && value.key?(:documentation) && value[:documentation].key?(:param_type) param_type = value[:documentation][:param_type] if is_array items = { '$ref' => data_type } data_type = 'array' end else param_type = case when path.include?(":#{param}") 'path' when %w(POST PUT PATCH).include?(method) if is_primitive?(data_type) 'form' else 'body' end else 'query' end end name = (value.is_a?(Hash) && value[:full_name]) || param parsed_params = { paramType: param_type, name: name, description: as_markdown(description), type: data_type, required: required, allowMultiple: is_array } if PRIMITIVE_MAPPINGS.key?(data_type) parsed_params[:type], parsed_params[:format] = PRIMITIVE_MAPPINGS[data_type] end parsed_params[:items] = items if items.present? parsed_params[:defaultValue] = example if example if default_value && example.blank? parsed_params[:defaultValue] = default_value end parsed_params.merge!(enum_or_range_values) if enum_or_range_values parsed_params end end |
#parse_path(path, version) ⇒ Object
168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/grape-swagger/doc_methods.rb', line 168 def parse_path(path, version) # adapt format to swagger format parsed_path = path.sub(/\(\..*\)$/, @@hide_format ? '' : '.{format}') # This is attempting to emulate the behavior of # Rack::Mount::Strexp. We cannot use Strexp directly because # all it does is generate regular expressions for parsing URLs. # TODO: Implement a Racc tokenizer to properly generate the # parsed path. parsed_path = parsed_path.gsub(/:([a-zA-Z_]\w*)/, '{\1}') # add the version version ? parsed_path.gsub('{version}', version) : parsed_path end |
#setup(options) ⇒ Object
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 |
# File 'lib/grape-swagger/doc_methods.rb', line 320 def setup() defaults = { target_class: nil, mount_path: '/swagger_doc', base_path: nil, api_version: '0.1', markdown: nil, hide_documentation_path: false, hide_format: false, format: nil, models: [], info: {}, authorizations: nil, root_base_path: true, api_documentation: { desc: 'Swagger compatible API description' }, specific_api_documentation: { desc: 'Swagger compatible API description for specific API' } } = defaults.merge() target_class = [:target_class] @@mount_path = [:mount_path] @@class_name = [:class_name] || [:mount_path].gsub('/', '') @@markdown = [:markdown] ? GrapeSwagger::Markdown.new([:markdown]) : nil @@hide_format = [:hide_format] api_version = [:api_version] = [:authorizations] root_base_path = [:root_base_path] extra_info = [:info] api_doc = [:api_documentation].dup specific_api_doc = [:specific_api_documentation].dup @@models = [:models] || [] @@hide_documentation_path = [:hide_documentation_path] if [:format] [:format, :default_format, :default_error_formatter].each do |method| send(method, [:format]) end end @@documentation_class = self desc api_doc.delete(:desc), api_doc get @@mount_path do header['Access-Control-Allow-Origin'] = '*' header['Access-Control-Request-Method'] = '*' namespaces = target_class.combined_namespaces namespace_routes = target_class.combined_namespace_routes if @@hide_documentation_path namespace_routes.reject! { |route, _value| "/#{route}/".index(@@documentation_class.parse_path(@@mount_path, nil) << '/') == 0 } end namespace_routes_array = namespace_routes.keys.map do |local_route| next if namespace_routes[local_route].map(&:route_hidden).all? { |value| value.respond_to?(:call) ? value.call : value } url_format = '.{format}' unless @@hide_format original_namespace_name = target_class.combined_namespace_identifiers.key?(local_route) ? target_class.combined_namespace_identifiers[local_route] : local_route description = namespaces[original_namespace_name] && namespaces[original_namespace_name].[:desc] description ||= "Operations about #{original_namespace_name.pluralize}" { path: "/#{local_route}#{url_format}", description: description } end.compact output = { apiVersion: api_version, swaggerVersion: '1.2', produces: @@documentation_class.content_types_for(target_class), apis: namespace_routes_array, info: @@documentation_class.parse_info(extra_info) } output[:authorizations] = unless .nil? || .empty? output end desc specific_api_doc.delete(:desc), { params: specific_api_doc.delete(:params) || {} }.merge(specific_api_doc) params do requires :name, type: String, desc: 'Resource name of mounted API' end get "#{@@mount_path}/:name" do header['Access-Control-Allow-Origin'] = '*' header['Access-Control-Request-Method'] = '*' models = [] routes = target_class.combined_namespace_routes[params[:name]] error!('Not Found', 404) unless routes visible_ops = routes.reject do |route| route.route_hidden.respond_to?(:call) ? route.route_hidden.call : route.route_hidden end ops = visible_ops.group_by do |route| @@documentation_class.parse_path(route.route_path, api_version) end error!('Not Found', 404) unless ops.any? apis = [] ops.each do |path, op_routes| operations = op_routes.map do |route| notes = @@documentation_class.as_markdown(route.route_detail || route.route_notes) http_codes = @@documentation_class.parse_http_codes(route.route_http_codes, models) models |= @@models if @@models.present? models |= Array(route.route_entity) if route.route_entity.present? models = @@documentation_class.models_with_included_presenters(models.flatten.compact) operation = { notes: notes.to_s, summary: route.route_description || '', nickname: route.route_nickname || (route.route_method + route.route_path.gsub(/[\/:\(\)\.]/, '-')), method: route.route_method, parameters: @@documentation_class.parse_header_params(route.route_headers) + @@documentation_class.parse_params(route.route_params, route.route_path, route.route_method), type: route.route_is_array ? 'array' : 'void' } operation[:authorizations] = route. unless route..nil? || route..empty? if operation[:parameters].any? { |param| param[:type] == 'File' } operation.merge!(consumes: ['multipart/form-data']) end operation.merge!(responseMessages: http_codes) unless http_codes.empty? if route.route_entity type = @@documentation_class.parse_entity_name(Array(route.route_entity).first) if route.route_is_array operation.merge!(items: { '$ref' => type }) else operation.merge!(type: type) end end operation[:nickname] = route.route_nickname if route.route_nickname operation end.compact apis << { path: path, operations: operations } end # use custom resource naming if available if target_class.combined_namespace_identifiers.key? params[:name] resource_path = target_class.combined_namespace_identifiers[params[:name]] else resource_path = params[:name] end api_description = { apiVersion: api_version, swaggerVersion: '1.2', resourcePath: "/#{resource_path}", produces: @@documentation_class.content_types_for(target_class), apis: apis } base_path = @@documentation_class.parse_base_path([:base_path], request) api_description[:basePath] = base_path if base_path && base_path.size > 0 && root_base_path != false api_description[:models] = @@documentation_class.parse_entity_models(models) unless models.empty? api_description[:authorizations] = if api_description end end |
#strip_heredoc(string) ⇒ Object
297 298 299 300 |
# File 'lib/grape-swagger/doc_methods.rb', line 297 def strip_heredoc(string) indent = string.scan(/^[ \t]*(?=\S)/).min.try(:size) || 0 string.gsub(/^[ \t]{#{indent}}/, '') end |