Skip to content

Adds support for grape >= 1.2 #717

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 29, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
### 0.31.2 (Next)
### 0.32.0 (Next)

#### Features

* Your contribution here.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please re-add it again, same for line 9


### Fixes

* Your contribution here.
* [#717](https://github.com/ruby-grape/grape-swagger/pull/717): Adds support for grape >= 1.2 - [@myxoh](https://github.com/myxoh).

### 0.31.1 (October 23, 2018)

Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ group :development, :test do
gem 'rake'
gem 'rdoc'
gem 'rspec', '~> 3.0'
gem 'rubocop', '~> 0.59', require: false
gem 'rubocop', '0.60', require: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't lock it, minor rubocop updates are much easier to handle then big ones

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dblock Could you guys agree on a decision? I'm happy either way as I see both views as valid, but I don't want to step over either of you

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets take it to #719

end

group :test do
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ grape-swagger | swagger spec | grape | grape-entity | represen
0.10.5 | 1.2 | >= 0.10.0 ... <= 0.14.0 | < 0.5.0 | n/a |
0.11.0 | 1.2 | >= 0.16.2 | < 0.5.0 | n/a |
0.25.2 | 2.0 | >= 0.14.0 ... <= 0.18.0 | <= 0.6.0 | >= 2.4.1 |
0.26.0 | 2.0 | >= 0.16.2 | <= 0.6.1 | >= 2.4.1 |
0.27.0 | 2.0 | >= 0.16.2 | >= 0.5.0 | >= 2.4.1 |
0.26.0 | 2.0 | >= 0.16.2 <= 1.1.0 | <= 0.6.1 | >= 2.4.1 |
0.27.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | >= 0.5.0 | >= 2.4.1 |
0.32.0 | 2.0 | >= 0.16.2 | >= 0.5.0 | >= 2.4.1 |


## Swagger-Spec <a name="swagger-spec"></a>
Expand Down
238 changes: 122 additions & 116 deletions lib/grape-swagger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

require 'grape'

require 'grape-swagger/instance'

require 'grape-swagger/version'
require 'grape-swagger/endpoint'
require 'grape-swagger/errors'
Expand All @@ -18,148 +20,152 @@ def model_parsers
autoload :Rake, 'grape-swagger/rake/oapi_tasks'
end

module Grape
class API
class << self
attr_accessor :combined_routes, :combined_namespaces, :combined_namespace_routes, :combined_namespace_identifiers

def add_swagger_documentation(options = {})
documentation_class = create_documentation_class
module SwaggerRouting
private

version_for(options)
options = { target_class: self }.merge(options)
@target_class = options[:target_class]
auth_wrapper = options[:endpoint_auth_wrapper] || Class.new
def combine_routes(app, doc_klass)
app.routes.each do |route|
route_path = route.path
route_match = route_path.split(/^.*?#{route.prefix.to_s}/).last
next unless route_match

use auth_wrapper if auth_wrapper.method_defined?(:before) && !middleware.flatten.include?(auth_wrapper)
route_match = route_match.match('\/([\w|-]*?)[\.\/\(]') || route_match.match('\/([\w|-]*)$')
next unless route_match

documentation_class.setup(options)
mount(documentation_class)
resource = route_match.captures.first
resource = '/' if resource.empty?
@target_class.combined_routes[resource] ||= []
next if doc_klass.hide_documentation_path && route.path.match(/#{doc_klass.mount_path}($|\/|\(\.)/)

@target_class.combined_routes = {}
combine_routes(@target_class, documentation_class)
@target_class.combined_routes[resource].unshift route
end
end

@target_class.combined_namespaces = {}
combine_namespaces(@target_class)
def determine_namespaced_routes(name, parent_route)
if parent_route.nil?
@target_class.combined_routes.values.flatten
else
parent_route.reject do |route|
!route_path_start_with?(route, name) || !route_instance_variable_equals?(route, name)
end
end
end

@target_class.combined_namespace_routes = {}
@target_class.combined_namespace_identifiers = {}
combine_namespace_routes(@target_class.combined_namespaces)
def combine_namespace_routes(namespaces)
# iterate over each single namespace
namespaces.each_key do |name, _|
# get the parent route for the namespace
parent_route_name = extract_parent_route(name)
parent_route = @target_class.combined_routes[parent_route_name]
# fetch all routes that are within the current namespace
namespace_routes = determine_namespaced_routes(name, parent_route)

# default case when not explicitly specified or nested == true
standalone_namespaces = namespaces.reject do |_, ns|
!ns.options.key?(:swagger) ||
!ns.options[:swagger].key?(:nested) ||
ns.options[:swagger][:nested] != false
end

exclusive_route_keys = @target_class.combined_routes.keys - @target_class.combined_namespaces.keys
exclusive_route_keys.each do |key|
@target_class.combined_namespace_routes[key] = @target_class.combined_routes[key]
end
documentation_class
parent_standalone_namespaces = standalone_namespaces.select { |ns_name, _| name.start_with?(ns_name) }
# add only to the main route
# if the namespace is not within any other namespace appearing as standalone resource
# rubocop:disable Style/Next
if parent_standalone_namespaces.empty?
# default option, append namespace methods to parent route
parent_route = @target_class.combined_namespace_routes.key?(parent_route_name)
@target_class.combined_namespace_routes[parent_route_name] = [] unless parent_route
@target_class.combined_namespace_routes[parent_route_name].push(*namespace_routes)
end
# rubocop:enable Style/Next
end
end

private
def extract_parent_route(name)
route_name = name.match(%r{^/?([^/]*).*$})[1]
return route_name unless route_name.include? ':'

def version_for(options)
options[:version] = version if version
end
matches = name.match(/\/[a-z]+/)
matches.nil? ? route_name : matches[0].delete('/')
end

def combine_routes(app, doc_klass)
app.routes.each do |route|
route_path = route.path
route_match = route_path.split(/^.*?#{route.prefix.to_s}/).last
next unless route_match
def route_instance_variable(route)
route.instance_variable_get(:@options)[:namespace]
end

route_match = route_match.match('\/([\w|-]*?)[\.\/\(]') || route_match.match('\/([\w|-]*)$')
next unless route_match
def route_instance_variable_equals?(route, name)
route_instance_variable(route) == "/#{name}" ||
route_instance_variable(route) == "/:version/#{name}"
end

resource = route_match.captures.first
resource = '/' if resource.empty?
@target_class.combined_routes[resource] ||= []
next if doc_klass.hide_documentation_path && route.path.match(/#{doc_klass.mount_path}($|\/|\(\.)/)
def route_path_start_with?(route, name)
route_prefix = route.prefix ? "/#{route.prefix}/#{name}" : "/#{name}"
route_versioned_prefix = route.prefix ? "/#{route.prefix}/:version/#{name}" : "/:version/#{name}"

@target_class.combined_routes[resource].unshift route
end
end
route.path.start_with?(route_prefix, route_versioned_prefix)
end
end

def combine_namespaces(app)
app.endpoints.each do |endpoint|
ns = endpoint.namespace_stackable(:namespace).last
module SwaggerDocumentationAdder
attr_accessor :combined_namespaces, :combined_namespace_identifiers
attr_accessor :combined_routes, :combined_namespace_routes
include SwaggerRouting

# use the full namespace here (not the latest level only)
# and strip leading slash
mount_path = (endpoint.namespace_stackable(:mount_path) || []).join('/')
full_namespace = (mount_path + endpoint.namespace).sub(/\/{2,}/, '/').sub(/^\//, '')
@target_class.combined_namespaces[full_namespace] = ns if ns
def add_swagger_documentation(options = {})
documentation_class = create_documentation_class

combine_namespaces(endpoint.options[:app]) if endpoint.options[:app]
end
end
version_for(options)
options = { target_class: self }.merge(options)
@target_class = options[:target_class]
auth_wrapper = options[:endpoint_auth_wrapper] || Class.new

def determine_namespaced_routes(name, parent_route)
if parent_route.nil?
@target_class.combined_routes.values.flatten
else
parent_route.reject do |route|
!route_path_start_with?(route, name) || !route_instance_variable_equals?(route, name)
end
end
end
use auth_wrapper if auth_wrapper.method_defined?(:before) && !middleware.flatten.include?(auth_wrapper)

def combine_namespace_routes(namespaces)
# iterate over each single namespace
namespaces.each_key do |name, _|
# get the parent route for the namespace
parent_route_name = extract_parent_route(name)
parent_route = @target_class.combined_routes[parent_route_name]
# fetch all routes that are within the current namespace
namespace_routes = determine_namespaced_routes(name, parent_route)

# default case when not explicitly specified or nested == true
standalone_namespaces = namespaces.reject do |_, ns|
!ns.options.key?(:swagger) ||
!ns.options[:swagger].key?(:nested) ||
ns.options[:swagger][:nested] != false
end

parent_standalone_namespaces = standalone_namespaces.select { |ns_name, _| name.start_with?(ns_name) }
# add only to the main route
# if the namespace is not within any other namespace appearing as standalone resource
# rubocop:disable Style/Next
if parent_standalone_namespaces.empty?
# default option, append namespace methods to parent route
parent_route = @target_class.combined_namespace_routes.key?(parent_route_name)
@target_class.combined_namespace_routes[parent_route_name] = [] unless parent_route
@target_class.combined_namespace_routes[parent_route_name].push(*namespace_routes)
end
# rubocop:enable Style/Next
end
end
documentation_class.setup(options)
mount(documentation_class)

def extract_parent_route(name)
route_name = name.match(%r{^/?([^/]*).*$})[1]
return route_name unless route_name.include? ':'
@target_class.combined_routes = {}
combine_routes(@target_class, documentation_class)

matches = name.match(/\/[a-z]+/)
matches.nil? ? route_name : matches[0].delete('/')
end
@target_class.combined_namespaces = {}
combine_namespaces(@target_class)

def route_instance_variable(route)
route.instance_variable_get(:@options)[:namespace]
end
@target_class.combined_namespace_routes = {}
@target_class.combined_namespace_identifiers = {}
combine_namespace_routes(@target_class.combined_namespaces)

def route_instance_variable_equals?(route, name)
route_instance_variable(route) == "/#{name}" ||
route_instance_variable(route) == "/:version/#{name}"
end
exclusive_route_keys = @target_class.combined_routes.keys - @target_class.combined_namespaces.keys
exclusive_route_keys.each do |key|
@target_class.combined_namespace_routes[key] = @target_class.combined_routes[key]
end
documentation_class
end

def route_path_start_with?(route, name)
route_prefix = route.prefix ? "/#{route.prefix}/#{name}" : "/#{name}"
route_versioned_prefix = route.prefix ? "/#{route.prefix}/:version/#{name}" : "/:version/#{name}"
private

route.path.start_with?(route_prefix, route_versioned_prefix)
end
def version_for(options)
options[:version] = version if version
end

def create_documentation_class
Class.new(Grape::API) do
extend GrapeSwagger::DocMethods
end
end
def combine_namespaces(app)
app.endpoints.each do |endpoint|
ns = endpoint.namespace_stackable(:namespace).last

# use the full namespace here (not the latest level only)
# and strip leading slash
mount_path = (endpoint.namespace_stackable(:mount_path) || []).join('/')
full_namespace = (mount_path + endpoint.namespace).sub(/\/{2,}/, '/').sub(/^\//, '')
@target_class.combined_namespaces[full_namespace] = ns if ns

combine_namespaces(endpoint.options[:app]) if endpoint.options[:app]
end
end

def create_documentation_class
Class.new(GrapeInstance) do
extend GrapeSwagger::DocMethods
end
end
end

GrapeInstance.extend(SwaggerDocumentationAdder)
2 changes: 1 addition & 1 deletion lib/grape-swagger/doc_methods/parse_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def call(param, settings, path, route, definitions)

# required properties
@parsed_param = {
in: param_type(value_type),
in: param_type(value_type),
name: settings[:full_name] || param
}

Expand Down
28 changes: 14 additions & 14 deletions lib/grape-swagger/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ def content_types_for(target_class)
# required keys for SwaggerObject
def swagger_object(target_class, request, options)
object = {
info: info_object(options[:info].merge(version: options[:doc_version])),
swagger: '2.0',
produces: content_types_for(target_class),
authorizations: options[:authorizations],
info: info_object(options[:info].merge(version: options[:doc_version])),
swagger: '2.0',
produces: content_types_for(target_class),
authorizations: options[:authorizations],
securityDefinitions: options[:security_definitions],
security: options[:security],
host: GrapeSwagger::DocMethods::OptionalObject.build(:host, options, request),
basePath: GrapeSwagger::DocMethods::OptionalObject.build(:base_path, options, request),
schemes: options[:schemes].is_a?(String) ? [options[:schemes]] : options[:schemes]
security: options[:security],
host: GrapeSwagger::DocMethods::OptionalObject.build(:host, options, request),
basePath: GrapeSwagger::DocMethods::OptionalObject.build(:base_path, options, request),
schemes: options[:schemes].is_a?(String) ? [options[:schemes]] : options[:schemes]
}

GrapeSwagger::DocMethods::Extensions.add_extensions_to_root(options, object)
Expand All @@ -43,12 +43,12 @@ def swagger_object(target_class, request, options)
# building info object
def info_object(infos)
result = {
title: infos[:title] || 'API title',
description: infos[:description],
title: infos[:title] || 'API title',
description: infos[:description],
termsOfServiceUrl: infos[:terms_of_service_url],
contact: contact_object(infos),
license: license_object(infos),
version: infos[:version]
contact: contact_object(infos),
license: license_object(infos),
version: infos[:version]
}

GrapeSwagger::DocMethods::Extensions.add_extensions_to_info(infos, result)
Expand All @@ -61,7 +61,7 @@ def info_object(infos)
def license_object(infos)
{
name: infos.delete(:license),
url: infos.delete(:license_url)
url: infos.delete(:license_url)
}.delete_if { |_, value| value.blank? }
end

Expand Down
7 changes: 7 additions & 0 deletions lib/grape-swagger/instance.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

GrapeInstance = if defined? Grape::API::Instance
Grape::API::Instance
else
Grape::API
end
2 changes: 1 addition & 1 deletion lib/grape-swagger/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module GrapeSwagger
VERSION = '0.31.2'
VERSION = '0.32.0'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert, will be done on next release

end
2 changes: 1 addition & 1 deletion spec/lib/move_params_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@
let(:definition) do
{
type: 'array',
items: {
items: {
type: 'object',
properties: {
description: 'Test description'
Expand Down
Loading