2017-03-08 16:32:31 +08:00
|
|
|
# frozen_string_literal: true
|
2015-02-05 06:12:48 +08:00
|
|
|
require 'grape'
|
2016-05-07 05:18:45 +08:00
|
|
|
|
2014-07-14 21:36:45 +08:00
|
|
|
require 'grape-swagger/version'
|
2015-09-25 01:08:24 +08:00
|
|
|
require 'grape-swagger/endpoint'
|
2014-08-13 17:43:48 +08:00
|
|
|
require 'grape-swagger/errors'
|
2016-03-16 08:18:07 +08:00
|
|
|
|
2015-04-30 02:21:14 +08:00
|
|
|
require 'grape-swagger/doc_methods'
|
2016-05-07 09:12:26 +08:00
|
|
|
require 'grape-swagger/model_parsers'
|
2016-03-16 08:18:07 +08:00
|
|
|
|
2016-05-07 09:12:26 +08:00
|
|
|
module GrapeSwagger
|
|
|
|
class << self
|
|
|
|
def model_parsers
|
|
|
|
@model_parsers ||= GrapeSwagger::ModelParsers.new
|
|
|
|
end
|
|
|
|
end
|
2016-09-08 23:32:09 +08:00
|
|
|
autoload :Rake, 'grape-swagger/rake/oapi_tasks'
|
2016-05-07 09:12:26 +08:00
|
|
|
end
|
|
|
|
|
2012-07-19 16:37:46 +08:00
|
|
|
module Grape
|
|
|
|
class API
|
|
|
|
class << self
|
2015-04-29 03:54:23 +08:00
|
|
|
attr_accessor :combined_routes, :combined_namespaces, :combined_namespace_routes, :combined_namespace_identifiers
|
2012-07-19 16:37:46 +08:00
|
|
|
|
2014-07-14 21:59:11 +08:00
|
|
|
def add_swagger_documentation(options = {})
|
2012-07-19 16:37:46 +08:00
|
|
|
documentation_class = create_documentation_class
|
|
|
|
|
2016-01-13 00:58:37 +08:00
|
|
|
version_for(options)
|
2015-04-29 03:54:23 +08:00
|
|
|
options = { target_class: self }.merge(options)
|
|
|
|
@target_class = options[:target_class]
|
2016-09-08 22:58:04 +08:00
|
|
|
auth_wrapper = options[:endpoint_auth_wrapper]
|
|
|
|
|
|
|
|
if auth_wrapper && auth_wrapper.method_defined?(:before) && !middleware.flatten.include?(auth_wrapper)
|
|
|
|
use auth_wrapper
|
|
|
|
end
|
2015-04-29 03:54:23 +08:00
|
|
|
|
|
|
|
documentation_class.setup(options)
|
2012-07-19 16:37:46 +08:00
|
|
|
mount(documentation_class)
|
2013-06-18 21:56:15 +08:00
|
|
|
|
2015-04-29 03:54:23 +08:00
|
|
|
@target_class.combined_routes = {}
|
2015-08-27 04:54:03 +08:00
|
|
|
combine_routes(@target_class, documentation_class)
|
2014-07-14 23:26:27 +08:00
|
|
|
|
2015-04-29 03:54:23 +08:00
|
|
|
@target_class.combined_namespaces = {}
|
|
|
|
combine_namespaces(@target_class)
|
2015-02-26 00:42:32 +08:00
|
|
|
|
2015-04-29 03:54:23 +08:00
|
|
|
@target_class.combined_namespace_routes = {}
|
|
|
|
@target_class.combined_namespace_identifiers = {}
|
|
|
|
combine_namespace_routes(@target_class.combined_namespaces)
|
2015-02-26 00:42:32 +08:00
|
|
|
|
2015-04-29 03:54:23 +08:00
|
|
|
exclusive_route_keys = @target_class.combined_routes.keys - @target_class.combined_namespaces.keys
|
2016-10-31 19:14:08 +08:00
|
|
|
exclusive_route_keys.each do |key|
|
|
|
|
@target_class.combined_namespace_routes[key] = @target_class.combined_routes[key]
|
|
|
|
end
|
2014-12-01 21:59:20 +08:00
|
|
|
documentation_class
|
2014-08-03 08:01:14 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2016-01-13 00:58:37 +08:00
|
|
|
def version_for(options)
|
|
|
|
options[:version] = version if version
|
2015-10-23 00:50:05 +08:00
|
|
|
end
|
|
|
|
|
2015-08-27 04:54:03 +08:00
|
|
|
def combine_routes(app, doc_klass)
|
|
|
|
app.routes.each do |route|
|
2016-05-07 05:18:45 +08:00
|
|
|
route_path = route.path
|
|
|
|
route_match = route_path.split(/^.*?#{route.prefix.to_s}/).last
|
2015-08-27 04:54:03 +08:00
|
|
|
next unless route_match
|
|
|
|
route_match = route_match.match('\/([\w|-]*?)[\.\/\(]') || route_match.match('\/([\w|-]*)$')
|
|
|
|
next unless route_match
|
|
|
|
resource = route_match.captures.first
|
|
|
|
next if resource.empty?
|
|
|
|
@target_class.combined_routes[resource] ||= []
|
2016-05-07 05:18:45 +08:00
|
|
|
next if doc_klass.hide_documentation_path && route.path.match(/#{doc_klass.mount_path}($|\/|\(\.)/)
|
2015-08-27 04:54:03 +08:00
|
|
|
@target_class.combined_routes[resource] << route
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-08-03 08:01:14 +08:00
|
|
|
def combine_namespaces(app)
|
|
|
|
app.endpoints.each do |endpoint|
|
2017-01-16 06:46:22 +08:00
|
|
|
ns = endpoint.namespace_stackable(:namespace).last
|
|
|
|
|
2015-02-26 00:42:32 +08:00
|
|
|
# use the full namespace here (not the latest level only)
|
|
|
|
# and strip leading slash
|
2016-09-22 17:31:13 +08:00
|
|
|
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
|
2014-08-03 08:01:14 +08:00
|
|
|
|
|
|
|
combine_namespaces(endpoint.options[:app]) if endpoint.options[:app]
|
2014-07-14 23:26:27 +08:00
|
|
|
end
|
2012-07-19 16:37:46 +08:00
|
|
|
end
|
|
|
|
|
2015-02-26 00:42:32 +08:00
|
|
|
def combine_namespace_routes(namespaces)
|
|
|
|
# iterate over each single namespace
|
2017-01-16 06:46:22 +08:00
|
|
|
namespaces.each do |name, _|
|
2015-02-26 00:42:32 +08:00
|
|
|
# get the parent route for the namespace
|
2016-11-16 21:41:47 +08:00
|
|
|
parent_route_name = extract_parent_route(name)
|
2015-04-29 03:54:23 +08:00
|
|
|
parent_route = @target_class.combined_routes[parent_route_name]
|
2015-02-26 00:42:32 +08:00
|
|
|
# fetch all routes that are within the current namespace
|
2015-08-27 06:45:38 +08:00
|
|
|
namespace_routes = parent_route.reject do |route|
|
|
|
|
!route_path_start_with?(route, name) || !route_instance_variable_equals?(route, name)
|
|
|
|
end
|
2015-02-26 00:42:32 +08:00
|
|
|
|
2017-01-16 06:46:22 +08:00
|
|
|
# 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.reject { |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)
|
2015-02-26 00:42:32 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-11-16 21:41:47 +08:00
|
|
|
def extract_parent_route(name)
|
2017-01-16 06:46:22 +08:00
|
|
|
name.match(%r{^/?([^/]*).*$})[1]
|
2015-08-27 06:45:38 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def route_instance_variable(route)
|
|
|
|
route.instance_variable_get(:@options)[:namespace]
|
|
|
|
end
|
|
|
|
|
|
|
|
def route_instance_variable_equals?(route, name)
|
|
|
|
route_instance_variable(route) == "/#{name}" ||
|
|
|
|
route_instance_variable(route) == "/:version/#{name}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def route_path_start_with?(route, name)
|
2016-05-07 05:18:45 +08:00
|
|
|
route_prefix = route.prefix ? "/#{route.prefix}/#{name}" : "/#{name}"
|
|
|
|
route_versioned_prefix = route.prefix ? "/#{route.prefix}/:version/#{name}" : "/:version/#{name}"
|
2015-08-27 06:45:38 +08:00
|
|
|
|
2016-05-07 05:18:45 +08:00
|
|
|
route.path.start_with?(route_prefix, route_versioned_prefix)
|
2015-08-27 06:45:38 +08:00
|
|
|
end
|
|
|
|
|
2012-07-19 22:15:14 +08:00
|
|
|
def create_documentation_class
|
2012-07-19 16:37:46 +08:00
|
|
|
Class.new(Grape::API) do
|
2015-04-30 02:21:14 +08:00
|
|
|
extend GrapeSwagger::DocMethods
|
2012-07-19 16:37:46 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|