swagger-doc-rails 1.0.0
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 +7 -0
- data/.gitignore +18 -0
- data/Appraisals +7 -0
- data/CHANGELOG.md +120 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +448 -0
- data/Rakefile +10 -0
- data/certs/gem-public_cert.pem +21 -0
- data/lib/swagger/docs.rb +14 -0
- data/lib/swagger/docs/api_declaration_file.rb +125 -0
- data/lib/swagger/docs/api_declaration_file_metadata.rb +22 -0
- data/lib/swagger/docs/config.rb +63 -0
- data/lib/swagger/docs/dsl.rb +119 -0
- data/lib/swagger/docs/generator.rb +327 -0
- data/lib/swagger/docs/impotent_methods.rb +24 -0
- data/lib/swagger/docs/methods.rb +52 -0
- data/lib/swagger/docs/task.rb +9 -0
- data/lib/swagger/docs/version.rb +5 -0
- data/lib/tasks/swagger.rake +10 -0
- data/spec/fixtures/controllers/application_controller.rb +4 -0
- data/spec/fixtures/controllers/custom_resource_path_controller.rb +18 -0
- data/spec/fixtures/controllers/ignored_controller.rb +6 -0
- data/spec/fixtures/controllers/multiple_routes_controller.rb +13 -0
- data/spec/fixtures/controllers/nested_controller.rb +18 -0
- data/spec/fixtures/controllers/sample_controller.rb +81 -0
- data/spec/lib/swagger/docs/api_declaration_file_metadata_spec.rb +48 -0
- data/spec/lib/swagger/docs/api_declaration_file_spec.rb +203 -0
- data/spec/lib/swagger/docs/config_spec.rb +48 -0
- data/spec/lib/swagger/docs/dsl_spec.rb +17 -0
- data/spec/lib/swagger/docs/generator_spec.rb +451 -0
- data/spec/lib/swagger/docs/methods.rb +15 -0
- data/spec/spec_helper.rb +56 -0
- data/swagger-doc-rails.gemspec +31 -0
- metadata +175 -0
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBBMRMwEQYDVQQDDApyaWNo
|
3
|
+
aG9sbGlzMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNj
|
4
|
+
b20wHhcNMTMxMDIyMTMwMzI3WhcNMTQxMDIyMTMwMzI3WjBBMRMwEQYDVQQDDApy
|
5
|
+
aWNoaG9sbGlzMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
|
6
|
+
FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDppQTU++yinAuC
|
7
|
+
ydu87c/vDGTmE5Go9/zI48/T0kTco+JbUn4BPUaK0DWCEpZULvqwQAqVQm8JQnIU
|
8
|
+
6Z3k1tAQbhtgbG2oWNIxyC7SyXMQw/ag5qoAhw6k3DFE+jGKrREzADFb7vG+nPYp
|
9
|
+
4yinx27jCTIAv7/z2AVt6HoHOYh1s0HniJQWCebi7QgNXboMY8MpFxSwNkcFjl14
|
10
|
+
KMSf9SX7iOyiwqgcJmN0fN4be8pH5j/EdinUL1rWlwldcUo2+6LChBswRPmtdaZv
|
11
|
+
UyICuX5VfVJA0KrA/ihIMLaZVO5esFso+YrpP+QgbvhLwhn5e/sB5dr3a+y0+GJZ
|
12
|
+
zPGtm60bAgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
13
|
+
BBShIiKLL1E1JG++RUVAOSPO7rZV0TAfBgNVHREEGDAWgRRyaWNoaG9sbGlzQGdt
|
14
|
+
YWlsLmNvbTAfBgNVHRIEGDAWgRRyaWNoaG9sbGlzQGdtYWlsLmNvbTANBgkqhkiG
|
15
|
+
9w0BAQUFAAOCAQEAe4P1TzJlVhUn60Wx/431wNnuHZS9K4gSzmNr4zuZU6lP3rxx
|
16
|
+
rMsSY1nJY1nTBqX9W62hO+KS14ncbZvNU59ao5YVXHDflEB3Yz20DP9E2Uws64Bx
|
17
|
+
ify0Dwuq4VV2PiQbczuTGhGupzQpkMttWNZqVdjDbH5k8sGx3MumNX7YUJwUerhZ
|
18
|
+
bTBme5soNyJzAeWBqCBPT9p98rC6vqhcBfAVF6RbERYL6MPyoBZWqGeuMR4H2X/v
|
19
|
+
RYcsqDfanYBx7QcftOnbeQq7/Ep7Zx+W9+Ph3TiJLMLdAr7bLkgN1SjvrjTL5mQR
|
20
|
+
FuQtYvE4LKiUQpG7vLTRB78dQBlSj9fnv2OM9w==
|
21
|
+
-----END CERTIFICATE-----
|
data/lib/swagger/docs.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "swagger/docs/config"
|
2
|
+
require "swagger/docs/dsl"
|
3
|
+
require "swagger/docs/api_declaration_file_metadata"
|
4
|
+
require "swagger/docs/api_declaration_file"
|
5
|
+
require "swagger/docs/generator"
|
6
|
+
require "swagger/docs/impotent_methods"
|
7
|
+
require "swagger/docs/methods"
|
8
|
+
require "swagger/docs/task"
|
9
|
+
require "swagger/docs/version"
|
10
|
+
|
11
|
+
module Swagger
|
12
|
+
module Docs
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Swagger
|
2
|
+
module Docs
|
3
|
+
class ApiDeclarationFile
|
4
|
+
attr_reader :metadata, :apis
|
5
|
+
|
6
|
+
def initialize(metadata, apis, models)
|
7
|
+
@metadata = metadata
|
8
|
+
@apis = camelize_keys_deep apis
|
9
|
+
@models = models
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate_resource
|
13
|
+
resource = build_resource_root_hash
|
14
|
+
# Add the already-normalized models to the resource.
|
15
|
+
resource = resource.merge({:models => models}) if models.present?
|
16
|
+
resource
|
17
|
+
end
|
18
|
+
|
19
|
+
def base_path
|
20
|
+
metadata.base_path
|
21
|
+
end
|
22
|
+
|
23
|
+
def path
|
24
|
+
metadata.path
|
25
|
+
end
|
26
|
+
|
27
|
+
def swagger_version
|
28
|
+
metadata.swagger_version
|
29
|
+
end
|
30
|
+
|
31
|
+
def api_version
|
32
|
+
metadata.api_version
|
33
|
+
end
|
34
|
+
|
35
|
+
def controller_base_path
|
36
|
+
metadata.controller_base_path
|
37
|
+
end
|
38
|
+
|
39
|
+
def camelize_model_properties
|
40
|
+
metadata.camelize_model_properties
|
41
|
+
end
|
42
|
+
|
43
|
+
def resource_path
|
44
|
+
metadata.overridden_resource_path || demod
|
45
|
+
end
|
46
|
+
|
47
|
+
def resource_file_path
|
48
|
+
trim_leading_slash(debased_path.to_s.underscore)
|
49
|
+
end
|
50
|
+
|
51
|
+
def models
|
52
|
+
normalize_model_properties @models
|
53
|
+
end
|
54
|
+
|
55
|
+
def authorizations
|
56
|
+
metadata.authorizations
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def build_resource_root_hash
|
62
|
+
{
|
63
|
+
"apiVersion" => api_version,
|
64
|
+
"swaggerVersion" => swagger_version,
|
65
|
+
"basePath" => base_path,
|
66
|
+
"resourcePath" => resource_path,
|
67
|
+
"apis" => apis,
|
68
|
+
"resourceFilePath" => resource_file_path,
|
69
|
+
"authorizations" => authorizations
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def normalize_model_properties(models)
|
74
|
+
Hash[
|
75
|
+
models.map do |k, v|
|
76
|
+
if camelize_model_properties
|
77
|
+
[k.to_s, camelize_keys_deep(v)]
|
78
|
+
else
|
79
|
+
[k.to_s, stringify_keys_deep(v)]
|
80
|
+
end
|
81
|
+
end]
|
82
|
+
end
|
83
|
+
|
84
|
+
def demod
|
85
|
+
"#{debased_path.to_s.camelize}".demodulize.camelize.underscore
|
86
|
+
end
|
87
|
+
|
88
|
+
def debased_path
|
89
|
+
path.gsub("#{controller_base_path}", "")
|
90
|
+
end
|
91
|
+
|
92
|
+
def trim_leading_slash(str)
|
93
|
+
return str if !str
|
94
|
+
str.gsub(/\A\/+/, '')
|
95
|
+
end
|
96
|
+
|
97
|
+
def camelize_keys_deep(obj)
|
98
|
+
process_keys_deep(obj){|key| key.to_s.camelize(:lower)}
|
99
|
+
end
|
100
|
+
|
101
|
+
def stringify_keys_deep(obj)
|
102
|
+
process_keys_deep(obj){|key| key.to_s}
|
103
|
+
end
|
104
|
+
|
105
|
+
def process_keys_deep(obj, &block)
|
106
|
+
if obj.is_a? Hash
|
107
|
+
Hash[
|
108
|
+
obj.map do |k, v|
|
109
|
+
new_key = block.call(k)
|
110
|
+
new_value = process_keys_deep v, &block
|
111
|
+
[new_key, new_value]
|
112
|
+
end
|
113
|
+
]
|
114
|
+
elsif obj.is_a? Array
|
115
|
+
new_value = obj.collect do |a|
|
116
|
+
process_keys_deep a, &block
|
117
|
+
end
|
118
|
+
else
|
119
|
+
obj
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Swagger
|
2
|
+
module Docs
|
3
|
+
class ApiDeclarationFileMetadata
|
4
|
+
DEFAULT_SWAGGER_VERSION = "1.2"
|
5
|
+
DEFAULT_RESOURCE_PATH = nil
|
6
|
+
|
7
|
+
attr_reader :api_version, :path, :base_path, :controller_base_path, :swagger_version, :camelize_model_properties
|
8
|
+
attr_reader :authorizations, :overridden_resource_path
|
9
|
+
|
10
|
+
def initialize(api_version, path, base_path, controller_base_path, options={})
|
11
|
+
@api_version = api_version
|
12
|
+
@path = path
|
13
|
+
@base_path = base_path
|
14
|
+
@controller_base_path = controller_base_path
|
15
|
+
@swagger_version = options.fetch(:swagger_version, DEFAULT_SWAGGER_VERSION)
|
16
|
+
@camelize_model_properties = options.fetch(:camelize_model_properties, true)
|
17
|
+
@authorizations = options.fetch(:authorizations, {})
|
18
|
+
@overridden_resource_path = options.fetch(:resource_path, DEFAULT_RESOURCE_PATH)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Swagger
|
2
|
+
module Docs
|
3
|
+
class Config
|
4
|
+
class << self
|
5
|
+
@@base_api_controller = nil
|
6
|
+
|
7
|
+
def base_api_controller
|
8
|
+
@@base_api_controller || ActionController::Base
|
9
|
+
end
|
10
|
+
|
11
|
+
def base_api_controllers
|
12
|
+
Array(base_api_controller)
|
13
|
+
end
|
14
|
+
|
15
|
+
def base_api_controller=(controller)
|
16
|
+
@@base_api_controller = controller
|
17
|
+
end
|
18
|
+
|
19
|
+
alias_method :base_api_controllers=, :base_api_controller=
|
20
|
+
|
21
|
+
def base_applications
|
22
|
+
Array(base_application)
|
23
|
+
end
|
24
|
+
|
25
|
+
def base_application
|
26
|
+
Rails.application
|
27
|
+
end
|
28
|
+
|
29
|
+
def register_apis(versions)
|
30
|
+
base_api_controllers.each do |controller|
|
31
|
+
controller.send(:include, ImpotentMethods)
|
32
|
+
end
|
33
|
+
@versions = versions
|
34
|
+
end
|
35
|
+
|
36
|
+
def registered_apis
|
37
|
+
@versions ||= {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def transform_path(path, api_version)
|
41
|
+
# This is only for overriding, so don't perform any path transformations by default.
|
42
|
+
path
|
43
|
+
end
|
44
|
+
|
45
|
+
def log_exception
|
46
|
+
yield
|
47
|
+
rescue => e
|
48
|
+
write_log(:error, e)
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
|
52
|
+
def log_env_name
|
53
|
+
'SD_LOG_LEVEL'
|
54
|
+
end
|
55
|
+
|
56
|
+
def write_log(type, output)
|
57
|
+
$stderr.puts output if type == :error and ENV[log_env_name]=="1"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Swagger
|
2
|
+
module Docs
|
3
|
+
class SwaggerDSL
|
4
|
+
# http://stackoverflow.com/questions/5851127/change-the-context-binding-inside-a-block-in-ruby/5851325#5851325
|
5
|
+
def self.call(action, caller, &block)
|
6
|
+
# Create a new SwaggerDSL instance, and instance_eval the block to it
|
7
|
+
instance = new
|
8
|
+
instance.instance_eval(&block)
|
9
|
+
# Now return all of the set instance variables as a Hash
|
10
|
+
instance.instance_variables.inject({}) { |result_hash, instance_variable|
|
11
|
+
result_hash[instance_variable] = instance.instance_variable_get(instance_variable)
|
12
|
+
result_hash # Gotta have the block return the result_hash
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def summary(text)
|
17
|
+
@summary = text
|
18
|
+
end
|
19
|
+
|
20
|
+
def notes(text)
|
21
|
+
@notes = text
|
22
|
+
end
|
23
|
+
|
24
|
+
def method(method)
|
25
|
+
@method = method
|
26
|
+
end
|
27
|
+
|
28
|
+
def type(type)
|
29
|
+
@type = type
|
30
|
+
end
|
31
|
+
|
32
|
+
def items(items)
|
33
|
+
@items = items
|
34
|
+
end
|
35
|
+
|
36
|
+
def consumes(mime_types)
|
37
|
+
@consumes = mime_types
|
38
|
+
end
|
39
|
+
|
40
|
+
def nickname(nickname)
|
41
|
+
@nickname = nickname
|
42
|
+
end
|
43
|
+
|
44
|
+
def parameters
|
45
|
+
@parameters ||= []
|
46
|
+
end
|
47
|
+
|
48
|
+
def param(param_type, name, type, required, description = nil, hash={})
|
49
|
+
parameters << {:param_type => param_type, :name => name, :type => type,
|
50
|
+
:description => description, :required => required == :required}.merge(hash)
|
51
|
+
end
|
52
|
+
|
53
|
+
# helper method to generate enums
|
54
|
+
def param_list(param_type, name, type, required, description = nil, allowed_values = [], hash = {})
|
55
|
+
hash.merge!({allowable_values: {value_type: "LIST", values: allowed_values}})
|
56
|
+
param(param_type, name, type, required, description, hash)
|
57
|
+
end
|
58
|
+
|
59
|
+
def response_messages
|
60
|
+
@response_messages ||= []
|
61
|
+
end
|
62
|
+
|
63
|
+
def response(status, text = nil, model = nil)
|
64
|
+
if status.is_a? Symbol
|
65
|
+
status = :ok if status == :success
|
66
|
+
status_code = Rack::Utils.status_code(status)
|
67
|
+
response_messages << {:code => status_code, :responseModel => model, :message => text || status.to_s.titleize}
|
68
|
+
else
|
69
|
+
response_messages << {:code => status, :responseModel => model, :message => text}
|
70
|
+
end
|
71
|
+
response_messages.sort_by!{|i| i[:code]}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class SwaggerModelDSL
|
76
|
+
attr_accessor :id
|
77
|
+
|
78
|
+
# http://stackoverflow.com/questions/5851127/change-the-context-binding-inside-a-block-in-ruby/5851325#5851325
|
79
|
+
def self.call(model_name, caller, &block)
|
80
|
+
# Create a new SwaggerModelDSL instance, and instance_eval the block to it
|
81
|
+
instance = new
|
82
|
+
instance.instance_eval(&block)
|
83
|
+
instance.id = model_name
|
84
|
+
# Now return all of the set instance variables as a Hash
|
85
|
+
instance.instance_variables.inject({}) { |result_hash, instance_var_name|
|
86
|
+
key = instance_var_name[1..-1].to_sym # Strip prefixed @ sign.
|
87
|
+
result_hash[key] = instance.instance_variable_get(instance_var_name)
|
88
|
+
result_hash # Gotta have the block return the result_hash
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def properties
|
93
|
+
@properties ||= {}
|
94
|
+
end
|
95
|
+
|
96
|
+
def required
|
97
|
+
@required ||= []
|
98
|
+
end
|
99
|
+
|
100
|
+
def description(description)
|
101
|
+
@description = description
|
102
|
+
end
|
103
|
+
|
104
|
+
def property(name, type, required, description = nil, hash={})
|
105
|
+
properties[name] = {
|
106
|
+
type: type,
|
107
|
+
description: description,
|
108
|
+
}.merge!(hash)
|
109
|
+
self.required << name if required == :required
|
110
|
+
end
|
111
|
+
|
112
|
+
# helper method to generate enums
|
113
|
+
def property_list(name, type, required, description = nil, allowed_values = [], hash = {})
|
114
|
+
hash.merge!({allowable_values: {value_type: "LIST", values: allowed_values}})
|
115
|
+
property(name, type, required, description, hash)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,327 @@
|
|
1
|
+
module Swagger
|
2
|
+
module Docs
|
3
|
+
class Generator
|
4
|
+
|
5
|
+
DEFAULT_VER = "1.0"
|
6
|
+
DEFAULT_CONFIG = {
|
7
|
+
:api_file_path => "public/",
|
8
|
+
:api_file_name => "api-docs.json",
|
9
|
+
:base_path => "/",
|
10
|
+
:clean_directory => false,
|
11
|
+
:formatting => :pretty
|
12
|
+
}
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
def set_real_methods
|
17
|
+
# replace impotent methods with live ones
|
18
|
+
Config.base_api_controllers.each do |controller|
|
19
|
+
controller.send(:include, Methods)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def write_docs(apis = nil)
|
24
|
+
results = generate_docs(apis)
|
25
|
+
results.each{|api_version, result| write_doc(result) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def write_doc(result)
|
29
|
+
settings = result[:settings]
|
30
|
+
config = result[:config]
|
31
|
+
create_output_paths(settings[:api_file_path])
|
32
|
+
clean_output_paths(settings[:api_file_path]) if config[:clean_directory] || false
|
33
|
+
root = result[:root]
|
34
|
+
resources = root.delete 'resources'
|
35
|
+
root.merge!(config[:attributes] || {}) # merge custom user attributes like info
|
36
|
+
# write the api-docs file
|
37
|
+
write_to_file("#{settings[:api_file_path]}/#{config[:api_file_name]}", root, config)
|
38
|
+
# write the individual resource files
|
39
|
+
resources.each do |resource|
|
40
|
+
resource_file_path = resource.delete 'resourceFilePath'
|
41
|
+
write_to_file(File.join(settings[:api_file_path], "#{resource_file_path}.json"), resource, config)
|
42
|
+
end
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
def generate_docs(apis=nil)
|
47
|
+
apis ||= Config.registered_apis
|
48
|
+
results = {}
|
49
|
+
set_real_methods
|
50
|
+
|
51
|
+
apis[DEFAULT_VER] = DEFAULT_CONFIG if apis.empty?
|
52
|
+
|
53
|
+
apis.each do |api_version, config|
|
54
|
+
settings = get_settings(api_version, config)
|
55
|
+
config.reverse_merge!(DEFAULT_CONFIG)
|
56
|
+
results[api_version] = generate_doc(api_version, settings, config)
|
57
|
+
results[api_version][:settings] = settings
|
58
|
+
results[api_version][:config] = config
|
59
|
+
end
|
60
|
+
results
|
61
|
+
end
|
62
|
+
|
63
|
+
def generate_doc(api_version, settings, config)
|
64
|
+
root = {
|
65
|
+
"swagger" => "2.0",
|
66
|
+
# "info" => settings[:info],
|
67
|
+
"host" => settings[:host],
|
68
|
+
"basePath" => settings[:base_path],
|
69
|
+
"schemes" => [
|
70
|
+
"http"
|
71
|
+
],
|
72
|
+
|
73
|
+
:paths => {},
|
74
|
+
}
|
75
|
+
|
76
|
+
results = {:processed => [], :skipped => []}
|
77
|
+
resources = []
|
78
|
+
|
79
|
+
get_route_paths(settings[:controller_base_path]).each do |path|
|
80
|
+
ret = process_path(path, root, config, settings)
|
81
|
+
results[ret[:action]] << ret
|
82
|
+
|
83
|
+
if ret[:action] == :processed
|
84
|
+
resources << generate_resource(ret[:path], ret[:apis], ret[:models], settings, root, config, ret[:klass].swagger_config)
|
85
|
+
debased_path = get_debased_path(ret[:path], settings[:controller_base_path])
|
86
|
+
|
87
|
+
ret[:apis] && ret[:apis].each do |rea|
|
88
|
+
name = rea[:path].gsub('.json', '')
|
89
|
+
name += '/' if root[:paths].has_key?(name)
|
90
|
+
|
91
|
+
api_content = rea[:operations][0]
|
92
|
+
description = ret[:klass].swagger_config[:description]
|
93
|
+
|
94
|
+
resource_api = {
|
95
|
+
# path: "/#{Config.transform_path(trim_leading_slash(debased_path), api_version)}.{format}",
|
96
|
+
"#{name}" => {
|
97
|
+
"#{api_content[:method]}" => {
|
98
|
+
"summary" => api_content[:summary],
|
99
|
+
"description" => description,
|
100
|
+
"parameters" => api_content[:parameters].map{|pr| {
|
101
|
+
"name" => pr[:name],
|
102
|
+
"in" => pr[:param_type],
|
103
|
+
"required" => pr[:required] ? true : false,
|
104
|
+
"description" => pr[:description],
|
105
|
+
"type" => pr[:type]
|
106
|
+
}
|
107
|
+
},
|
108
|
+
"responses" => {
|
109
|
+
"200" => {
|
110
|
+
"description" => description,
|
111
|
+
"schema" => {
|
112
|
+
# "type": "array",
|
113
|
+
# "items": {
|
114
|
+
# "properties": {
|
115
|
+
# "firstName": {
|
116
|
+
# "type": "string"
|
117
|
+
# },
|
118
|
+
# "lastName": {
|
119
|
+
# "type": "string"
|
120
|
+
# },
|
121
|
+
# "username": {
|
122
|
+
# "type": "string"
|
123
|
+
# }
|
124
|
+
# }
|
125
|
+
# }
|
126
|
+
}
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}.as_json
|
132
|
+
# root[:apis] << resource_api
|
133
|
+
root[:paths].merge!(resource_api)
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
root['resources'] = resources
|
139
|
+
results[:root] = root
|
140
|
+
results
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def transform_spec_to_api_path(spec, controller_base_path, extension)
|
146
|
+
api_path = spec.to_s.dup
|
147
|
+
api_path.gsub!('(.:format)', extension ? ".#{extension}" : '')
|
148
|
+
api_path.gsub!(/:(\w+)/, '{\1}')
|
149
|
+
api_path.gsub!(controller_base_path, '')
|
150
|
+
"/" + trim_slashes(api_path)
|
151
|
+
end
|
152
|
+
|
153
|
+
def camelize_keys_deep!(h)
|
154
|
+
h.keys.each do |k|
|
155
|
+
ks = k.to_s.camelize(:lower)
|
156
|
+
h[ks] = h.delete k
|
157
|
+
camelize_keys_deep! h[ks] if h[ks].kind_of? Hash
|
158
|
+
if h[ks].kind_of? Array
|
159
|
+
h[ks].each do |a|
|
160
|
+
next unless a.kind_of? Hash
|
161
|
+
camelize_keys_deep! a
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def trim_leading_slash(str)
|
168
|
+
return str if !str
|
169
|
+
str.gsub(/\A\/+/, '')
|
170
|
+
end
|
171
|
+
|
172
|
+
def trim_trailing_slash(str)
|
173
|
+
return str if !str
|
174
|
+
str.gsub(/\/+\z/, '')
|
175
|
+
end
|
176
|
+
|
177
|
+
def trim_slashes(str)
|
178
|
+
trim_leading_slash(trim_trailing_slash(str))
|
179
|
+
end
|
180
|
+
|
181
|
+
def get_debased_path(path, controller_base_path)
|
182
|
+
path.gsub("#{controller_base_path}", "")
|
183
|
+
end
|
184
|
+
|
185
|
+
def process_path(path, root, config, settings)
|
186
|
+
return {action: :skipped, reason: :empty_path} if path.empty?
|
187
|
+
klass = Config.log_exception { "#{path.to_s.camelize}Controller".constantize } rescue nil
|
188
|
+
return {action: :skipped, path: path, reason: :klass_not_present} if !klass
|
189
|
+
return {action: :skipped, path: path, reason: :not_swagger_resource} if !klass.methods.include?(:swagger_config) or !klass.swagger_config[:controller]
|
190
|
+
return {action: :skipped, path: path, reason: :not_kind_of_parent_controller} if config[:parent_controller] && !(klass < config[:parent_controller])
|
191
|
+
apis, models, defined_nicknames = [], {}, []
|
192
|
+
routes.select{|i| i.defaults[:controller] == path}.each do |route|
|
193
|
+
unless nickname_defined?(defined_nicknames, path, route) # only add once for each route once e.g. PATCH, PUT
|
194
|
+
ret = get_route_path_apis(path, route, klass, settings, config)
|
195
|
+
apis = apis + ret[:apis]
|
196
|
+
models.merge!(ret[:models])
|
197
|
+
defined_nicknames << ret[:nickname] if ret[:nickname].present?
|
198
|
+
end
|
199
|
+
end
|
200
|
+
{action: :processed, path: path, apis: apis, models: models, klass: klass}
|
201
|
+
end
|
202
|
+
|
203
|
+
def route_verbs(route)
|
204
|
+
if defined?(route.verb.source) then route.verb.source.to_s.delete('$'+'^').split('|') else [route.verb] end.collect{|verb| verb.downcase.to_sym}
|
205
|
+
end
|
206
|
+
|
207
|
+
def path_route_nickname(path, route)
|
208
|
+
action = route.defaults[:action]
|
209
|
+
"#{path.camelize}##{action}"
|
210
|
+
end
|
211
|
+
|
212
|
+
def nickname_defined?(defined_nicknames, path, route)
|
213
|
+
target_nickname = path_route_nickname(path, route)
|
214
|
+
defined_nicknames.each{|nickname| return true if nickname == target_nickname }
|
215
|
+
false
|
216
|
+
end
|
217
|
+
|
218
|
+
def generate_resource(path, apis, models, settings, root, config, swagger_config)
|
219
|
+
metadata = ApiDeclarationFileMetadata.new(
|
220
|
+
root["apiVersion"], path, root["basePath"],
|
221
|
+
settings[:controller_base_path],
|
222
|
+
camelize_model_properties: config.fetch(:camelize_model_properties, true),
|
223
|
+
swagger_version: root["swaggerVersion"],
|
224
|
+
authorizations: root[:authorizations],
|
225
|
+
resource_path: swagger_config[:resource_path]
|
226
|
+
)
|
227
|
+
declaration = ApiDeclarationFile.new(metadata, apis, models)
|
228
|
+
declaration.generate_resource
|
229
|
+
end
|
230
|
+
|
231
|
+
def routes
|
232
|
+
Config.base_applications.map{|app| app.routes.routes.to_a }.flatten
|
233
|
+
end
|
234
|
+
|
235
|
+
def get_route_path_apis(path, route, klass, settings, config)
|
236
|
+
models, apis = {}, []
|
237
|
+
action = route.defaults[:action]
|
238
|
+
verbs = route_verbs(route)
|
239
|
+
return {apis: apis, models: models, nickname: nil} if !operation = klass.swagger_actions[action.to_sym]
|
240
|
+
operation = Hash[operation.map {|k, v| [k.to_s.gsub("@","").to_sym, v.respond_to?(:deep_dup) ? v.deep_dup : v.dup] }] # rename :@instance hash keys
|
241
|
+
nickname = operation[:nickname] = path_route_nickname(path, route)
|
242
|
+
|
243
|
+
route_path = if defined?(route.path.spec) then route.path.spec else route.path end
|
244
|
+
api_path = transform_spec_to_api_path(route_path, settings[:controller_base_path], config[:api_extension_type])
|
245
|
+
operation[:parameters] = filter_path_params(api_path, operation[:parameters]) if operation[:parameters]
|
246
|
+
operations = verbs.collect{|verb|
|
247
|
+
op = operation.dup
|
248
|
+
op[:method] = verb
|
249
|
+
op
|
250
|
+
}
|
251
|
+
apis << {:path => api_path, :operations => operations}
|
252
|
+
models = get_klass_models(klass)
|
253
|
+
|
254
|
+
{apis: apis, models: models, nickname: nickname}
|
255
|
+
end
|
256
|
+
|
257
|
+
def get_klass_models(klass)
|
258
|
+
models = {}
|
259
|
+
# Add any declared models to the root of the resource.
|
260
|
+
klass.swagger_models.each do |model_name, model|
|
261
|
+
formatted_model = {
|
262
|
+
id: model[:id],
|
263
|
+
required: model[:required],
|
264
|
+
properties: model[:properties],
|
265
|
+
}
|
266
|
+
formatted_model[:description] = model[:description] if model[:description]
|
267
|
+
models[model[:id]] = formatted_model
|
268
|
+
end
|
269
|
+
models
|
270
|
+
end
|
271
|
+
|
272
|
+
def get_settings(api_version, config)
|
273
|
+
base_path = trim_trailing_slash(config[:base_path] || "")
|
274
|
+
controller_base_path = trim_leading_slash(config[:controller_base_path] || "")
|
275
|
+
base_path += "/#{controller_base_path}" unless controller_base_path.empty?
|
276
|
+
api_file_path = config[:api_file_path]
|
277
|
+
authorizations = config[:authorizations]
|
278
|
+
|
279
|
+
# new version
|
280
|
+
host = config[:host]
|
281
|
+
info = config[:attributes][:info]
|
282
|
+
settings = {
|
283
|
+
base_path: base_path,
|
284
|
+
controller_base_path: controller_base_path,
|
285
|
+
api_file_path: api_file_path,
|
286
|
+
authorizations: authorizations,
|
287
|
+
|
288
|
+
info: info,
|
289
|
+
host: host
|
290
|
+
}.freeze
|
291
|
+
end
|
292
|
+
|
293
|
+
def get_route_paths(controller_base_path)
|
294
|
+
paths = routes.map{|i| "#{i.defaults[:controller]}" }
|
295
|
+
paths.uniq.select{|i| i.start_with?(controller_base_path)}
|
296
|
+
end
|
297
|
+
|
298
|
+
def create_output_paths(api_file_path)
|
299
|
+
FileUtils.mkdir_p(api_file_path) # recursively create out output path
|
300
|
+
end
|
301
|
+
|
302
|
+
def clean_output_paths(api_file_path)
|
303
|
+
Dir.foreach(api_file_path) do |f|
|
304
|
+
fn = File.join(api_file_path, f)
|
305
|
+
File.delete(fn) if !File.directory?(fn) and File.extname(fn) == '.json'
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def write_to_file(path, structure, config={})
|
310
|
+
content = case config[:formatting]
|
311
|
+
when :pretty; JSON.pretty_generate structure
|
312
|
+
else; structure.to_json
|
313
|
+
end
|
314
|
+
FileUtils.mkdir_p File.dirname(path)
|
315
|
+
File.open(path, 'w') { |file| file.write content }
|
316
|
+
end
|
317
|
+
|
318
|
+
def filter_path_params(path, params)
|
319
|
+
params.reject do |param|
|
320
|
+
param_as_variable = "{#{param[:name]}}"
|
321
|
+
param[:param_type] == :path && !path.include?(param_as_variable)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|