open-api 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +143 -0
- data/lib/open-api/controller.rb +9 -0
- data/lib/open-api/endpoints.rb +173 -0
- data/lib/open-api/generator.rb +153 -0
- data/lib/open-api/objects.rb +150 -0
- data/lib/open-api/tags.rb +149 -0
- data/lib/open-api/utils.rb +131 -0
- data/lib/open-api.rb +35 -0
- data/open-api.gemspec +24 -0
- metadata +55 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4058f027c10a1877a4d58919f0f919f282c443a5
|
4
|
+
data.tar.gz: 1cca862076eb32844b8dee56d2757dbd545efcd7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db59e78681bd7d0826fe61cf6623adf3675494f0e1a743f74a1edf8da095dd50f04cf5eec5ae373238006dc7ab1ce7d67451c9ae0dab4cf2d26870853985e60d
|
7
|
+
data.tar.gz: 60c02fa2366b4db2fd6412dd928e264e34e4b86a6b128ca2cfe0ac2d7f114ab6dc5a716c6fbd92865e91b8de8cfdc34db15da79bcc4fad833a14f4ba158a6fc0
|
data/README.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# open-api
|
2
|
+
|
3
|
+
**A flexible, inline [OpenAPI](https://github.com/OAI/OpenAPI-Specification) documentation solution
|
4
|
+
for your Rails-based REST API.**
|
5
|
+
|
6
|
+
[OpenAPI](https://github.com/OAI/OpenAPI-Specification) (formerly Swagger) is a popular,
|
7
|
+
JSON-based, language-agnostic standard for documenting a REST API. It's quickly becoming the
|
8
|
+
industry-leading appraoch for describing REST API's of all shapes and sizes.
|
9
|
+
|
10
|
+
However, maintaining your own lengthy, stand-alone JSON documentation alongside your Rails API
|
11
|
+
source code is tedious and error-prone process to say the least. Here's why using the open-api gem
|
12
|
+
is better:
|
13
|
+
|
14
|
+
+ The open-api gem merges documentation details you provide with API metadata supplied by the
|
15
|
+
Rails framework itself, reducing your documentation effort and helping to maintain the accuracy
|
16
|
+
of your documentation over time.
|
17
|
+
+ Your API documentation details live inline right alongside your API source code. As your API
|
18
|
+
changes, locating and updating documentation affected by those changes becomes a far easier task.
|
19
|
+
+ Metadata inheritance and intelligent merging rules miminize the need to document anything more
|
20
|
+
than once, further reducing the development and maintenance burden associated with your API
|
21
|
+
documentation.
|
22
|
+
+ Metadata that's not directly interpreted by open-api is generally passed through to the output
|
23
|
+
JSON intact. As the OpenAPI standard evolves, you won't be limited to using OpenAPI features the
|
24
|
+
gem was explicitly written to manage.
|
25
|
+
|
26
|
+
## Table of Contents
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
Put this in your Gemfile:
|
31
|
+
|
32
|
+
``` ruby
|
33
|
+
gem 'open-api'
|
34
|
+
```
|
35
|
+
## Configuration
|
36
|
+
|
37
|
+
Configuration for the open-api gem is performed using an `open_api.rb` initializer in your
|
38
|
+
`config/initializers` subdirectory. A sample initializer follows:
|
39
|
+
|
40
|
+
``` ruby
|
41
|
+
OpenApi.configure do |config|
|
42
|
+
|
43
|
+
# Default base path(s), used to scan Rails routes for API endpoints.
|
44
|
+
config.base_paths = ['/widget-api/v1']
|
45
|
+
|
46
|
+
# General information about your API.
|
47
|
+
config.info = {
|
48
|
+
title: 'Acme Widget API',
|
49
|
+
description: "Documentation of the Acme's Widget API service",
|
50
|
+
version: '1.0.0',
|
51
|
+
terms_of_service: 'https://www.acme.com/widget-api/terms_of_service',
|
52
|
+
|
53
|
+
contact: {
|
54
|
+
name: 'Acme Corporation API Team',
|
55
|
+
url: 'http://www.acme.com/widget-api',
|
56
|
+
email: 'widget-api-support@acme.com'
|
57
|
+
},
|
58
|
+
|
59
|
+
license: {
|
60
|
+
name: 'Apache 2.0',
|
61
|
+
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
# Default output file path for your generated Open API JSON document.
|
66
|
+
config.output_file_path = Rails.root.join('apidoc', 'api-docs.json')
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
For details regarding content you may include in the "info" section of your API documentation, see
|
71
|
+
the[OpenAPI specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#infoObject).
|
72
|
+
|
73
|
+
|
74
|
+
## Describing Endpoints
|
75
|
+
|
76
|
+
### Controller-Level Metadata
|
77
|
+
|
78
|
+
Controller-level metadata is Open API documentation metadata common to all endpoints for a
|
79
|
+
controller, as well as any endpoints associated with that controller's subclasses. Below is an
|
80
|
+
example of how characteristics common to all endpoints in an API might be defined in a base API
|
81
|
+
controller:
|
82
|
+
``` ruby
|
83
|
+
class BaseApiController < ActionController::Base
|
84
|
+
|
85
|
+
# Include once in your base API controller class
|
86
|
+
include OpenApi::Controller
|
87
|
+
|
88
|
+
open_api_controller \
|
89
|
+
query_string: {
|
90
|
+
access_token: {
|
91
|
+
type: :string,
|
92
|
+
description: 'OAuth 2 access token query parameter',
|
93
|
+
required: false
|
94
|
+
}
|
95
|
+
},
|
96
|
+
headers: {
|
97
|
+
'X-Access-Token' => {
|
98
|
+
type: :string,
|
99
|
+
description: 'OAuth 2 access token HTTP header',
|
100
|
+
required: false
|
101
|
+
}
|
102
|
+
},
|
103
|
+
responses: {
|
104
|
+
200 => { description: 'Successful' },
|
105
|
+
401 => { description: 'Invalid request' },
|
106
|
+
403 => { description: 'Not authorized' }
|
107
|
+
}
|
108
|
+
```
|
109
|
+
|
110
|
+
Another common use of `api_controller` is to define a tag for all endpoints associated with a
|
111
|
+
specific controller:
|
112
|
+
``` ruby
|
113
|
+
class WidgetController < BaseApiController
|
114
|
+
|
115
|
+
open_api_controller \
|
116
|
+
tag: {
|
117
|
+
name: 'Widgets',
|
118
|
+
description: 'Manage the widgets associated with your user account'
|
119
|
+
}
|
120
|
+
```
|
121
|
+
Note that, in the example above, any documentation metadata specified for `BaseApiController` is
|
122
|
+
inherited for endpoints defined in `WidgetController`. If the same metadata key is defined for both
|
123
|
+
controllers, the child controller's metadata will override the superclass' for that key.
|
124
|
+
|
125
|
+
Note also that the process of merging metadata in a class hierarchy isn't as simple as doing a
|
126
|
+
top-level merge or recursive merge for metadata belonging to the classes in that hierarchy. The
|
127
|
+
merge process can vary depending on the sort of metadata being merged. For example, when two query
|
128
|
+
string parameter lists are merged amongst classes in a hierarchy, the query string entries will be
|
129
|
+
merged recursively. This might, for example, allow a description to be amended in a child
|
130
|
+
controller to a query string parameter defined in a base controller. For other metadata, the
|
131
|
+
collection value of a parent controller might be entirely replaced.
|
132
|
+
|
133
|
+
|
134
|
+
## Describing Objects
|
135
|
+
## Generating Documentation
|
136
|
+
|
137
|
+
Generate OpenApi (Swagger) JSON by running the following:
|
138
|
+
|
139
|
+
rake open_api:docs
|
140
|
+
|
141
|
+
Optionally, you may specify the base path and output file:
|
142
|
+
|
143
|
+
rake open_api:docs[/api/v1,/home/myhome/api-v1.json]
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module OpenApi
|
2
|
+
class Endpoints
|
3
|
+
class << self
|
4
|
+
METADATA_MERGE = {
|
5
|
+
tags: (lambda do |tags, merge_tags|
|
6
|
+
tags ||= {}
|
7
|
+
return tags if merge_tags.nil?
|
8
|
+
fail 'Expected tags as an Array!' unless merge_tags.is_a?(Array)
|
9
|
+
tags + merge_tags
|
10
|
+
end),
|
11
|
+
headers: (lambda do |headers, merge_headers, opts|
|
12
|
+
OpenApi::Utils.verify_and_merge_hash(headers, merge_headers, 'header parameters', opts)
|
13
|
+
end),
|
14
|
+
path_params: (lambda do |path_params, merge_path_params, opts|
|
15
|
+
OpenApi::Utils.verify_and_merge_hash(path_params, merge_path_params, 'path parameters',
|
16
|
+
opts)
|
17
|
+
end),
|
18
|
+
query_string: (lambda do |query_string, merge_query_string, opts|
|
19
|
+
OpenApi::Utils.verify_and_merge_hash(query_string, merge_query_string,
|
20
|
+
'query string parameters', opts)
|
21
|
+
end),
|
22
|
+
form_data: (lambda do |form_data, merge_form_data, opts|
|
23
|
+
OpenApi::Utils.verify_and_merge_hash(form_data, merge_form_data, 'form data parameters',
|
24
|
+
opts)
|
25
|
+
end),
|
26
|
+
body: (lambda do |body_data, merge_body_data, opts|
|
27
|
+
OpenApi::Utils.verify_and_merge_hash(body_data, merge_body_data, 'body', opts)
|
28
|
+
end),
|
29
|
+
responses: (lambda do |responses, merge_responses, opts|
|
30
|
+
merge_responses = Hash[(merge_responses.map do |key, hash|
|
31
|
+
fail "Invalid response code #{key}" if key.to_s != key.to_i.to_s
|
32
|
+
[key.to_i, hash]
|
33
|
+
end)]
|
34
|
+
OpenApi::Utils.verify_and_merge_hash(responses, merge_responses, 'responses', opts)
|
35
|
+
end)
|
36
|
+
}
|
37
|
+
|
38
|
+
def merge_metadata(metadata, merge_metadata, opts = {})
|
39
|
+
if (body_value = merge_metadata[:body]).respond_to?(:to_sym)
|
40
|
+
merge_metadata = merge_metadata.merge(body: { schema: { :'$ref' => body_value.to_sym } })
|
41
|
+
end
|
42
|
+
if merge_metadata.include?(:children)
|
43
|
+
merge_metadata = merge_metadata.reject { |k, _v| k == :children }
|
44
|
+
end
|
45
|
+
OpenApi::Utils.merge_hash(metadata, merge_metadata, opts.merge(merge_by: METADATA_MERGE))
|
46
|
+
end
|
47
|
+
|
48
|
+
def relative_path(path, base_path)
|
49
|
+
return path if path.blank? || base_path.blank?
|
50
|
+
relative_path = path[base_path.length..-1]
|
51
|
+
relative_path = "/#{relative_path}" unless relative_path.starts_with?('/')
|
52
|
+
relative_path
|
53
|
+
end
|
54
|
+
|
55
|
+
def verb_key(route_wrapper)
|
56
|
+
route_wrapper.verb.to_s.downcase
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_matching_routes(base_path, opts = {})
|
60
|
+
path_filter = opts[:path_filter]
|
61
|
+
if path_filter.is_a?(String) && !path_filter.starts_with?('/')
|
62
|
+
path_filter = "/#{path_filter}"
|
63
|
+
end
|
64
|
+
matching_routes = []
|
65
|
+
Rails.application.routes.routes.each do |route|
|
66
|
+
route_wrapper = ActionDispatch::Routing::RouteWrapper.new(route)
|
67
|
+
if (path = route_wrapper.path.to_s).starts_with?(base_path)
|
68
|
+
next unless check_path_filter(route, relative_path(path, base_path), path_filter, opts)
|
69
|
+
matching_routes << route_wrapper
|
70
|
+
next
|
71
|
+
end
|
72
|
+
end
|
73
|
+
matching_routes
|
74
|
+
end
|
75
|
+
|
76
|
+
def check_path_filter(route, rel_path, path_filter, opts = {})
|
77
|
+
return true unless path_filter.present?
|
78
|
+
return rel_path == path_filter if path_filter.is_a?(String)
|
79
|
+
return rel_path =~ path_filter if path_filter.is_a?(Regexp)
|
80
|
+
return path_filter.include?(rel_path) if path_filter.is_a?(Array)
|
81
|
+
if path_filter.respond_to?(:call) && path_filter.respond_to?(:parameters)
|
82
|
+
route_opts = opts.merge(route: route, controller: route.defaults[:controller],
|
83
|
+
action: route.defaults[:action])
|
84
|
+
rslt = path_filter.send(*([:call, rel_path, route_opts][0..path_filter.parameters.size]))
|
85
|
+
return rslt ? true : false
|
86
|
+
end
|
87
|
+
false
|
88
|
+
end
|
89
|
+
|
90
|
+
def build_parameter_metadata(endpoint_metadata)
|
91
|
+
parameters = {}
|
92
|
+
parameters = (parameters.is_a?(Array) ? parameters : []) +
|
93
|
+
param_array(endpoint_metadata.delete(:headers), :header) +
|
94
|
+
param_array(endpoint_metadata.delete(:path_params), :path) +
|
95
|
+
param_array(endpoint_metadata.delete(:query_string), :query) +
|
96
|
+
param_array(endpoint_metadata.delete(:form_data), :form_data)
|
97
|
+
if (body_param = endpoint_metadata.delete(:body)).is_a?(Hash)
|
98
|
+
parameters += param_array({ body: body_param }, :body)
|
99
|
+
end
|
100
|
+
return unless parameters.present?
|
101
|
+
endpoint_metadata[:parameters] = OpenApi::Utils.camelize_metadata(parameters, end_depth: 3)
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def param_array(hash, param_in)
|
107
|
+
return [] if hash.nil?
|
108
|
+
fail "Expected Hash for parameter type '#{param_in}'!" unless hash.is_a?(Hash)
|
109
|
+
hash.map do |key, value|
|
110
|
+
{ name: key, in: OpenApi::Utils.camelize_key(param_in) }.merge(
|
111
|
+
value.reject { |k, _v| [:name, :in].include?(k) })
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
module Controller
|
117
|
+
module ClassMethods
|
118
|
+
def open_api_path(path, metadata = nil)
|
119
|
+
@open_api_path_metadata ||= {} if metadata.present?
|
120
|
+
OpenApi::Utils.metadata_by_string_or_regexp(@open_api_path_metadata, path, metadata,
|
121
|
+
arg_name: 'path')
|
122
|
+
end
|
123
|
+
|
124
|
+
def open_api_path_param(path_param, param_metadata)
|
125
|
+
regexp = %r{(\A|\/)\:#{path_param}(\Z|\/)|\(\.\:#{path_param}\)}
|
126
|
+
open_api_path regexp, path_params: {
|
127
|
+
path_param.to_s => { type: :integer, required: true }.merge(param_metadata)
|
128
|
+
}
|
129
|
+
end
|
130
|
+
|
131
|
+
def open_api_controller(metadata = nil)
|
132
|
+
return (@open_api_controller_metadata || {}).deep_dup if metadata.blank?
|
133
|
+
fail 'Expected Hash argument for open_api_controller()!' unless metadata.is_a?(Hash)
|
134
|
+
OpenApi::Endpoints.merge_metadata(@open_api_controller_metadata ||= {}, metadata)
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
|
138
|
+
def open_api_action(action, metadata = nil)
|
139
|
+
@open_api_action_metadata ||= {} if metadata.present?
|
140
|
+
OpenApi::Utils.metadata_by_string_or_regexp(@open_api_action_metadata, action, metadata,
|
141
|
+
arg_name: 'action')
|
142
|
+
end
|
143
|
+
|
144
|
+
def open_api_endpoint_metadata(action, path, opts = {})
|
145
|
+
path = OpenApi::Endpoints.relative_path(path, opts[:base_path])
|
146
|
+
controller_class_hierarchy = OpenApi::Utils.controller_class_hierarchy(self)
|
147
|
+
aggr_controller_metadata = {}
|
148
|
+
controller_class_hierarchy.each do |controller_class|
|
149
|
+
OpenApi::Endpoints.merge_metadata(aggr_controller_metadata,
|
150
|
+
controller_class.send(:open_api_controller), opts)
|
151
|
+
end
|
152
|
+
aggr_path_metadata = {}
|
153
|
+
controller_class_hierarchy.each do |controller_class|
|
154
|
+
OpenApi::Endpoints.merge_metadata(aggr_path_metadata,
|
155
|
+
controller_class.send(:open_api_path, path), opts)
|
156
|
+
end
|
157
|
+
aggr_action_metadata = {}
|
158
|
+
controller_class_hierarchy.each do |controller_class|
|
159
|
+
OpenApi::Endpoints.merge_metadata(aggr_action_metadata,
|
160
|
+
controller_class.send(:open_api_action, action), opts)
|
161
|
+
end
|
162
|
+
OpenApi::Endpoints.merge_metadata(aggr_controller_metadata,
|
163
|
+
OpenApi::Endpoints.merge_metadata(aggr_action_metadata, aggr_path_metadata,
|
164
|
+
opts), opts)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.included(base)
|
169
|
+
base.extend(ClassMethods)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# rubocop:disable Rails/Output
|
2
|
+
module OpenApi
|
3
|
+
class Generator
|
4
|
+
HIDDEN_ROOT_KEYS = [:output_file_path, :base_paths]
|
5
|
+
class << self
|
6
|
+
def build(opts = {})
|
7
|
+
base_paths = find_base_paths(opts)
|
8
|
+
|
9
|
+
doc = OpenApi.global_metadata.reject { |k, _v| HIDDEN_ROOT_KEYS.include?(k.to_sym) }
|
10
|
+
doc[:info] = OpenApi::Utils.camelize_metadata(doc[:info]) if doc[:info].is_a?(Hash)
|
11
|
+
|
12
|
+
tags, paths, definitions = build_endpoint_content(base_paths, opts)
|
13
|
+
doc[:tags] = OpenApi::Utils.camelize_metadata(tags.values) if tags.present?
|
14
|
+
doc[:paths] = OpenApi::Utils.camelize_metadata(paths, start_depth: 2, end_depth: 4)
|
15
|
+
doc[:definitions] = OpenApi::Utils.camelize_metadata(definitions, start_depth: 2,
|
16
|
+
end_depth: 3)
|
17
|
+
|
18
|
+
doc = OpenApi::Utils.camelize_metadata(doc, end_depth: 2)
|
19
|
+
doc[:swagger] = doc[:swagger].to_s if doc.include?(:swagger)
|
20
|
+
|
21
|
+
doc
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(opts = {})
|
25
|
+
output_file_path = opts[:output_file_path] || OpenApi.global_metadata[:output_file_path]
|
26
|
+
unless output_file_path.respond_to?(:to_s) && output_file_path.to_s.present?
|
27
|
+
fail 'Missing output file path; Must be passed as output_file_path option, or ' \
|
28
|
+
'output_file_path must be configured in the OpenApi initializer ' \
|
29
|
+
'(config/initializers/open_api.rb)'
|
30
|
+
end
|
31
|
+
|
32
|
+
doc = nil
|
33
|
+
File.open(output_file_path.to_s, 'w') do |file|
|
34
|
+
file.write JSON.pretty_generate(doc = build(opts))
|
35
|
+
end
|
36
|
+
|
37
|
+
doc
|
38
|
+
end
|
39
|
+
|
40
|
+
def log_message(level, message, opts = {})
|
41
|
+
unless [:debug, :info, :warn, :error, :fatal].include?(level)
|
42
|
+
fail "Invalid message level: #{level}"
|
43
|
+
end
|
44
|
+
if opts[:stdout]
|
45
|
+
puts "[#{level}] #{message}"
|
46
|
+
else
|
47
|
+
Rails.logger.send(level, message.to_s)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def build_endpoint_content(base_paths, opts = {})
|
52
|
+
paths = {}
|
53
|
+
tags = {}
|
54
|
+
definitions = {}
|
55
|
+
common_base_path = find_common_base_path(base_paths)
|
56
|
+
global_opts = opts.merge(base_path: common_base_path)
|
57
|
+
base_paths.each do |base_path, base_path_opts|
|
58
|
+
opts = global_opts.merge(base_path_opts)
|
59
|
+
OpenApi::Endpoints.find_matching_routes(base_path, opts).each do |route_wrapper|
|
60
|
+
controller_name = (route_wrapper.controller).split('/').map(&:camelize).join('::') +
|
61
|
+
'Controller'
|
62
|
+
controller = controller_name.constantize
|
63
|
+
if controller.nil?
|
64
|
+
log_message(:warn, "Can't resolve controller: #{route_wrapper.controller}", opts)
|
65
|
+
next
|
66
|
+
end
|
67
|
+
next unless controller.respond_to?(:open_api_endpoint_metadata)
|
68
|
+
endpoint_metadata = controller.open_api_endpoint_metadata(route_wrapper.action,
|
69
|
+
route_wrapper.path, opts.merge(base_path: base_path))
|
70
|
+
next if endpoint_metadata[:hidden]
|
71
|
+
path = add_path(route_wrapper, paths, common_base_path, opts)
|
72
|
+
next if path.nil?
|
73
|
+
OpenApi::Endpoints.build_parameter_metadata(endpoint_metadata)
|
74
|
+
endpoint_metadata = OpenApi::Objects.resolve_refs(endpoint_metadata, definitions,
|
75
|
+
controller, opts)
|
76
|
+
endpoint_metadata = OpenApi::Tags.resolve_refs(endpoint_metadata, tags, controller,
|
77
|
+
opts)
|
78
|
+
path[OpenApi::Endpoints.verb_key(route_wrapper)] = endpoint_metadata
|
79
|
+
end
|
80
|
+
end
|
81
|
+
[tags, paths, definitions]
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_path(route_wrapper, paths, common_base_path, opts = {})
|
85
|
+
relative_path = OpenApi::Endpoints.relative_path(route_wrapper.path.to_s, common_base_path)
|
86
|
+
route_wrapper.parts.each do |path_param|
|
87
|
+
relative_path = relative_path
|
88
|
+
.gsub(%r{(\A|\/)\:(#{path_param})(\Z|\/)}, '\1{\2}\3')
|
89
|
+
.gsub(/\(\.\:#{path_param}\)/, ".{#{path_param}}")
|
90
|
+
end
|
91
|
+
path = (paths[relative_path] ||= {})
|
92
|
+
verb_key = OpenApi::Endpoints.verb_key(route_wrapper)
|
93
|
+
if path.include?(verb_key)
|
94
|
+
base_message = "Warning: Multiple OpenApi::Endpoints match #{route_wrapper.verb} " \
|
95
|
+
"#{relative_path} ... skipping entry for route"
|
96
|
+
if route_wrapper.name.present?
|
97
|
+
log_message(:warn, "#{base_message} '#{route_wrapper.name}'", opts)
|
98
|
+
else
|
99
|
+
log_message(:warn, "#{base_message} #{route_wrapper.verb} #{route_wrapper.path}", opts)
|
100
|
+
end
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
path
|
104
|
+
end
|
105
|
+
|
106
|
+
def find_base_paths(opts = {})
|
107
|
+
base_paths = opts[:base_paths] || OpenApi.global_metadata[:base_paths]
|
108
|
+
if base_paths.is_a?(Array)
|
109
|
+
base_paths = base_paths.map(&:to_s).reject(&:blank?).uniq
|
110
|
+
base_paths = Hash[(base_paths.map do |base_path|
|
111
|
+
[base_path.starts_with?('/') ? base_path : "/#{base_path}", {}]
|
112
|
+
end)]
|
113
|
+
elsif base_paths.is_a?(Hash)
|
114
|
+
base_paths = Hash[(base_paths.map do |base_path, api_opts|
|
115
|
+
fail "Expected options hash for base path '#{base_path}'" unless api_opts.is_a?(Hash)
|
116
|
+
[base_path.starts_with?('/') ? base_path : "/#{base_path}", api_opts]
|
117
|
+
end)]
|
118
|
+
else
|
119
|
+
fail "Invalid value for 'base_paths': Expected Hash or Array"
|
120
|
+
end
|
121
|
+
if base_paths.blank?
|
122
|
+
fail 'Missing API base paths; Must be passed as base_paths option, or base_paths must ' \
|
123
|
+
'be configured in the OpenApi initializer (config/initializers/open_api.rb)'
|
124
|
+
end
|
125
|
+
base_paths
|
126
|
+
end
|
127
|
+
|
128
|
+
def find_common_base_path(base_paths)
|
129
|
+
return nil if base_paths.blank?
|
130
|
+
split_paths = base_paths.map do |base_path|
|
131
|
+
base_path.split('/').reject(&:blank?)
|
132
|
+
end
|
133
|
+
path_count = split_paths.length
|
134
|
+
first_path = split_paths[0]
|
135
|
+
return "/#{first_path.join('/')}" if path_count == 1
|
136
|
+
common_elems = 0
|
137
|
+
while common_elems < first_path.length
|
138
|
+
path_elem_idx = 0
|
139
|
+
while path_elem_idx < path_count - 1
|
140
|
+
cmp_path = split_paths[path_elem_idx + 1]
|
141
|
+
break if cmp_path.length <= common_elems
|
142
|
+
break if cmp_path[common_elems] != first_path[common_elems]
|
143
|
+
path_elem_idx += 1
|
144
|
+
end
|
145
|
+
break if path_elem_idx < path_count - 1
|
146
|
+
common_elems += 1
|
147
|
+
end
|
148
|
+
return '/' if common_elems == 0
|
149
|
+
"/#{first_path[0..(common_elems - 1)].join('/')}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module OpenApi
|
2
|
+
module Objects
|
3
|
+
class << self
|
4
|
+
METADATA_MERGE = {
|
5
|
+
properties: (lambda do |properties, merge_properties, opts|
|
6
|
+
OpenApi::Utils.verify_and_merge_hash(properties, merge_properties, 'properties',
|
7
|
+
opts.merge(recursive_merge: true))
|
8
|
+
end)
|
9
|
+
}
|
10
|
+
|
11
|
+
def merge_metadata(metadata, merge_metadata, opts = {})
|
12
|
+
OpenApi::Utils.merge_hash(metadata, merge_metadata, opts.merge(merge_by: METADATA_MERGE))
|
13
|
+
end
|
14
|
+
|
15
|
+
def resolve_refs(metadata, definitions, controller, opts = {})
|
16
|
+
resolve_proc = -> (object_name) { controller.open_api_object_metadata(object_name) }
|
17
|
+
if metadata.is_a?(Hash)
|
18
|
+
Hash[(metadata.map do |key, value|
|
19
|
+
value = resolve_refs(value, definitions, controller, opts)
|
20
|
+
if ['schema', 'items', '$ref'].include?(key.to_s) && value.respond_to?(:to_sym) &&
|
21
|
+
!(object = resolve_ref(value.to_sym, resolve_proc)).nil?
|
22
|
+
fail 'Expected Hash for definitions!' unless definitions.is_a?(Hash)
|
23
|
+
object = resolve_refs(object, definitions, controller, opts)
|
24
|
+
add_definition(definitions, value.to_sym, object)
|
25
|
+
next [:'$ref', "#/definitions/#{value}"] if key.to_s == '$ref'
|
26
|
+
next [key.to_sym, { :'$ref' => "#/definitions/#{value}" }]
|
27
|
+
end
|
28
|
+
[key, value]
|
29
|
+
end)]
|
30
|
+
elsif metadata.is_a?(Array)
|
31
|
+
metadata.map do |elem|
|
32
|
+
resolve_refs(elem, definitions, controller, opts)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
metadata
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def resolve_ref(key, resolve_proc, opts = {})
|
42
|
+
unless resolve_proc.respond_to?(:call) &&
|
43
|
+
resolve_proc.respond_to?(:parameters)
|
44
|
+
fail 'Expected proc/lambda for resolve_proc!'
|
45
|
+
end
|
46
|
+
proc_param_count = resolve_proc.parameters.size
|
47
|
+
fail 'Expected 1+ parameters (object name) for resolve_proc!' if proc_param_count < 1
|
48
|
+
object = resolve_proc.send(*([:call, key, opts][0..proc_param_count]))
|
49
|
+
return nil if object.nil?
|
50
|
+
fail 'Expected hash result from resolve_proc!' unless object.is_a?(Hash)
|
51
|
+
object
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_definition(definitions, key, object)
|
55
|
+
return if object.nil?
|
56
|
+
json = object.to_json
|
57
|
+
retry_index = 0
|
58
|
+
loop do
|
59
|
+
if definitions.include?(key)
|
60
|
+
break if definitions[key].to_json == json
|
61
|
+
retry_index += 1
|
62
|
+
key = "#{key}#{retry_index}".to_sym
|
63
|
+
else
|
64
|
+
definitions[key] = object
|
65
|
+
break
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module Controller
|
72
|
+
module ClassMethods
|
73
|
+
def open_api_objects(metadata = nil)
|
74
|
+
return (@open_api_objects_metadata || {}).deep_dup if metadata.blank?
|
75
|
+
fail 'Expected Hash argument for open_api_objects()!' unless metadata.is_a?(Hash)
|
76
|
+
metadata.each do |object_key, object_metadata|
|
77
|
+
open_api_object(object_key, object_metadata)
|
78
|
+
end
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def open_api_object(object_key, metadata = nil)
|
83
|
+
fail 'Valid object argument required!' unless object_key.respond_to?(:to_sym)
|
84
|
+
return (@open_api_objects_metadata || {})[object_key.to_sym].deep_dup if metadata.blank?
|
85
|
+
fail 'Expected Hash argument for open_api_object()!' unless metadata.is_a?(Hash)
|
86
|
+
metadata = expand_nested_object_metadata(metadata)
|
87
|
+
object_metadata = ((@open_api_objects_metadata ||= {})[object_key.to_sym] ||= {})
|
88
|
+
OpenApi::Objects.merge_metadata(object_metadata, metadata)
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def expand_nested_object_metadata(metadata)
|
93
|
+
unless metadata[:type].respond_to?(:to_sym) && metadata[:type].to_sym == :object
|
94
|
+
metadata = { type: :object, properties: metadata }
|
95
|
+
end
|
96
|
+
required_attrs = (metadata[:required] || []).map(&:to_sym)
|
97
|
+
if (properties = metadata[:properties]).is_a?(Hash) && properties.present?
|
98
|
+
metadata = metadata.dup
|
99
|
+
properties = Hash[(properties.map do |name, property|
|
100
|
+
property = expand_nested_object_property(name, property, required_attrs)
|
101
|
+
[name, property]
|
102
|
+
end)]
|
103
|
+
metadata[:properties] = properties
|
104
|
+
end
|
105
|
+
metadata[:required] = required_attrs.uniq if required_attrs.present?
|
106
|
+
metadata
|
107
|
+
end
|
108
|
+
|
109
|
+
def expand_nested_object_property(name, property, required_attrs)
|
110
|
+
if property.is_a?(Hash)
|
111
|
+
required = property[:required]
|
112
|
+
if required.nil?
|
113
|
+
required_attrs << name.to_sym # Presume required if required option not spec'd
|
114
|
+
elsif [TrueClass, FalseClass].include?(required.class)
|
115
|
+
required_attrs << name.to_sym if property.delete(:required)
|
116
|
+
end
|
117
|
+
unless property.blank? || property[:type].respond_to?(:to_sym)
|
118
|
+
property = expand_nested_object_metadata(property)
|
119
|
+
end
|
120
|
+
else
|
121
|
+
api_type, api_format = OpenApi::Utils.open_api_type_and_format(property)
|
122
|
+
if api_type.nil?
|
123
|
+
property = { type: :object, '$ref' => property }
|
124
|
+
elsif api_format.present?
|
125
|
+
property = { type: api_type, format: api_format }
|
126
|
+
else
|
127
|
+
property = { type: api_type }
|
128
|
+
end
|
129
|
+
required_attrs << name.to_sym # Presume required if required option not spec'd
|
130
|
+
end
|
131
|
+
property
|
132
|
+
end
|
133
|
+
|
134
|
+
def open_api_object_metadata(object_key, opts = {})
|
135
|
+
controller_class_hierarchy = OpenApi::Utils.controller_class_hierarchy(self)
|
136
|
+
aggr_object_metadata = {}
|
137
|
+
controller_class_hierarchy.each do |controller_class|
|
138
|
+
OpenApi::Objects.merge_metadata(aggr_object_metadata,
|
139
|
+
controller_class.send(:open_api_object, object_key), opts)
|
140
|
+
end
|
141
|
+
aggr_object_metadata
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.included(base)
|
146
|
+
base.extend(ClassMethods)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module OpenApi
|
2
|
+
class Tags
|
3
|
+
class << self
|
4
|
+
def merge_metadata(metadata, merge_metadata, opts = {})
|
5
|
+
OpenApi::Utils.merge_hash(metadata, merge_metadata, opts.merge(recursive_merge: true))
|
6
|
+
end
|
7
|
+
|
8
|
+
def resolve_refs(metadata, tags, controller, opts = {})
|
9
|
+
opts = opts.symbolize_keys
|
10
|
+
opts[:define_proc] ||=
|
11
|
+
-> (tag_name, tag_metadata) { controller.open_api_tag(tag_name, tag_metadata) }
|
12
|
+
opts[:resolve_proc] ||=
|
13
|
+
-> (tag_name) { controller.open_api_tag_metadata(tag_name) }
|
14
|
+
if metadata.is_a?(Hash)
|
15
|
+
resolve_hash_refs(metadata, tags, controller, opts)
|
16
|
+
elsif metadata.is_a?(Array)
|
17
|
+
metadata.map do |elem|
|
18
|
+
resolve_refs(elem, tags, controller, opts)
|
19
|
+
end
|
20
|
+
else
|
21
|
+
metadata
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def resolve_hash_refs(hash, tags, controller, opts)
|
28
|
+
Hash[(hash.map do |key, value|
|
29
|
+
next [key, value] unless %w(tag tags).include?(key.to_s)
|
30
|
+
if key.to_s == 'tag'
|
31
|
+
values = [value]
|
32
|
+
else
|
33
|
+
next [key, value] unless value.is_a?(Array)
|
34
|
+
values = value
|
35
|
+
end
|
36
|
+
values = resolve_tag_values(values, tags, controller, opts)
|
37
|
+
next values.nil? ? [key, values] : [:tags, values]
|
38
|
+
end)]
|
39
|
+
end
|
40
|
+
|
41
|
+
def resolve_tag_values(values, tags, controller, opts = {})
|
42
|
+
define_proc = opts[:define_proc] ||
|
43
|
+
-> (tag_name, metadata) { controller.open_api_tag(tag_name, metadata) }
|
44
|
+
resolve_proc = opts[:resolve_proc] ||
|
45
|
+
-> (tag_name) { controller.open_api_tag_metadata(tag_name) }
|
46
|
+
values = (values.map do |value|
|
47
|
+
if value.is_a?(Hash)
|
48
|
+
next nil unless value[:name].respond_to?(:to_sym)
|
49
|
+
name = value[:name].to_s
|
50
|
+
value = value.merge(name: name)
|
51
|
+
metadata = define_ref(name, value, define_proc, opts)
|
52
|
+
add_tag(tags, name, metadata)
|
53
|
+
name
|
54
|
+
else
|
55
|
+
value.respond_to?(:to_sym) ? value.to_s : nil
|
56
|
+
end
|
57
|
+
end).compact
|
58
|
+
return nil if values.empty?
|
59
|
+
values = (values.map do |tag|
|
60
|
+
next nil unless tag.respond_to?(:to_sym)
|
61
|
+
name = tag.to_s
|
62
|
+
metadata = resolve_ref(name, resolve_proc, opts)
|
63
|
+
fail 'Expected Hash for metadata!' unless metadata.is_a?(Hash)
|
64
|
+
add_tag(tags, name, metadata)
|
65
|
+
name
|
66
|
+
end).compact
|
67
|
+
values
|
68
|
+
end
|
69
|
+
|
70
|
+
def resolve_ref(key, resolve_proc, opts = {})
|
71
|
+
unless resolve_proc.respond_to?(:call) &&
|
72
|
+
resolve_proc.respond_to?(:parameters)
|
73
|
+
fail 'Expected proc/lambda for resolve_proc!'
|
74
|
+
end
|
75
|
+
proc_param_count = resolve_proc.parameters.size
|
76
|
+
fail 'Expected 1+ parameters (tag) for resolve_proc!' if proc_param_count < 1
|
77
|
+
tag = resolve_proc.send(*([:call, key, opts][0..proc_param_count]))
|
78
|
+
return nil if tag.nil?
|
79
|
+
fail 'Expected hash result from resolve_proc!' unless tag.is_a?(Hash)
|
80
|
+
tag
|
81
|
+
end
|
82
|
+
|
83
|
+
def define_ref(key, metadata, define_proc, opts = {})
|
84
|
+
unless define_proc.respond_to?(:call) &&
|
85
|
+
define_proc.respond_to?(:parameters)
|
86
|
+
fail 'Expected proc/lambda for define_proc!'
|
87
|
+
end
|
88
|
+
proc_param_count = define_proc.parameters.size
|
89
|
+
fail 'Expected 2+ parameters (tag, metadata) for define_proc!' if proc_param_count < 1
|
90
|
+
tag = define_proc.send(*([:call, key, metadata, opts][0..proc_param_count]))
|
91
|
+
return nil if tag.nil?
|
92
|
+
fail 'Expected hash result from define_proc!' unless tag.is_a?(Hash)
|
93
|
+
tag
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_tag(tags, tag, metadata)
|
97
|
+
return if metadata.nil?
|
98
|
+
json = metadata.to_json
|
99
|
+
retry_index = 0
|
100
|
+
loop do
|
101
|
+
if tags.include?(tag)
|
102
|
+
break if tags[tag].to_json == json
|
103
|
+
retry_index += 1
|
104
|
+
tag = "#{tag} (#{retry_index})".to_s
|
105
|
+
else
|
106
|
+
tags[tag] = metadata
|
107
|
+
break
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
module Controller
|
114
|
+
module ClassMethods
|
115
|
+
def open_api_tags(metadata = nil)
|
116
|
+
return (@open_api_tags_metadata || {}).deep_dup if metadata.blank?
|
117
|
+
fail 'Expected Hash argument for open_api_tags()!' unless metadata.is_a?(Hash)
|
118
|
+
metadata.each { |tag, tag_metadata| open_api_tag(tag, tag_metadata) }
|
119
|
+
(@open_api_tags_metadata || {})
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
|
123
|
+
def open_api_tag(tag, metadata = nil)
|
124
|
+
fail 'Valid tag argument required!' unless tag.respond_to?(:to_s)
|
125
|
+
return (@open_api_tags_metadata || {})[tag.to_s].deep_dup if metadata.blank?
|
126
|
+
fail 'Expected Hash argument for open_api_tag()!' unless metadata.is_a?(Hash)
|
127
|
+
metadata = { name: tag.to_s }.merge(metadata) unless metadata.include?(:name)
|
128
|
+
tag_metadata = ((@open_api_tags_metadata ||= {})[tag.to_s] ||= {})
|
129
|
+
OpenApi::Tags.merge_metadata(tag_metadata, metadata)
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
def open_api_tag_metadata(tag, opts = {})
|
134
|
+
controller_class_hierarchy = OpenApi::Utils.controller_class_hierarchy(self)
|
135
|
+
aggr_metadata = {}
|
136
|
+
controller_class_hierarchy.each do |controller_class|
|
137
|
+
OpenApi::Tags.merge_metadata(aggr_metadata,
|
138
|
+
controller_class.send(:open_api_tag, tag), opts)
|
139
|
+
end
|
140
|
+
aggr_metadata
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.included(base)
|
145
|
+
base.extend(ClassMethods)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module OpenApi
|
2
|
+
class Utils
|
3
|
+
class << self
|
4
|
+
def controller_class_hierarchy(controller_class)
|
5
|
+
controller_class_hierarchy = [controller_class]
|
6
|
+
loop do
|
7
|
+
controller_class = controller_class.superclass
|
8
|
+
break if controller_class.nil? || !controller_class.respond_to?(:open_api_controller)
|
9
|
+
controller_class_hierarchy << controller_class
|
10
|
+
end
|
11
|
+
controller_class_hierarchy
|
12
|
+
end
|
13
|
+
|
14
|
+
def verify_and_merge_hash(hash, merge_hash, hash_desc, opts = {})
|
15
|
+
return (hash || {}) if merge_hash.nil?
|
16
|
+
fail "Expected #{hash_desc} in the form of a Hash!" unless merge_hash.is_a?(Hash)
|
17
|
+
merge_hash(hash, merge_hash, opts)
|
18
|
+
end
|
19
|
+
|
20
|
+
def merge_hash(hash, merge_hash, opts = {})
|
21
|
+
hash ||= {}
|
22
|
+
return hash if merge_hash.nil?
|
23
|
+
fail 'Expected Hash!' unless merge_hash.is_a?(Hash)
|
24
|
+
merge_hash.each do |key, value|
|
25
|
+
if hash.include?(key)
|
26
|
+
merge_hash_entry(hash, key, value, opts)
|
27
|
+
elsif !value.nil?
|
28
|
+
hash[key] = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
hash
|
32
|
+
end
|
33
|
+
|
34
|
+
def camelize_metadata(metadata, opts = {})
|
35
|
+
if (start_depth = opts[:start_depth].to_i) > 0
|
36
|
+
start_depth -= 1
|
37
|
+
if start_depth > 0
|
38
|
+
opts = opts.merge(start_depth: start_depth)
|
39
|
+
else
|
40
|
+
(opts = opts.dup).delete(:start_depth)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
if (end_depth = opts[:end_depth]).present?
|
44
|
+
end_depth -= 1
|
45
|
+
return metadata if end_depth <= 0
|
46
|
+
opts = opts.merge(end_depth: end_depth)
|
47
|
+
end
|
48
|
+
if metadata.is_a?(Hash)
|
49
|
+
Hash[(metadata.map do |k, v|
|
50
|
+
[start_depth > 0 ? k : camelize_key(k), camelize_metadata(v, opts)]
|
51
|
+
end)]
|
52
|
+
elsif metadata.is_a?(Array)
|
53
|
+
metadata.map { |v| camelize_metadata(v, opts) }
|
54
|
+
else
|
55
|
+
metadata
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def camelize_key(key)
|
60
|
+
key.to_s.camelize(:lower).to_sym
|
61
|
+
end
|
62
|
+
|
63
|
+
def metadata_by_string_or_regexp(metadata_map, string_or_regexp, metadata, opts = {})
|
64
|
+
arg_ref = "#{opts[:arg_name]} argument".strip
|
65
|
+
if metadata.blank?
|
66
|
+
fail "Valid #{arg_ref} required!" unless string_or_regexp.respond_to?(:to_sym)
|
67
|
+
response_metadata = {}
|
68
|
+
(metadata_map || {}).each do |key, item_metadata|
|
69
|
+
if key.is_a?(Regexp)
|
70
|
+
next unless string_or_regexp =~ key
|
71
|
+
else
|
72
|
+
next unless string_or_regexp.casecmp(key) == 0
|
73
|
+
end
|
74
|
+
OpenApi::Endpoints.merge_metadata(response_metadata, item_metadata.deep_dup)
|
75
|
+
end
|
76
|
+
return response_metadata
|
77
|
+
end
|
78
|
+
unless string_or_regexp.respond_to?(:to_sym) || string_or_regexp.is_a?(Regexp)
|
79
|
+
fail "Valid #{arg_ref} required!"
|
80
|
+
end
|
81
|
+
fail 'Expected Hash metadata_map argument!' unless metadata_map.is_a?(Hash)
|
82
|
+
fail 'Expected Hash metadata argument!' unless metadata.is_a?(Hash)
|
83
|
+
string_or_regexp = string_or_regexp.to_s unless string_or_regexp.is_a?(Regexp)
|
84
|
+
existing_metadata = (metadata_map[string_or_regexp] ||= {})
|
85
|
+
OpenApi::Endpoints.merge_metadata(existing_metadata, metadata)
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
def open_api_type_and_format(type_name)
|
90
|
+
case type_name.to_s.downcase.to_sym
|
91
|
+
when :integer then [:integer, :int32]
|
92
|
+
when :long then [:integer, :int]
|
93
|
+
when :float then [:number, :float]
|
94
|
+
when :double then [:number, :double]
|
95
|
+
when :string then [:string, nil]
|
96
|
+
when :byte then [:string, :byte]
|
97
|
+
when :binary then [:string, :binary]
|
98
|
+
when :boolean then [:boolean, nil]
|
99
|
+
when :date then [:string, :date]
|
100
|
+
when :datetime then [:string, :'date-time']
|
101
|
+
when :password then [:string, :password]
|
102
|
+
else [nil, nil]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def merge_hash_entry(hash, key, value, opts)
|
109
|
+
merge_by_hash = opts[:merge_by] || {}
|
110
|
+
if (merge_by = merge_by_hash[key]).present?
|
111
|
+
if merge_by.respond_to?(:call) && merge_by.respond_to?(:parameters)
|
112
|
+
if (param_count = merge_by.parameters.size) < 2
|
113
|
+
fail "Expected 2+ parameters (existing/merged values) for '#{key}' merge_by proc!"
|
114
|
+
end
|
115
|
+
merge_by.send(*([:call, hash[key], value, opts][0..param_count]))
|
116
|
+
end
|
117
|
+
elsif hash[key].is_a?(Hash) && value.is_a?(Hash)
|
118
|
+
if opts[:recursive_merge]
|
119
|
+
merge_hash(hash[key], value, opts)
|
120
|
+
else
|
121
|
+
hash[key].merge!(value)
|
122
|
+
end
|
123
|
+
elsif value.nil?
|
124
|
+
hash.delete(key)
|
125
|
+
else
|
126
|
+
hash[key] = value
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/lib/open-api.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'open-api/controller.rb'
|
2
|
+
require 'open-api/endpoints.rb'
|
3
|
+
require 'open-api/generator.rb'
|
4
|
+
require 'open-api/objects.rb'
|
5
|
+
require 'open-api/tags.rb'
|
6
|
+
require 'open-api/utils.rb'
|
7
|
+
|
8
|
+
module OpenApi
|
9
|
+
class << self
|
10
|
+
def configure(metadata = nil, &block)
|
11
|
+
return unless metadata.is_a?(Hash) || block_given?
|
12
|
+
global_metadata = @open_api_global_metadata || default_global_metadata
|
13
|
+
if metadata.is_a?(Hash)
|
14
|
+
global_metadata = OpenApi::Utils.merge_hash(global_metadata, metadata)
|
15
|
+
end
|
16
|
+
if block_given?
|
17
|
+
config = OpenStruct.new(global_metadata)
|
18
|
+
block.call(config)
|
19
|
+
global_metadata = OpenApi::Utils.merge_hash(global_metadata, config.to_h.symbolize_keys)
|
20
|
+
end
|
21
|
+
@open_api_global_metadata = global_metadata
|
22
|
+
end
|
23
|
+
|
24
|
+
def global_metadata
|
25
|
+
@open_api_global_metadata || default_global_metadata
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_global_metadata
|
29
|
+
{
|
30
|
+
swagger: 2.0,
|
31
|
+
schemes: [:http]
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/open-api.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'open-api'
|
6
|
+
s.version = '0.8.2'
|
7
|
+
s.summary = 'Inline Openi API documentation for Ruby on Rails'
|
8
|
+
s.description = 'Provides the ability to specify Open API documentation inline within the ' \
|
9
|
+
'source code of your Ruby on Rails project, utilizing a rake task to generate / maintain ' \
|
10
|
+
'that documentation as required.'
|
11
|
+
s.licenses = ['Apache 2']
|
12
|
+
s.authors = ['Matthew Mead']
|
13
|
+
s.email = 'm.mead@precisionhawk.com'
|
14
|
+
|
15
|
+
s.files = Dir.glob("{lib,spec,config}/**/*")
|
16
|
+
s.files += %w(open-api.gemspec README.md)
|
17
|
+
|
18
|
+
s.require_path = "lib"
|
19
|
+
|
20
|
+
s.add_dependency "rails", ">= 4.0"
|
21
|
+
|
22
|
+
s.add_development_dependency "rspec-rails"
|
23
|
+
s.add_development_dependency "factory_girl"
|
24
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: open-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Mead
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
12
|
-
dependencies:
|
11
|
+
date: 2016-06-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: factory_girl
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
13
55
|
description: Provides the ability to specify Open API documentation inline within
|
14
56
|
the source code of your Ruby on Rails project, utilizing a rake task to generate
|
15
57
|
/ maintain that documentation as required.
|
@@ -17,7 +59,16 @@ email: m.mead@precisionhawk.com
|
|
17
59
|
executables: []
|
18
60
|
extensions: []
|
19
61
|
extra_rdoc_files: []
|
20
|
-
files:
|
62
|
+
files:
|
63
|
+
- README.md
|
64
|
+
- lib/open-api.rb
|
65
|
+
- lib/open-api/controller.rb
|
66
|
+
- lib/open-api/endpoints.rb
|
67
|
+
- lib/open-api/generator.rb
|
68
|
+
- lib/open-api/objects.rb
|
69
|
+
- lib/open-api/tags.rb
|
70
|
+
- lib/open-api/utils.rb
|
71
|
+
- open-api.gemspec
|
21
72
|
homepage:
|
22
73
|
licenses:
|
23
74
|
- Apache 2
|