betterdocs 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84f61a235deb0348813dd4ccfc102e41e08ce374b3bcc7d48991935d08f1ce17
4
- data.tar.gz: dee9b3cfb105cea04192377d5f1b11306aee02da2165bc85c78eeaa2d239089c
3
+ metadata.gz: 7324105b58e4926bdbcec0e372efda88e523c1c31fb4f0181e9d4cebc94b135c
4
+ data.tar.gz: 6926a2ea9814cae5e763113badf138104f2fd9ae250c57e9cc8a16fb6eb18d43
5
5
  SHA512:
6
- metadata.gz: ca0afd3ada7b6852b0dda51e73457f37144b0c851fba52da52177ab81f76d22afe9f322b17f9e5a1d5fb965c7c0c4d0439a708fc112dd18de474880a2b97c711
7
- data.tar.gz: c095d79714edc2c0cc66eae7bd7c76b4f6791d989f20826a0081e6c7661d928744dfd260d4be03884a6955bcfbffcbe1639fc52b02993b952e0f06c75b9b5117
6
+ metadata.gz: ccaab04fbb9dfc351d79b9b722d1430c1a8afc5007f3f8bfd68d2d7d1c06ab4d26cf843e745750d1eb805b3d83122fb6b48597a8c1a3bc127fee5bc95e43443e
7
+ data.tar.gz: 67968de292f5c9e0d17b0d2669d72ca75f1e6d709ea219de4f36beab3b99e14452dfa626ff4cb0fe6222419ba99e19f3be4f4765242dbd8d9f0622839d150cf9
data/.gitignore CHANGED
@@ -3,6 +3,7 @@
3
3
  .DS_Store
4
4
  .bundle
5
5
  .byebug_history
6
+ .history
6
7
  .ruby-version
7
8
  .rvmrc
8
9
  .utilsrc
@@ -0,0 +1,6 @@
1
+ {
2
+ "cSpell.words": [
3
+ "Betterdocs",
4
+ "infobar"
5
+ ]
6
+ }
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ GemHadar do
12
12
  test_dir 'spec'
13
13
  ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', 'coverage', '.rvmrc',
14
14
  '.ruby-version', '.AppleDouble', 'tags', '.DS_Store', '.utilsrc',
15
- '.bundle', '.byebug_history', 'errors.lst', '.yardoc'
15
+ '.bundle', '.byebug_history', 'errors.lst', '.yardoc', '.history'
16
16
  readme 'README.md'
17
17
  title "#{name.camelize}"
18
18
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.1
1
+ 0.8.0
data/betterdocs.gemspec CHANGED
@@ -1,18 +1,18 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: betterdocs 0.7.1 ruby lib
2
+ # stub: betterdocs 0.8.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "betterdocs".freeze
6
- s.version = "0.7.1"
6
+ s.version = "0.8.0"
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
10
10
  s.authors = ["betterplace Developers".freeze]
11
- s.date = "2021-06-01"
11
+ s.date = "2021-07-07"
12
12
  s.description = "This library provides tools to generate API documention for a web site's REST-ful JSON API.".freeze
13
13
  s.email = "developers@betterplace.org".freeze
14
- s.extra_rdoc_files = ["README.md".freeze, "lib/betterdocs.rb".freeze, "lib/betterdocs/controller_collector.rb".freeze, "lib/betterdocs/dsl.rb".freeze, "lib/betterdocs/dsl/common.rb".freeze, "lib/betterdocs/dsl/controller.rb".freeze, "lib/betterdocs/dsl/controller/action.rb".freeze, "lib/betterdocs/dsl/controller/action/param.rb".freeze, "lib/betterdocs/dsl/controller/action/response.rb".freeze, "lib/betterdocs/dsl/controller/controller.rb".freeze, "lib/betterdocs/dsl/controller/controller_base.rb".freeze, "lib/betterdocs/dsl/json_params.rb".freeze, "lib/betterdocs/dsl/json_params/param.rb".freeze, "lib/betterdocs/dsl/json_type_mapper.rb".freeze, "lib/betterdocs/dsl/naming.rb".freeze, "lib/betterdocs/dsl/representer.rb".freeze, "lib/betterdocs/dsl/result.rb".freeze, "lib/betterdocs/dsl/result/collection_property.rb".freeze, "lib/betterdocs/dsl/result/link.rb".freeze, "lib/betterdocs/dsl/result/property.rb".freeze, "lib/betterdocs/generator/config_shortcuts.rb".freeze, "lib/betterdocs/generator/markdown.rb".freeze, "lib/betterdocs/global.rb".freeze, "lib/betterdocs/json_params_representer.rb".freeze, "lib/betterdocs/json_params_representer_collector.rb".freeze, "lib/betterdocs/json_time_with_zone.rb".freeze, "lib/betterdocs/mix_into_controller.rb".freeze, "lib/betterdocs/railtie.rb".freeze, "lib/betterdocs/rake_tasks.rb".freeze, "lib/betterdocs/representer.rb".freeze, "lib/betterdocs/result_representer.rb".freeze, "lib/betterdocs/result_representer_collector.rb".freeze, "lib/betterdocs/sanitizer.rb".freeze, "lib/betterdocs/section.rb".freeze, "lib/betterdocs/version.rb".freeze]
15
- s.files = [".codeclimate.yml".freeze, ".gitignore".freeze, ".rspec".freeze, ".semaphore/semaphore.yml".freeze, ".tool-versions".freeze, "COPYING".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "betterdocs.gemspec".freeze, "lib/betterdocs.rb".freeze, "lib/betterdocs/controller_collector.rb".freeze, "lib/betterdocs/dsl.rb".freeze, "lib/betterdocs/dsl/common.rb".freeze, "lib/betterdocs/dsl/controller.rb".freeze, "lib/betterdocs/dsl/controller/action.rb".freeze, "lib/betterdocs/dsl/controller/action/param.rb".freeze, "lib/betterdocs/dsl/controller/action/response.rb".freeze, "lib/betterdocs/dsl/controller/controller.rb".freeze, "lib/betterdocs/dsl/controller/controller_base.rb".freeze, "lib/betterdocs/dsl/json_params.rb".freeze, "lib/betterdocs/dsl/json_params/param.rb".freeze, "lib/betterdocs/dsl/json_type_mapper.rb".freeze, "lib/betterdocs/dsl/naming.rb".freeze, "lib/betterdocs/dsl/representer.rb".freeze, "lib/betterdocs/dsl/result.rb".freeze, "lib/betterdocs/dsl/result/collection_property.rb".freeze, "lib/betterdocs/dsl/result/link.rb".freeze, "lib/betterdocs/dsl/result/property.rb".freeze, "lib/betterdocs/generator/config_shortcuts.rb".freeze, "lib/betterdocs/generator/markdown.rb".freeze, "lib/betterdocs/generator/markdown/templates/README.md.erb".freeze, "lib/betterdocs/generator/markdown/templates/section.md.erb".freeze, "lib/betterdocs/global.rb".freeze, "lib/betterdocs/json_params_representer.rb".freeze, "lib/betterdocs/json_params_representer_collector.rb".freeze, "lib/betterdocs/json_time_with_zone.rb".freeze, "lib/betterdocs/mix_into_controller.rb".freeze, "lib/betterdocs/railtie.rb".freeze, "lib/betterdocs/rake_tasks.rb".freeze, "lib/betterdocs/representer.rb".freeze, "lib/betterdocs/result_representer.rb".freeze, "lib/betterdocs/result_representer_collector.rb".freeze, "lib/betterdocs/sanitizer.rb".freeze, "lib/betterdocs/section.rb".freeze, "lib/betterdocs/tasks/doc.rake".freeze, "lib/betterdocs/version.rb".freeze, "spec/assets/app/assets/images/logos/logo.png".freeze, "spec/assets/app/controllers/api/foos_controller.rb".freeze, "spec/assets/app/views/api_v4/documentation/assets/CHANGELOG.md".freeze, "spec/assets/config/betterdocs.yml".freeze, "spec/betterdocs/dsl/controller/action/param_spec.rb".freeze, "spec/betterdocs/dsl/controller/action/response_spec.rb".freeze, "spec/betterdocs/dsl/json_type_mapper_spec.rb".freeze, "spec/betterdocs/dsl/result/collection_property_spec.rb".freeze, "spec/betterdocs/dsl/result/link_spec.rb".freeze, "spec/betterdocs/dsl/result/property_spec.rb".freeze, "spec/betterdocs/generator/markdown_spec.rb".freeze, "spec/betterdocs/global_spec.rb".freeze, "spec/betterdocs/json_params_representer_spec.rb".freeze, "spec/betterdocs/result_representer_spec.rb".freeze, "spec/betterdocs/sanitizer_spec.rb".freeze, "spec/controller_dsl_spec.rb".freeze, "spec/result_representer_dsl_spec.rb".freeze, "spec/spec_helper.rb".freeze]
14
+ s.extra_rdoc_files = ["README.md".freeze, "lib/betterdocs.rb".freeze, "lib/betterdocs/controller_collector.rb".freeze, "lib/betterdocs/dsl.rb".freeze, "lib/betterdocs/dsl/common.rb".freeze, "lib/betterdocs/dsl/controller.rb".freeze, "lib/betterdocs/dsl/controller/action.rb".freeze, "lib/betterdocs/dsl/controller/action/param.rb".freeze, "lib/betterdocs/dsl/controller/action/response.rb".freeze, "lib/betterdocs/dsl/controller/controller.rb".freeze, "lib/betterdocs/dsl/controller/controller_base.rb".freeze, "lib/betterdocs/dsl/json_params.rb".freeze, "lib/betterdocs/dsl/json_params/param.rb".freeze, "lib/betterdocs/dsl/json_type_mapper.rb".freeze, "lib/betterdocs/dsl/naming.rb".freeze, "lib/betterdocs/dsl/representer.rb".freeze, "lib/betterdocs/dsl/result.rb".freeze, "lib/betterdocs/dsl/result/collection_property.rb".freeze, "lib/betterdocs/dsl/result/link.rb".freeze, "lib/betterdocs/dsl/result/property.rb".freeze, "lib/betterdocs/generator/config_shortcuts.rb".freeze, "lib/betterdocs/generator/markdown.rb".freeze, "lib/betterdocs/generator/swagger.rb".freeze, "lib/betterdocs/global.rb".freeze, "lib/betterdocs/json_params_representer.rb".freeze, "lib/betterdocs/json_params_representer_collector.rb".freeze, "lib/betterdocs/json_time_with_zone.rb".freeze, "lib/betterdocs/mix_into_controller.rb".freeze, "lib/betterdocs/railtie.rb".freeze, "lib/betterdocs/rake_tasks.rb".freeze, "lib/betterdocs/representer.rb".freeze, "lib/betterdocs/result_representer.rb".freeze, "lib/betterdocs/result_representer_collector.rb".freeze, "lib/betterdocs/sanitizer.rb".freeze, "lib/betterdocs/section.rb".freeze, "lib/betterdocs/version.rb".freeze]
15
+ s.files = [".codeclimate.yml".freeze, ".gitignore".freeze, ".rspec".freeze, ".semaphore/semaphore.yml".freeze, ".tool-versions".freeze, ".vscode/settings.json".freeze, "COPYING".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "betterdocs.gemspec".freeze, "lib/betterdocs.rb".freeze, "lib/betterdocs/controller_collector.rb".freeze, "lib/betterdocs/dsl.rb".freeze, "lib/betterdocs/dsl/common.rb".freeze, "lib/betterdocs/dsl/controller.rb".freeze, "lib/betterdocs/dsl/controller/action.rb".freeze, "lib/betterdocs/dsl/controller/action/param.rb".freeze, "lib/betterdocs/dsl/controller/action/response.rb".freeze, "lib/betterdocs/dsl/controller/controller.rb".freeze, "lib/betterdocs/dsl/controller/controller_base.rb".freeze, "lib/betterdocs/dsl/json_params.rb".freeze, "lib/betterdocs/dsl/json_params/param.rb".freeze, "lib/betterdocs/dsl/json_type_mapper.rb".freeze, "lib/betterdocs/dsl/naming.rb".freeze, "lib/betterdocs/dsl/representer.rb".freeze, "lib/betterdocs/dsl/result.rb".freeze, "lib/betterdocs/dsl/result/collection_property.rb".freeze, "lib/betterdocs/dsl/result/link.rb".freeze, "lib/betterdocs/dsl/result/property.rb".freeze, "lib/betterdocs/generator/config_shortcuts.rb".freeze, "lib/betterdocs/generator/markdown.rb".freeze, "lib/betterdocs/generator/markdown/templates/README.md.erb".freeze, "lib/betterdocs/generator/markdown/templates/section.md.erb".freeze, "lib/betterdocs/generator/swagger.rb".freeze, "lib/betterdocs/generator/swagger_static/index.html".freeze, "lib/betterdocs/global.rb".freeze, "lib/betterdocs/json_params_representer.rb".freeze, "lib/betterdocs/json_params_representer_collector.rb".freeze, "lib/betterdocs/json_time_with_zone.rb".freeze, "lib/betterdocs/mix_into_controller.rb".freeze, "lib/betterdocs/railtie.rb".freeze, "lib/betterdocs/rake_tasks.rb".freeze, "lib/betterdocs/representer.rb".freeze, "lib/betterdocs/result_representer.rb".freeze, "lib/betterdocs/result_representer_collector.rb".freeze, "lib/betterdocs/sanitizer.rb".freeze, "lib/betterdocs/section.rb".freeze, "lib/betterdocs/tasks/doc.rake".freeze, "lib/betterdocs/version.rb".freeze, "spec/assets/app/assets/images/logos/logo.png".freeze, "spec/assets/app/controllers/api/foos_controller.rb".freeze, "spec/assets/app/views/api_v4/documentation/assets/CHANGELOG.md".freeze, "spec/assets/config/betterdocs.yml".freeze, "spec/betterdocs/dsl/controller/action/param_spec.rb".freeze, "spec/betterdocs/dsl/controller/action/response_spec.rb".freeze, "spec/betterdocs/dsl/json_type_mapper_spec.rb".freeze, "spec/betterdocs/dsl/result/collection_property_spec.rb".freeze, "spec/betterdocs/dsl/result/link_spec.rb".freeze, "spec/betterdocs/dsl/result/property_spec.rb".freeze, "spec/betterdocs/generator/markdown_spec.rb".freeze, "spec/betterdocs/global_spec.rb".freeze, "spec/betterdocs/json_params_representer_spec.rb".freeze, "spec/betterdocs/result_representer_spec.rb".freeze, "spec/betterdocs/sanitizer_spec.rb".freeze, "spec/controller_dsl_spec.rb".freeze, "spec/result_representer_dsl_spec.rb".freeze, "spec/spec_helper.rb".freeze]
16
16
  s.homepage = "http://github.com/betterplace/betterdocs".freeze
17
17
  s.rdoc_options = ["--title".freeze, "Betterdocs".freeze, "--main".freeze, "README.md".freeze]
18
18
  s.rubygems_version = "3.2.15".freeze
data/lib/betterdocs.rb CHANGED
@@ -26,6 +26,7 @@ require 'betterdocs/section'
26
26
  require 'betterdocs/mix_into_controller'
27
27
  require 'betterdocs/generator/config_shortcuts'
28
28
  require 'betterdocs/generator/markdown'
29
+ require 'betterdocs/generator/swagger'
29
30
  require 'betterdocs/rake_tasks'
30
31
  require 'betterdocs/json_time_with_zone'
31
32
  defined? Rails and require 'betterdocs/railtie'
@@ -1,3 +1,5 @@
1
+ require 'active_support/time_with_zone'
2
+
1
3
  require 'betterdocs/dsl/naming'
2
4
 
3
5
  class Betterdocs::Dsl::JsonParams::Param < Betterdocs::Dsl::Representer
@@ -1,3 +1,5 @@
1
+ require 'active_support/time_with_zone'
2
+
1
3
  require 'betterdocs/dsl/representer'
2
4
  require 'betterdocs/dsl/common'
3
5
  require 'betterdocs/dsl/naming'
@@ -0,0 +1,405 @@
1
+ require 'infobar'
2
+ require 'fileutils'
3
+ require 'term/ansicolor'
4
+ require 'json'
5
+
6
+ module Betterdocs
7
+ module Generator
8
+ class Swagger
9
+ include ::Betterdocs::Generator::ConfigShortcuts
10
+ include Term::ANSIColor
11
+ include FileUtils
12
+
13
+ def initialize(only: nil)
14
+ only and @only = Regexp.new(only)
15
+ end
16
+
17
+ def generate
18
+ if dir = config.swagger_output_directory.full?
19
+ generate_to dir
20
+ else
21
+ raise 'Specify an output_directory in your configuration!'
22
+ end
23
+ end
24
+
25
+ def generate_to(dirname)
26
+ map = { openapi: '3.0.2', paths: {}, components: { schemas: {} } }
27
+ add_error_envelope_schema(map[:components][:schemas])
28
+ configure_for_creation
29
+ prepare_dir(dirname)
30
+ add_global_info(map)
31
+ create_sections(map)
32
+ create_swagger(map, dirname)
33
+ create_assets
34
+ self
35
+ end
36
+
37
+ def configure_for_creation
38
+ infobar.puts color(40, "Setting asset_host to #{Betterdocs::Global.asset_host.inspect}.")
39
+ Betterdocs.rails.configuration.action_controller.asset_host = Betterdocs::Global.asset_host
40
+ options = {
41
+ host: Betterdocs::Global.api_host,
42
+ protocol: Betterdocs::Global.api_protocol,
43
+ }
44
+ infobar.puts color(40, "Setting default_url_options to #{options.inspect}.")
45
+ Betterdocs.rails.application.routes.default_url_options = options
46
+ self
47
+ end
48
+
49
+ def add_global_info(map)
50
+ slugs = get_request_url_slugs(sections.values.first.first.request)
51
+ map[:info] = { title: project_name, version: slugs[:ver] }
52
+ map[:servers] =
53
+ [{ url: "#{slugs[:protocol]}://#{[slugs[:host], slugs[:lang], "api_#{slugs[:ver]}"].join('/')}" }]
54
+ end
55
+
56
+ def create_sections(map)
57
+ Infobar(total: sections.size)
58
+ sections.values.each do |section|
59
+ infobar.progress(
60
+ message: " Section #{section.name.to_s.inspect} %c/%t in %te ETA %e @%E ",
61
+ force: true,
62
+ )
63
+ @only =~ section.name or next if @only
64
+
65
+ map = get_section_data(map, section)
66
+ end
67
+ infobar.finish message: ' %t sections created in %te, completed @%E '
68
+ infobar.newline
69
+ self
70
+ end
71
+
72
+ def create_assets
73
+ Infobar(total: config.swagger_assets.size)
74
+ config.each_swagger_asset do |src, dst|
75
+ infobar.progress(
76
+ message: " Asset #{File.basename(src).inspect} %c/%t in %te ETA %e @%E ",
77
+ force: true,
78
+ )
79
+ mkdir_p File.dirname(dst)
80
+ cp Betterdocs.rails.root.join(src), dst
81
+ end
82
+ infobar.finish message: ' %t assets created in %te, completed @%E '
83
+ infobar.newline
84
+ self
85
+ end
86
+
87
+ def create_swagger(map, dirname)
88
+ name = 'swagger.json'
89
+ cd dirname do
90
+ infobar.puts color(40, 'Creating swagger.')
91
+ json = JSON.pretty_generate(map)
92
+ render_to(name, json)
93
+ end
94
+ self
95
+ end
96
+
97
+ def fail_while_rendering(exception)
98
+ message = color(
99
+ 231,
100
+ on_color(124, " *** ERROR #{exception.class}: #{exception.message.inspect} ***")
101
+ )
102
+ infobar.puts message
103
+ exit 1
104
+ end
105
+
106
+ def add_param(hash, name, param_in, required = false, type = nil, description = '', schema = nil)
107
+ hash[:parameters] ||= []
108
+ p = { in: param_in, required: required, description: description, name: name }
109
+ p[:schema] = { type: type } unless type.nil?
110
+ p[:schema] = schema unless schema.nil?
111
+ hash[:parameters].push(p)
112
+ hash
113
+ end
114
+
115
+ def add_default_list_params(hash)
116
+ p = add_param(hash, 'page', 'query', false, 'integer')
117
+ add_param(p, 'per_page', 'query', false, 'integer')
118
+ end
119
+
120
+ def get_list_response_wrapper_schema
121
+ {
122
+ type: 'object',
123
+ properties: {
124
+ total_entries: { type: 'integer' },
125
+ offset: { type: 'integer' },
126
+ total_pages: { type: 'integer' },
127
+ current_page: { type: 'integer' },
128
+ per_page: { type: 'integer' },
129
+ },
130
+ required: %w[total_entries offset current_page per_page total_pages data],
131
+ }
132
+ end
133
+
134
+ def get_param_value_type(value)
135
+ case value
136
+ when String then 'string'
137
+ when Integer then 'integer'
138
+ when TrueClass, FalseClass then 'boolean'
139
+ when Float then 'number'
140
+ when Array then 'array'
141
+ else 'object'
142
+ end
143
+ end
144
+
145
+ def get_path_param_slug_type(slug)
146
+ return 'integer' if /\A[0-9]+\z/.match(slug)
147
+
148
+ 'string'
149
+ end
150
+
151
+ def add_error_envelope_schema(definitions)
152
+ definitions['ErrorEnvelope'] = {
153
+ type: 'object',
154
+ properties: {
155
+ name: { type: 'string' },
156
+ status: { type: 'string' },
157
+ status_code: { type: 'integer' },
158
+ reason: { type: 'string' },
159
+ backtrace: { type: 'array', items: { type: 'string' } },
160
+ message: { type: 'string' },
161
+ errors: { type: 'object' },
162
+ links: { type: 'array', items: { type: 'string' } },
163
+ },
164
+ required: %w[name status status_code reason backtrace message links],
165
+ }
166
+ end
167
+
168
+ def get_error_envelope_schema_ref
169
+ { schema: get_schema_ref('ErrorEnvelope') }
170
+ end
171
+
172
+ def get_request_url_slugs(request)
173
+ slugs = %r{
174
+ (?<method>GET|POST|PUT|DELETE|OPTIONS)\s+
175
+ (?<protocol>https?)://
176
+ (?<host>[a-z.]+)/
177
+ (?<lang>[a-z]{2})/api_
178
+ (?<ver>v[0-9]+)/
179
+ (?<path>[\w/]+\.json?)
180
+ }x.match(request).named_captures.transform_keys(&:to_sym)
181
+ slugs[:params] = []
182
+ split = slugs[:path].split('/')
183
+ path_cmp = split.map.with_index do |cmp, i|
184
+ unless i.even?
185
+ suffix = cmp['.json'] || ''
186
+ name = "#{split[i - 1]}_id"
187
+ cmp = "{#{name}}#{suffix}"
188
+ param = { name: name, type: get_path_param_slug_type(cmp.gsub('.json', '')) }
189
+ slugs[:params].push(param)
190
+ end
191
+ cmp
192
+ end
193
+ slugs[:method] = slugs[:method].downcase
194
+ slugs[:path] = "/#{path_cmp.join('/')}"
195
+ slugs
196
+ end
197
+
198
+ def get_request_definition(params)
199
+ schema = { type: 'object', properties: {}, required: [] }
200
+ params.each do |param|
201
+ schema[:properties][param.name] = get_schema(param.types, nil, param.description)
202
+ schema[:required].push(param.name) if param.required == true || param.required == 'yes'
203
+ end
204
+ schema
205
+ end
206
+
207
+ def get_name(title)
208
+ /([\w\s]+)/.match(title)[1]
209
+ end
210
+
211
+ def get_type(types)
212
+ type = types
213
+ nullable = false
214
+ if types.instance_of? Array
215
+ case types.length
216
+ when 0
217
+ type = nil
218
+ when 1
219
+ type = types[0]
220
+ else
221
+ types.each do |t|
222
+ if t == 'null'
223
+ nullable = true
224
+ else
225
+ type = t
226
+ end
227
+ end
228
+ end
229
+ end
230
+ unless %w[integer number string boolean array object].include?(type)
231
+ puts "Warning: invalid type #{type || 'nil'}"
232
+ end
233
+ [type, nullable]
234
+ end
235
+
236
+ def get_deprecated_from_description(description)
237
+ description.include?('DEPRECATED')
238
+ end
239
+
240
+ def get_schema(types, sub_cls, description)
241
+ type, nullable = get_type(types)
242
+ deprecated = get_deprecated_from_description(description)
243
+ res = { description: description, type: type, nullable: nullable, deprecated: deprecated }
244
+ case type
245
+ when 'array'
246
+ items = { type: 'string' }
247
+ items = get_schema_ref(sub_cls) if sub_cls
248
+ res[:items] = items
249
+ when 'object'
250
+ res = get_schema_ref(sub_cls) if sub_cls
251
+ res[:description] = description
252
+ res[:deprecated] = deprecated
253
+ end
254
+ res[:nullable] = true if nullable
255
+ res
256
+ end
257
+
258
+ def get_dto_name(representer)
259
+ representer&.name&.demodulize
260
+ end
261
+
262
+ def add_links_definition(definitions, cls, value)
263
+ definition = initialise_definition(definitions, cls)
264
+ definition[:properties][:links] ||= {
265
+ type: 'array',
266
+ nullable: false,
267
+ items: {
268
+ type: 'object',
269
+ properties: {
270
+ rel: { type: 'string', enum: [] },
271
+ href: { type: 'string' },
272
+ },
273
+ required: %w[rel href],
274
+ },
275
+ }
276
+ enum = definition[:properties][:links][:items][:properties][:rel][:enum]
277
+ enum.push(value) unless enum.include?(value)
278
+ end
279
+
280
+ def initialise_definition(definitions, cls)
281
+ definitions[cls] ||= { type: 'object', properties: {} }
282
+ end
283
+
284
+ def add_response_schema(definitions, response)
285
+ return nil unless response.full?
286
+
287
+ subs = {}
288
+ resp_cls = get_dto_name(response.representer)
289
+ (response.full?(:properties) || []).each do |p|
290
+ subs[p.full_name] = p.sub_representer? if p.sub_representer?
291
+ sub_cls = get_dto_name(subs[p.nesting_name])
292
+ cls = sub_cls || resp_cls
293
+ definition = initialise_definition(definitions, cls)
294
+ definition[:properties][p.public_name] = get_schema(p.types, get_dto_name(p.sub_representer?), p.description)
295
+ end
296
+ (response.full?(:links) || []).each do |l|
297
+ sub_cls = get_dto_name(subs[l.nesting_name])
298
+ add_links_definition(definitions, sub_cls || resp_cls, l.public_name)
299
+ end
300
+ get_schema_ref(resp_cls)
301
+ end
302
+
303
+ def get_schema_ref(name)
304
+ {
305
+ "$ref": "#/components/schemas/#{name}",
306
+ }
307
+ end
308
+
309
+ def add_required_to_definitions(definitions)
310
+ definitions.each do |def_key, d|
311
+ req = d[:properties].select { |_k, v| v.key?(:nullable) }.keys
312
+ definitions[def_key][:required] = req unless req.empty?
313
+ end
314
+ end
315
+
316
+ def add_body(definitions, params, action, name)
317
+ if action.json_params.full?
318
+ payload_name = "#{name.titleize.gsub(/\s/, '')}Request"
319
+ definitions[payload_name] = get_request_definition(action.json_params.values)
320
+ params[:requestBody] = wrap_content_object({ schema: get_schema_ref(payload_name) }, "#{name} Request")
321
+ end
322
+ end
323
+
324
+ def add_path_params(params, from_path)
325
+ from_path.each do |param|
326
+ add_param(params, param[:name], 'path', true, param[:type], param[:name])
327
+ end
328
+ end
329
+
330
+ def add_query_params(params, from_query, is_a_list)
331
+ from_query.each do |param|
332
+ add_param(params, param.name, 'query', param.required, get_param_value_type(param.value),
333
+ param.description)
334
+ end
335
+ add_default_list_params(params) if is_a_list
336
+ end
337
+
338
+ def wrap_content_object(object, description)
339
+ { content: { "application/json": object }, description: description }
340
+ end
341
+
342
+ def add_example(obj, name, example)
343
+ obj[:examples] ||= {}
344
+ obj[:examples][name] = { value: example }
345
+ end
346
+
347
+ def get_section_data(map, section)
348
+ section.each do |action|
349
+ name = get_name(action.title)
350
+ p = { description: action.description, summary: name }
351
+ is_a_list = action.response.data.instance_of?(ApiTools::ResultSet)
352
+ slugs = get_request_url_slugs(action.request)
353
+ add_path_params(p, slugs[:params])
354
+ add_query_params(p, action.params.values, is_a_list)
355
+ add_body(map[:components][:schemas], p, action, name)
356
+ item = add_response_schema(map[:components][:schemas], action.response)
357
+ schema = item
358
+ if is_a_list
359
+ list = get_list_response_wrapper_schema
360
+ list[:properties][:data] = { type: 'array', items: item }
361
+ schema = list
362
+ end
363
+ ok = { schema: schema }
364
+ unless action.response.nil?
365
+ add_example(ok, 'example', JSON.parse(JSON.pretty_generate(action.response, quirks_mode: true)))
366
+ end
367
+ p[:responses] =
368
+ { "200": wrap_content_object(ok, 'OK'),
369
+ default: wrap_content_object(get_error_envelope_schema_ref, 'Error') }
370
+ wrapped = {}
371
+ wrapped = map[slugs[:path]] if map.key?(slugs[:path])
372
+ wrapped[slugs[:method]] = p
373
+ map[:paths][slugs[:path]] = wrapped
374
+ end
375
+ add_required_to_definitions(map[:components][:schemas])
376
+ map
377
+ end
378
+
379
+ def render_to(filename, content)
380
+ File.open(filename, 'w') do |output|
381
+ output.write(content)
382
+ end
383
+ self
384
+ rescue StandardError => e
385
+ fail_while_rendering(e)
386
+ end
387
+
388
+ def prepare_dir(dirname)
389
+ dirname.present? or raise ArgumentError,
390
+ "#{dirname.inspect} should be an explicite output dirname"
391
+ begin
392
+ stat = File.stat(dirname)
393
+ if stat.directory?
394
+ rm_rf Dir[dirname.to_s + '/**/*']
395
+ else
396
+ raise ArgumentError, "#{dirname.inspect} is not a directory"
397
+ end
398
+ rescue Errno::ENOENT
399
+ end
400
+ mkdir_p dirname.to_s
401
+ self
402
+ end
403
+ end
404
+ end
405
+ end
@@ -0,0 +1,66 @@
1
+ <!-- HTML for static distribution bundle build -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <title>Swagger UI</title>
7
+
8
+ <link
9
+ rel="stylesheet"
10
+ href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.8.1/swagger-ui.css"
11
+ type="text/css"
12
+ charset="UTF-8"
13
+ />
14
+ <style>
15
+ html {
16
+ box-sizing: border-box;
17
+ overflow: -moz-scrollbars-vertical;
18
+ overflow-y: scroll;
19
+ }
20
+
21
+ *,
22
+ *:before,
23
+ *:after {
24
+ box-sizing: inherit;
25
+ }
26
+
27
+ body {
28
+ margin: 0;
29
+ background: #fafafa;
30
+ }
31
+ </style>
32
+ </head>
33
+
34
+ <body>
35
+ <div id="swagger-ui"></div>
36
+
37
+ <script
38
+ src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.8.1/swagger-ui-bundle.js"
39
+ charset="UTF-8"
40
+ ></script>
41
+ <script
42
+ src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.8.1/swagger-ui-standalone-preset.js"
43
+ charset="UTF-8"
44
+ ></script>
45
+ <script>
46
+ window.onload = function () {
47
+ fetch("./swagger.json")
48
+ .then(resp => resp.json())
49
+ .then(spec => {
50
+ const ui = SwaggerUIBundle({
51
+ spec: spec,
52
+ dom_id: "#swagger-ui",
53
+ deepLinking: true,
54
+ presets: [
55
+ SwaggerUIBundle.presets.apis,
56
+ SwaggerUIStandalonePreset,
57
+ ],
58
+ plugins: [SwaggerUIBundle.plugins.DownloadUrl],
59
+ layout: "BaseLayout",
60
+ });
61
+ window.ui = ui;
62
+ });
63
+ };
64
+ </script>
65
+ </body>
66
+ </html>
@@ -27,7 +27,7 @@ module Betterdocs
27
27
  platform_host host
28
28
  __send__("#{prefix}_host", host)
29
29
  else
30
- [ __send__("#{prefix}_protocol"), __send__("#{prefix}_host") ] * '://'
30
+ [__send__("#{prefix}_protocol"), __send__("#{prefix}_host")].join('://')
31
31
  end
32
32
  end
33
33
 
@@ -41,7 +41,7 @@ module Betterdocs
41
41
 
42
42
  dsl_accessor :api_protocol do platform_protocol end # Protocol the API understands
43
43
 
44
- dsl_accessor :api_host do platform_host end # Hostname of the API (eventuallly with port number)
44
+ dsl_accessor :api_host do platform_host end # Hostname of the API (eventuallly with port number)
45
45
 
46
46
  def api_url(url = nil)
47
47
  handle_url(:api, url)
@@ -55,7 +55,7 @@ module Betterdocs
55
55
  handle_url(:asset, url)
56
56
  end
57
57
 
58
- dsl_accessor :api_default_format, 'json'
58
+ dsl_accessor :api_default_format, 'json'
59
59
 
60
60
  def api_base_url
61
61
  "#{api_protocol}://#{api_host}/#{api_prefix}"
@@ -65,9 +65,11 @@ module Betterdocs
65
65
  { protocol: api_protocol, host: api_host, format: api_default_format }
66
66
  end
67
67
 
68
- dsl_accessor :templates_directory # Template directory, where customised templates live if any exist
68
+ dsl_accessor :templates_directory # Template directory, where customised templates live if any exist
69
69
 
70
- dsl_accessor :output_directory, 'api_docs' # Output directory, where the api docs are created
70
+ dsl_accessor :output_directory, 'api_docs' # Output directory, where the api docs are created
71
+
72
+ dsl_accessor :swagger_output_directory, 'swagger_docs' # Output directory, where the swagger docs are created
71
73
 
72
74
  dsl_accessor :publish_git # URL to the git repo to which the docs are pushed
73
75
 
@@ -89,6 +91,17 @@ module Betterdocs
89
91
  @assets
90
92
  end
91
93
 
94
+ def swagger_assets=(hash)
95
+ @swagger_assets&.clear
96
+ swagger_assets(hash)
97
+ end
98
+
99
+ def swagger_assets(hash = nil)
100
+ @swagger_assets ||= {}
101
+ hash&.each { |path, to| swagger_asset path, to: to }
102
+ @swagger_assets
103
+ end
104
+
92
105
  # Defines an asset for the file at +path+. If +to+ was given it will be
93
106
  # copied to this path (it includes the basename) below
94
107
  # +templates_directory+ in the output, otherwise it will be copied
@@ -100,14 +113,25 @@ module Betterdocs
100
113
  elsif to == :root
101
114
  @assets[path.to_s] = to
102
115
  else
103
- raise ArgumentError, "keyword argument to needs to be a string or :root"
116
+ raise ArgumentError, 'keyword argument to needs to be a string or :root'
117
+ end
118
+ end
119
+
120
+ def swagger_asset(path, to: :root)
121
+ @swagger_assets ||= {}
122
+ if destination = to.ask_and_send(:to_str)
123
+ @swagger_assets[path.to_s] = destination
124
+ elsif to == :root
125
+ @swagger_assets[path.to_s] = to
126
+ else
127
+ raise ArgumentError, 'keyword argument to needs to be a string or :root'
104
128
  end
105
129
  end
106
130
 
107
131
  # Maps the assets original source path to its destination path in the
108
132
  # output by yielding to every asset's source/destination pair.
109
133
  def each_asset
110
- for (path, destination) in assets
134
+ assets.each do |(path, destination)|
111
135
  path = path.to_s
112
136
  if destination == :root
113
137
  yield path, File.join(output_directory.to_s, File.basename(path))
@@ -117,6 +141,17 @@ module Betterdocs
117
141
  end
118
142
  end
119
143
 
144
+ def each_swagger_asset
145
+ swagger_assets.each do |(path, destination)|
146
+ path = path.to_s
147
+ if destination == :root
148
+ yield path, File.join(swagger_output_directory.to_s, File.basename(path))
149
+ else
150
+ yield path, File.join(swagger_output_directory.to_s, destination.to_str)
151
+ end
152
+ end
153
+ end
154
+
120
155
  def configuration_file
121
156
  cc.betterdocs
122
157
  rescue ComplexConfig::ConfigurationFileMissing
@@ -192,7 +227,8 @@ module Betterdocs
192
227
 
193
228
  def url_for(options = {})
194
229
  Betterdocs.rails.application.routes.url_for(
195
- options | Betterdocs::Global.config.api_url_options)
230
+ options | Betterdocs::Global.config.api_url_options
231
+ )
196
232
  end
197
233
  end
198
234
  end
@@ -1,6 +1,6 @@
1
1
  namespace :doc do
2
- desc "Create the API documentation"
3
- task :api => :'doc:api:sandbox' do
2
+ desc 'Create the API documentation'
3
+ task api: :'doc:api:sandbox' do
4
4
  Betterdocs::Global.config do |config|
5
5
  Betterdocs::Generator::Markdown.new(only: ENV['ONLY']).generate
6
6
  cd config.output_directory do
@@ -15,39 +15,46 @@ namespace :doc do
15
15
 
16
16
  namespace :api do
17
17
  desc 'Let database transactions run in a sandboxed environment'
18
- task :sandbox => [:'doc:set_betterdocs_env', :environment] do
19
-
18
+ task sandbox: %i[doc:set_betterdocs_env environment] do
20
19
  ActiveRecord::Base.connection.begin_db_transaction
21
20
  at_exit do
22
- ActiveRecord::Base.connection.rollback_db_transaction
21
+ ActiveRecord::Base.connection.rollback_db_transaction
23
22
  end
24
23
  end
25
24
 
26
- desc "Push the newly created API documentation to the remote git repo"
27
- task :push => :api do
25
+ desc 'Create the API swagger documentation'
26
+ task swagger: :'doc:api:sandbox' do
28
27
  Betterdocs::Global.config do |config|
29
- config.publish_git or fail "Configure a git repo as publish_git to publish to"
28
+ Betterdocs::Generator::Swagger.new(only: ENV['ONLY']).generate
29
+ cd config.swagger_output_directory do
30
+ File.open('.gitignore', 'w') { |ignore| ignore.puts config.ignore }
31
+ end
32
+ end
33
+ end
34
+
35
+ desc 'Push the newly created API documentation to the remote git repo'
36
+ task push: :api do
37
+ Betterdocs::Global.config do |config|
38
+ config.publish_git or raise 'Configure a git repo as publish_git to publish to'
30
39
  cd config.output_directory do
31
- File.directory?('.git') or sh "git init"
32
- sh "git remote rm publish_git || true"
40
+ File.directory?('.git') or sh 'git init'
41
+ sh 'git remote rm publish_git || true'
33
42
  sh "git remote add publish_git #{config.publish_git}"
34
- sh "git add -A"
43
+ sh 'git add -A'
35
44
  sh 'git commit -m "Add some more changes to API documentation" || true'
36
45
  sh 'git push -f publish_git master'
37
46
  end
38
47
  end
39
48
  end
40
49
 
41
- desc "Publish the newly created API documentation"
42
- task :publish => [ :push ]
50
+ desc 'Publish the newly created API documentation'
51
+ task publish: [:push]
43
52
 
44
- desc "Publish and view the newly created API documentation"
45
- task :view => :publish do
53
+ desc 'Publish and view the newly created API documentation'
54
+ task view: :publish do
46
55
  Betterdocs::Global.config do |config|
47
56
  url = config.publish_git
48
- if url !~ /\Ahttps?:/
49
- url.sub!(/.*?([^@]*):/, 'http://\1/')
50
- end
57
+ url.sub!(/.*?([^@]*):/, 'http://\1/') if url !~ /\Ahttps?:/
51
58
  sh "open #{url.inspect}"
52
59
  end
53
60
  end
@@ -1,6 +1,6 @@
1
1
  module Betterdocs
2
2
  # Betterdocs version
3
- VERSION = '0.7.1'
3
+ VERSION = '0.8.0'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: betterdocs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - betterplace Developers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-01 00:00:00.000000000 Z
11
+ date: 2021-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gem_hadar
@@ -190,6 +190,7 @@ extra_rdoc_files:
190
190
  - lib/betterdocs/dsl/result/property.rb
191
191
  - lib/betterdocs/generator/config_shortcuts.rb
192
192
  - lib/betterdocs/generator/markdown.rb
193
+ - lib/betterdocs/generator/swagger.rb
193
194
  - lib/betterdocs/global.rb
194
195
  - lib/betterdocs/json_params_representer.rb
195
196
  - lib/betterdocs/json_params_representer_collector.rb
@@ -209,6 +210,7 @@ files:
209
210
  - ".rspec"
210
211
  - ".semaphore/semaphore.yml"
211
212
  - ".tool-versions"
213
+ - ".vscode/settings.json"
212
214
  - COPYING
213
215
  - Gemfile
214
216
  - LICENSE
@@ -239,6 +241,8 @@ files:
239
241
  - lib/betterdocs/generator/markdown.rb
240
242
  - lib/betterdocs/generator/markdown/templates/README.md.erb
241
243
  - lib/betterdocs/generator/markdown/templates/section.md.erb
244
+ - lib/betterdocs/generator/swagger.rb
245
+ - lib/betterdocs/generator/swagger_static/index.html
242
246
  - lib/betterdocs/global.rb
243
247
  - lib/betterdocs/json_params_representer.rb
244
248
  - lib/betterdocs/json_params_representer_collector.rb