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 all 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
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,3 @@ matrix:
- rvm: 2.3.7
- rvm: ruby-head
- rvm: jruby-head
- env: GRAPE_VERSION=HEAD
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
#### Features

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

### Fixes
#### Fixes

* Your contribution here.

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)
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
Loading