swagger_autogenerate 2.0.0 → 2.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2c5c12b58edcd185c9831efb1c2e2f01053281858e6ec0ae85c149a3f3a2f71
4
- data.tar.gz: 1c6e7a1d15457fd89837315cbfaf5b41631047def4df33a5ba382d6c5382b3c3
3
+ metadata.gz: 82b7c6523f9a6f361c4f78be66068aab528b9707419a11ae61b5264878706ef1
4
+ data.tar.gz: f7671a245cb3a4a89452db82f6d5ba48a1f5b810c1a52d2323cee1a94f76726a
5
5
  SHA512:
6
- metadata.gz: 96261e06a8de505e0513f89c0aec39ca76ddf054412c117c29510310581312f5ca957184250e750f29a9b88674dc7ffa5e3a5abfa43d8f0e537504a078b3fe7f
7
- data.tar.gz: 1f11a7ae9e1551f88fa2e4739f49edd4e3a1c5c7dc11004dc5a79721428622eee7dd05a10bfe7649f2863d4781efb1cf6d15915d2de16d90484774463eb8495c
6
+ metadata.gz: 2e4b21d768fcbd45e6a082f3caf0c437079d535d53c5fa9466aa1c7b70c69e289de2fadc9d440d2c9128cfbcf99dabeb1cf1f5742154ede03d203cc86c3a8cac
7
+ data.tar.gz: 4e0b9ce2a3cc3d9dae32383b8980d4b31e70b0f7611c8e3769e0ef7360da94e42ef060aa62860366698fafdb8bd0dbdf3b2bc475e4ad6d257b5f41a2c202c8eb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ### Improved
4
+ - README documents full Rails + rswag host setup (routes, initializers, SwaggerCombiner, rake/bin)
5
+
6
+ ## [2.0.1] - 2026-09-03
3
7
  ## [2.0.0] - 2026-09-03
4
8
 
5
9
  ### Breaking
data/README.md CHANGED
@@ -2,27 +2,28 @@
2
2
 
3
3
  Generate **OpenAPI / Swagger YAML** from your existing Rails RSpec request (or controller) specs.
4
4
 
5
- Designed to drop into projects that already use **`rswag-api`** and **`rswag-ui`**: run specs → YAML lands under `swagger/` (or `swagger/v1/`) → rswag serves and renders it.
5
+ Designed to drop into projects that use **`rswag-api`** and **`rswag-ui`**:
6
6
 
7
- ## Why
8
-
9
- - Keep docs in sync with real request/response behavior from tests
10
- - No hand-maintained path definitions for happy-path coverage
11
- - Works with any Rails app; rswag-friendly defaults out of the box
7
+ 1. Run specs with `SWAGGER_GENERATE=1` → one YAML fragment per resource under `swagger/v1/`
8
+ 2. Combine fragments into `swagger/v1/swagger.yaml`
9
+ 3. Rswag serves that file at `/api-docs`
12
10
 
13
11
  ## Dependencies
14
12
 
15
13
  - Ruby `>= 2.7`
16
14
  - Rails `>= 5.2`
17
15
  - [rspec-rails](https://github.com/rspec/rspec-rails) in the host app
18
- - Optional but recommended: [`rswag-api`](https://github.com/rswag/rswag) + [`rswag-ui`](https://github.com/rswag/rswag)
16
+ - Recommended: [`rswag-api`](https://github.com/rswag/rswag) + [`rswag-ui`](https://github.com/rswag/rswag)
19
17
 
20
18
  ## Installation
21
19
 
22
- Add to the test group:
23
-
24
20
  ```ruby
21
+ # Gemfile
22
+ gem 'rswag-api'
23
+ gem 'rswag-ui'
24
+
25
25
  group :test do
26
+ gem 'rspec-rails'
26
27
  gem 'swagger_autogenerate'
27
28
  end
28
29
  ```
@@ -31,57 +32,278 @@ end
31
32
  bundle install
32
33
  ```
33
34
 
34
- ### With rswag
35
+ Then add the host-app files below (copy-paste and adjust titles / URLs for your API).
36
+
37
+ ---
35
38
 
36
- Typical Gemfile:
39
+ ## Host app setup (required for rswag UI)
40
+
41
+ ### 1. Mount rswag in `config/routes.rb`
37
42
 
38
43
  ```ruby
39
- gem 'rswag-api'
40
- gem 'rswag-ui'
44
+ mount Rswag::Ui::Engine => "/api-docs"
45
+ mount Rswag::Api::Engine => "/api-docs"
46
+ ```
41
47
 
42
- group :test do
43
- gem 'rspec-rails'
44
- gem 'swagger_autogenerate'
48
+ ### 2. `config/initializers/rswag_api.rb`
49
+
50
+ Serves `swagger/v1/swagger.yaml` at `/api-docs/v1/swagger.yaml`.
51
+
52
+ ```ruby
53
+ # frozen_string_literal: true
54
+
55
+ Rswag::Api.configure do |c|
56
+ # Serves files from swagger/ — so swagger/v1/swagger.yaml → /api-docs/v1/swagger.yaml
57
+ c.openapi_root = Rails.root.join("swagger").to_s
58
+ end
59
+
60
+ Rswag::Ui.configure do |c|
61
+ c.openapi_endpoint "/api-docs/v1/swagger.yaml", "My API V1"
45
62
  end
46
63
  ```
47
64
 
48
- Point rswag at the same folder this gem writes to (default `swagger` or `swagger/v1` when that directory exists).
65
+ ### 3. `config/initializers/swagger_autogenerate.rb`
49
66
 
50
- ## Quick start
67
+ Guard with `defined?` so `rails s` does not crash when the gem is only in the `:test` group.
51
68
 
52
- ### 1. Auto-include (default)
69
+ ```ruby
70
+ # frozen_string_literal: true
53
71
 
54
- In **test**, the gem Railtie includes itself into `ApplicationController` automatically.
72
+ # swagger_autogenerate is only loaded in the test (and optionally development) group.
73
+ # Guard so `rails s` in development does not crash when the gem is not required.
74
+ return unless defined?(SwaggerAutogenerate)
75
+
76
+ SwaggerAutogenerate.configure do |config|
77
+ # Where SWAGGER_GENERATE writes files (rswag default layout)
78
+ config.default_path = "swagger/v1"
55
79
 
56
- You can still include manually if you prefer:
80
+ # OpenAPI info
81
+ config.info_title = "My API"
82
+ config.info_description = "Public HTTP API"
83
+ config.info_version = "1.0.0"
84
+ config.openapi_version = "3.0.1"
85
+ config.servers = [{ "url" => "http://localhost:3000" }]
86
+
87
+ # Auth for rswag / OpenAPI (optional — remove if you have no auth)
88
+ config.security_schemes = {
89
+ "bearerAuth" => {
90
+ "type" => "http",
91
+ "scheme" => "bearer",
92
+ "bearerFormat" => "JWT"
93
+ }
94
+ }
95
+ config.security = [{ "bearerAuth" => [] }]
96
+
97
+ # Behavior flags
98
+ config.with_config = true
99
+ config.with_multiple_examples = true
100
+ config.with_rspec_examples = true
101
+ config.with_response_description = true
102
+ config.with_payload_properties = true
103
+ config.action_for_old_examples = :append
104
+
105
+ # Env var names / environment gate
106
+ config.swagger_path_environment_variable = "SWAGGER_GENERATE_PATH"
107
+ config.generate_swagger_environment_variable = "SWAGGER_GENERATE"
108
+ config.environment_name = :test
109
+ config.auto_include = true
110
+ end
111
+ ```
112
+
113
+ ### 4. Combine fragments → single `swagger.yaml`
114
+
115
+ `SWAGGER_GENERATE=1` writes **one file per resource** (e.g. `swagger/v1/users.yaml`). Rswag UI expects a **single** file at `swagger/v1/swagger.yaml`. Add a small combiner in the host app.
116
+
117
+ #### `lib/swagger_combiner.rb`
57
118
 
58
119
  ```ruby
59
- # app/controllers/application_controller.rb
60
- include SwaggerAutogenerate if Rails.env.test?
120
+ # frozen_string_literal: true
121
+
122
+ require "yaml"
123
+ require "fileutils"
124
+
125
+ # Merges every OpenAPI fragment under swagger/v1/*.yaml (except swagger.yaml)
126
+ # into a single swagger/v1/swagger.yaml for Rswag UI.
127
+ module SwaggerCombiner
128
+ module_function
129
+
130
+ SOURCE_DIR = File.expand_path("../swagger/v1", __dir__)
131
+ OUTPUT_FILE = File.join(SOURCE_DIR, "swagger.yaml")
132
+ SKIP_BASENAMES = %w[swagger.yaml swagger.yml].freeze
133
+
134
+ DEFAULT_DOC = {
135
+ "openapi" => "3.0.1",
136
+ "info" => {
137
+ "title" => "My API",
138
+ "description" => "Public HTTP API",
139
+ "version" => "1.0.0"
140
+ },
141
+ "servers" => [
142
+ { "url" => "http://localhost:3000" }
143
+ ],
144
+ "components" => {
145
+ "securitySchemes" => {
146
+ "bearerAuth" => {
147
+ "type" => "http",
148
+ "scheme" => "bearer",
149
+ "bearerFormat" => "JWT"
150
+ }
151
+ }
152
+ },
153
+ "security" => [
154
+ { "bearerAuth" => [] }
155
+ ],
156
+ "paths" => {}
157
+ }.freeze
158
+
159
+ def combine!(source_dir: SOURCE_DIR, output_file: OUTPUT_FILE)
160
+ FileUtils.mkdir_p(source_dir)
161
+
162
+ fragments = Dir[File.join(source_dir, "*.{yaml,yml}")]
163
+ .reject { |path| SKIP_BASENAMES.include?(File.basename(path)) }
164
+ .sort
165
+
166
+ if fragments.empty?
167
+ warn "[SwaggerCombiner] no fragment files found in #{source_dir}"
168
+ write_yaml(output_file, DEFAULT_DOC.dup)
169
+ return output_file
170
+ end
171
+
172
+ merged = deep_dup(DEFAULT_DOC)
173
+
174
+ fragments.each do |path|
175
+ doc = load_yaml(path)
176
+ next unless doc.is_a?(Hash)
177
+
178
+ merged["paths"] = deep_merge(merged["paths"], stringify_keys(doc["paths"] || {}))
179
+ merged["components"] = deep_merge(merged["components"], stringify_keys(doc["components"] || {}))
180
+
181
+ if doc["tags"].is_a?(Array)
182
+ merged["tags"] = Array(merged["tags"]) | doc["tags"]
183
+ end
184
+ end
185
+
186
+ merged["paths"] = merged["paths"].sort.to_h
187
+
188
+ write_yaml(output_file, merged)
189
+ puts "[SwaggerCombiner] wrote #{output_file} (#{merged["paths"].size} paths from #{fragments.size} files)"
190
+ output_file
191
+ end
192
+
193
+ def load_yaml(path)
194
+ YAML.safe_load(
195
+ File.read(path),
196
+ permitted_classes: [Date, Time, Symbol],
197
+ aliases: true
198
+ )
199
+ rescue Psych::DisallowedClass, Psych::BadAlias
200
+ YAML.load_file(path) # fragments may contain complex examples
201
+ end
202
+
203
+ def write_yaml(path, data)
204
+ File.write(path, data.to_yaml)
205
+ end
206
+
207
+ def deep_merge(left, right)
208
+ left = {} unless left.is_a?(Hash)
209
+ right = {} unless right.is_a?(Hash)
210
+
211
+ left.merge(right) do |_key, old_val, new_val|
212
+ if old_val.is_a?(Hash) && new_val.is_a?(Hash)
213
+ deep_merge(old_val, new_val)
214
+ else
215
+ new_val.nil? ? old_val : new_val
216
+ end
217
+ end
218
+ end
219
+
220
+ def stringify_keys(value)
221
+ case value
222
+ when Hash
223
+ value.each_with_object({}) do |(k, v), memo|
224
+ memo[k.to_s] = stringify_keys(v)
225
+ end
226
+ when Array
227
+ value.map { |item| stringify_keys(item) }
228
+ else
229
+ value
230
+ end
231
+ end
232
+
233
+ def deep_dup(value)
234
+ Marshal.load(Marshal.dump(value))
235
+ end
236
+ end
61
237
  ```
62
238
 
63
- Disable auto-include:
239
+ #### `config/initializers/combine_swagger.rb`
240
+
241
+ Rebuilds the combined file on every boot so `/api-docs` stays fresh.
64
242
 
65
243
  ```ruby
66
- # config/initializers/swagger_autogenerate.rb
67
- SwaggerAutogenerate.configure do |config|
68
- config.auto_include = false
244
+ # frozen_string_literal: true
245
+
246
+ # Rebuild swagger/v1/swagger.yaml from per-resource fragments on every boot.
247
+ # Rswag UI expects a single file at /api-docs/v1/swagger.yaml.
248
+ begin
249
+ require Rails.root.join("lib/swagger_combiner")
250
+ SwaggerCombiner.combine!(
251
+ source_dir: Rails.root.join("swagger/v1").to_s,
252
+ output_file: Rails.root.join("swagger/v1/swagger.yaml").to_s
253
+ )
254
+ rescue StandardError => e
255
+ warn "[SwaggerCombiner] failed to combine OpenAPI docs: #{e.class}: #{e.message}"
256
+ end
257
+ ```
258
+
259
+ #### `lib/tasks/swagger.rake`
260
+
261
+ ```ruby
262
+ # frozen_string_literal: true
263
+
264
+ namespace :swagger do
265
+ desc "Combine swagger/v1/*.yaml fragments into swagger/v1/swagger.yaml"
266
+ task combine: :environment do
267
+ require Rails.root.join("lib/swagger_combiner")
268
+ SwaggerCombiner.combine!
269
+ end
69
270
  end
70
271
  ```
71
272
 
72
- ### 2. Generate docs from specs
273
+ #### `bin/combine_swagger`
274
+
275
+ ```ruby
276
+ #!/usr/bin/env ruby
277
+ # frozen_string_literal: true
73
278
 
74
- Write a normal file path (recommended with rswag):
279
+ require_relative "../lib/swagger_combiner"
280
+
281
+ SwaggerCombiner.combine!
282
+ ```
75
283
 
76
284
  ```bash
77
- SWAGGER_GENERATE_PATH='swagger/v1/swagger.yaml' bundle exec rspec spec/requests/users_spec.rb
285
+ chmod +x bin/combine_swagger
78
286
  ```
79
287
 
80
- Or generate one YAML file per controller tag under the default directory:
288
+ ---
289
+
290
+ ## Generate docs
291
+
292
+ In **test**, the gem Railtie includes itself into `ApplicationController` automatically.
81
293
 
82
294
  ```bash
295
+ # One YAML fragment per controller tag under swagger/v1/
83
296
  SWAGGER_GENERATE=1 bundle exec rspec spec/requests/
84
- # => swagger/users.yaml (or swagger/v1/users.yaml when that folder exists / is configured)
297
+
298
+ # Then combine into swagger/v1/swagger.yaml
299
+ bin/combine_swagger
300
+ # or: bundle exec rake swagger:combine
301
+ ```
302
+
303
+ Or write a single file directly:
304
+
305
+ ```bash
306
+ SWAGGER_GENERATE_PATH='swagger/v1/swagger.yaml' bundle exec rspec spec/requests/users_spec.rb
85
307
  ```
86
308
 
87
309
  Generation only runs when:
@@ -91,80 +313,46 @@ Generation only runs when:
91
313
 
92
314
  Without those env vars, specs run normally and nothing is written.
93
315
 
94
- ## Configuration
316
+ Open the UI: `http://localhost:3000/api-docs`
95
317
 
96
- Optional initializer — **only override what you need**. Defaults are project-agnostic (empty security, app name as title, `swagger` output path).
318
+ ### Environment variables
319
+
320
+ | Variable | Purpose |
321
+ |---|---|
322
+ | `SWAGGER_GENERATE_PATH` | Exact `.yaml`/`.yml` file **or** a directory (one file per tag) |
323
+ | `SWAGGER_GENERATE` | Write under `config.default_path` (rswag-aware) |
324
+ | `tag` | Optional override for the OpenAPI tag / filename stem |
325
+
326
+ ### Manual include / disable auto-include
327
+
328
+ ```ruby
329
+ # app/controllers/application_controller.rb
330
+ include SwaggerAutogenerate if Rails.env.test?
331
+ ```
97
332
 
98
333
  ```ruby
99
334
  # config/initializers/swagger_autogenerate.rb
100
- SwaggerAutogenerate.configure do |config|
101
- # Where SWAGGER_GENERATE writes files (rswag default layout)
102
- config.default_path = 'swagger/v1'
335
+ config.auto_include = false
336
+ ```
103
337
 
104
- # OpenAPI info (auto title = Rails app module name when unset)
105
- config.info_title = 'My API'
106
- config.info_description = 'Public HTTP API'
107
- config.info_version = '1.0.0'
108
- config.openapi_version = '3.0.1'
109
- config.servers = [{ 'url' => 'https://api.example.com' }]
338
+ ---
110
339
 
111
- # Auth for rswag / OpenAPI
112
- config.security_schemes = {
113
- 'bearerAuth' => {
114
- 'type' => 'http',
115
- 'scheme' => 'bearer',
116
- 'bearerFormat' => 'JWT'
117
- }
118
- }
119
- config.security = [{ 'bearerAuth' => [] }]
340
+ ## Configuration reference
120
341
 
121
- # Behavior flags
122
- config.with_config = true # write openapi/info/components header
123
- config.with_multiple_examples = true # keep several response examples per status
124
- config.with_rspec_examples = true # use RSpec example group description as example name
125
- config.with_response_description = true # human-readable status descriptions
126
- config.with_payload_properties = true # document request payload shapes per example
127
- config.action_for_old_examples = :append # or :replace
128
-
129
- # Env var names / environment gate (rarely changed)
130
- config.swagger_path_environment_variable = 'SWAGGER_GENERATE_PATH'
131
- config.generate_swagger_environment_variable = 'SWAGGER_GENERATE'
132
- config.environment_name = :test
133
- config.auto_include = true
134
- end
135
- ```
342
+ Only override what you need. Defaults are project-agnostic (empty security, app name as title, `swagger` output path).
136
343
 
137
344
  ### Full document override
138
345
 
139
- If you already maintain a root OpenAPI hash (or want zero magic):
140
-
141
346
  ```ruby
142
347
  SwaggerAutogenerate.configure do |config|
143
348
  config.swagger_config = {
144
- 'openapi' => '3.0.1',
145
- 'info' => { 'title' => 'Custom', 'version' => '1.0.0' },
146
- 'components' => { 'securitySchemes' => {} }
349
+ "openapi" => "3.0.1",
350
+ "info" => { "title" => "Custom", "version" => "1.0.0" },
351
+ "components" => { "securitySchemes" => {} }
147
352
  }
148
353
  end
149
354
  ```
150
355
 
151
- ### Environment variables
152
-
153
- | Variable | Purpose |
154
- |---|---|
155
- | `SWAGGER_GENERATE_PATH` | Exact `.yaml`/`.yml` file **or** a directory (one file per tag) |
156
- | `SWAGGER_GENERATE` | Write under `config.default_path` (rswag-aware) |
157
- | `tag` | Optional override for the OpenAPI tag / filename stem |
158
-
159
- ## Example
160
-
161
- ```bash
162
- SWAGGER_GENERATE_PATH='swagger/v1/swagger.yaml' \
163
- bundle exec rspec spec/requests/employees_spec.rb
164
- ```
165
-
166
- Coverage in the YAML matches what your examples actually hit (paths, statuses, bodies, query/path params). Prefer request specs that exercise real routes.
167
-
168
356
  ## Library layout
169
357
 
170
358
  ```
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwaggerAutogenerate
4
- VERSION = '2.0.0'
4
+ VERSION = '2.0.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swagger_autogenerate
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - MohammedBuraiah