docit 0.3.0 → 0.4.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 +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- data/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
- data/CHANGELOG.md +23 -1
- data/CONTRIBUTING.md +1 -1
- data/README.md +32 -390
- data/lib/docit/ai/autodoc_runner.rb +25 -24
- data/lib/docit/ai/doc_block_validator.rb +48 -0
- data/lib/docit/ai/tag_injector.rb +1 -1
- data/lib/docit/configuration.rb +33 -0
- data/lib/docit/doc_file.rb +1 -1
- data/lib/docit/dsl.rb +6 -3
- data/lib/docit/operation.rb +7 -2
- data/lib/docit/route_inspector.rb +1 -1
- data/lib/docit/schema_generator.rb +24 -5
- data/lib/docit/version.rb +1 -1
- data/lib/generators/docit/install/install_generator.rb +3 -3
- data/lib/tasks/docit.rake +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39589c6800b65d7a0ad55701378b345f18277735df3511cce965bc889035e74f
|
|
4
|
+
data.tar.gz: 91c392bded5bbd0897222859feab4d68e8c0d3064d4054e6b75509a325ced2d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ea253c903e6a90eb3754ed9f4a586adcc7339c831d9623401cc4bb53256359c81e069bba4067a12379da350fbf354b50bfd14420ae0aa75c3841c74942ae5f2
|
|
7
|
+
data.tar.gz: 8441b41ae22a25f1bd29514fbdcb425b4bbd56b591da6904df0c9ad070a7ac830b1eb9338783a8c751d8f648db4826d69b7755a0f42cd320baaf8e3ebeb7c628
|
|
@@ -17,7 +17,7 @@ Explain the problem this feature would solve or the workflow it would improve.
|
|
|
17
17
|
Describe how you'd like it to work, including example DSL usage if applicable:
|
|
18
18
|
|
|
19
19
|
```ruby
|
|
20
|
-
|
|
20
|
+
doc_for :index do
|
|
21
21
|
# your proposed syntax here
|
|
22
22
|
end
|
|
23
23
|
```
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.4.0] - 2026-04-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `operation_id` DSL method: set a custom `operationId` per endpoint for cleaner SDK codegen
|
|
7
|
+
- Auto-generated `operationId` for every endpoint (e.g., `index_users`, `login_auth`) when not explicitly set
|
|
8
|
+
- `config.license` option: add license info (`name`, `url`) to the OpenAPI spec
|
|
9
|
+
- `config.contact` option: add contact info (`name`, `email`, `url`) to the OpenAPI spec
|
|
10
|
+
- `config.terms_of_service` option: add terms of service URL to the OpenAPI spec
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Renamed `swagger_doc` DSL to `doc_for` (`swagger_doc` still works as a backward-compatible alias)
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- `TagInjector` no longer crashes when `initializer_path` is nil
|
|
17
|
+
- `docit:autodoc` rake task: removed broken `--dry-run` CLI flag (use `DRY_RUN=1` env var instead)
|
|
18
|
+
- Fixed dummy app `auth_docs.rb` summary and request body to match integration test expectations
|
|
19
|
+
|
|
20
|
+
## [0.3.1] - 2026-04-17
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Fixed gem packaging: `doc_block_validator.rb` was missing from the published 0.3.0 gem, causing `LoadError` on require
|
|
24
|
+
|
|
3
25
|
## [0.3.0] - 2026-04-16
|
|
4
26
|
|
|
5
27
|
### Added
|
|
@@ -51,7 +73,7 @@
|
|
|
51
73
|
## [0.1.0] - 2026-04-08
|
|
52
74
|
|
|
53
75
|
- Initial release
|
|
54
|
-
- DSL: `
|
|
76
|
+
- DSL: `doc_for` macro for inline controller documentation
|
|
55
77
|
- DSL: `use_docs` + `Docit::DocFile` for separate doc files
|
|
56
78
|
- Builders: request body, response, and parameter builders with nested object/array support
|
|
57
79
|
- Schema `$ref` components via `Docit.define_schema`
|
data/CONTRIBUTING.md
CHANGED
|
@@ -55,7 +55,7 @@ bundle exec rspec spec/docit/v2_features_spec.rb:30
|
|
|
55
55
|
lib/docit.rb # Entry point, configuration, schema registry
|
|
56
56
|
lib/docit/configuration.rb # Config class (title, auth, tags)
|
|
57
57
|
lib/docit/registry.rb # Global operation store
|
|
58
|
-
lib/docit/dsl.rb #
|
|
58
|
+
lib/docit/dsl.rb # doc_for macro
|
|
59
59
|
lib/docit/operation.rb # Single endpoint documentation
|
|
60
60
|
lib/docit/builders/ # DSL builders (response, request_body, parameter)
|
|
61
61
|
lib/docit/schema_definition.rb # Reusable $ref schema definitions
|
data/README.md
CHANGED
|
@@ -5,44 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
Decorator-style API documentation for Ruby on Rails. Write OpenAPI 3.0.3 docs with clean controller DSL macros, separate doc modules, or AI-assisted scaffolding for undocumented endpoints.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- [Style 1: Inline (simple APIs)](#style-1-inline-simple-apis)
|
|
16
|
-
- [Style 2: Separate doc files (recommended for larger APIs)](#style-2-separate-doc-files-recommended-for-larger-apis)
|
|
17
|
-
- Endpoint DSL reference
|
|
18
|
-
- [Endpoint documentation DSL](#endpoint-documentation-dsl)
|
|
19
|
-
- [Request bodies](#request-bodies)
|
|
20
|
-
- [Path parameters](#path-parameters)
|
|
21
|
-
- [Enums](#enums)
|
|
22
|
-
- [Security](#security)
|
|
23
|
-
- [Deprecated endpoints](#deprecated-endpoints)
|
|
24
|
-
- [Nested objects and arrays](#nested-objects-and-arrays)
|
|
25
|
-
- [Response examples](#response-examples)
|
|
26
|
-
- [Shared schemas (`$ref`)](#shared-schemas-ref)
|
|
27
|
-
- [File uploads](#file-uploads)
|
|
28
|
-
- AI documentation
|
|
29
|
-
- [AI Automatic Documentation](#ai-automatic-documentation)
|
|
30
|
-
- [Quick start (included in install)](#quick-start-included-in-install)
|
|
31
|
-
- [Standalone commands](#standalone-commands)
|
|
32
|
-
- [Supported providers](#supported-providers)
|
|
33
|
-
- [What the AI generates](#what-the-ai-generates)
|
|
34
|
-
- Runtime and development
|
|
35
|
-
- [Documentation UIs](#documentation-uis)
|
|
36
|
-
- [How it works](#how-it-works)
|
|
37
|
-
- [Mounting at a different path](#mounting-at-a-different-path)
|
|
38
|
-
- [JSON spec only](#json-spec-only)
|
|
39
|
-
- [Development](#development)
|
|
40
|
-
- [Contributing](#contributing)
|
|
41
|
-
- [License](#license)
|
|
42
|
-
- Project docs
|
|
43
|
-
- [CHANGELOG](CHANGELOG.md)
|
|
44
|
-
- [CONTRIBUTING](CONTRIBUTING.md)
|
|
45
|
-
- [CODE OF CONDUCT](CODE_OF_CONDUCT.md)
|
|
8
|
+
### Scalar (default)
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
### Swagger
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
> **Full documentation:** [docitruby.dev/docs](https://docitruby.dev/docs)
|
|
46
15
|
|
|
47
16
|
## Installation
|
|
48
17
|
|
|
@@ -70,8 +39,6 @@ The install generator does everything in one step:
|
|
|
70
39
|
|
|
71
40
|
Visit `/api-docs` to see your interactive API documentation (Scalar by default, Swagger UI also available at `/api-docs/swagger`).
|
|
72
41
|
|
|
73
|
-
If you choose AI setup, Docit stores your provider config in `.docit_ai.yml` with restricted file permissions and adds that file to `.gitignore` when possible.
|
|
74
|
-
|
|
75
42
|
## Configuration
|
|
76
43
|
|
|
77
44
|
Edit `config/initializers/docit.rb`:
|
|
@@ -81,48 +48,40 @@ Docit.configure do |config|
|
|
|
81
48
|
config.title = "My API"
|
|
82
49
|
config.version = "1.0.0"
|
|
83
50
|
config.description = "Backend API documentation"
|
|
51
|
+
config.default_ui = :scalar # :scalar (default) or :swagger
|
|
84
52
|
|
|
85
|
-
#
|
|
86
|
-
config.default_ui = :scalar
|
|
87
|
-
|
|
88
|
-
# Authentication: pick one (or multiple):
|
|
89
|
-
config.auth :bearer # Bearer token (JWT by default)
|
|
90
|
-
config.auth :basic # HTTP Basic
|
|
91
|
-
config.auth :api_key, name: "X-API-Key", # API key in header
|
|
92
|
-
location: "header"
|
|
93
|
-
|
|
94
|
-
# Tag descriptions (shown in the documentation sidebar):
|
|
53
|
+
config.auth :bearer # Bearer token (JWT)
|
|
95
54
|
config.tag "Users", description: "User account management"
|
|
96
|
-
config.tag "Auth", description: "Authentication endpoints"
|
|
97
|
-
|
|
98
|
-
# Server URLs (shown in the server dropdown):
|
|
99
55
|
config.server "https://api.example.com", description: "Production"
|
|
100
|
-
config.server "https://staging.example.com", description: "Staging"
|
|
101
|
-
config.server "http://localhost:3000", description: "Development"
|
|
102
56
|
end
|
|
103
57
|
```
|
|
104
58
|
|
|
105
|
-
|
|
59
|
+
See the [full configuration reference](https://docitruby.dev/docs/configuration) for all options including `license`, `contact`, `terms_of_service`, multiple auth schemes, and server URLs.
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
106
62
|
|
|
107
|
-
Docit supports two styles
|
|
63
|
+
Docit supports two styles. Choose whichever fits your project or mix both.
|
|
108
64
|
|
|
109
65
|
### Style 1: Inline (simple APIs)
|
|
110
66
|
|
|
111
|
-
Add `
|
|
67
|
+
Add `doc_for` blocks directly in your controller:
|
|
112
68
|
|
|
113
69
|
```ruby
|
|
114
70
|
class Api::V1::UsersController < ApplicationController
|
|
115
|
-
|
|
71
|
+
doc_for :index do
|
|
116
72
|
summary "List all users"
|
|
117
73
|
tags "Users"
|
|
118
74
|
response 200, "Users retrieved"
|
|
119
75
|
end
|
|
76
|
+
|
|
120
77
|
def index
|
|
121
78
|
# your code
|
|
122
79
|
end
|
|
123
80
|
end
|
|
124
81
|
```
|
|
125
82
|
|
|
83
|
+
> **Upgrading?** The previous `swagger_doc` method still works as an alias for `doc_for`, so existing code won't break.
|
|
84
|
+
|
|
126
85
|
### Style 2: Separate doc files (recommended for larger APIs)
|
|
127
86
|
|
|
128
87
|
Keep controllers clean by defining docs in dedicated files:
|
|
@@ -134,387 +93,70 @@ module Api::V1::UsersDocs
|
|
|
134
93
|
|
|
135
94
|
doc :index do
|
|
136
95
|
summary "List all users"
|
|
137
|
-
description "Returns a paginated list of users"
|
|
138
96
|
tags "Users"
|
|
139
97
|
|
|
140
|
-
parameter :page, location: :query, type: :integer, description: "Page number"
|
|
141
|
-
|
|
142
98
|
response 200, "Users retrieved" do
|
|
143
99
|
property :users, type: :array, items: :object do
|
|
144
100
|
property :id, type: :integer, example: 1
|
|
145
101
|
property :email, type: :string, example: "user@example.com"
|
|
146
102
|
end
|
|
147
|
-
property :total, type: :integer, example: 42
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
doc :create do
|
|
152
|
-
summary "Create a user"
|
|
153
|
-
tags "Users"
|
|
154
|
-
|
|
155
|
-
request_body required: true do
|
|
156
|
-
property :email, type: :string, required: true
|
|
157
|
-
property :password, type: :string, required: true, format: :password
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
response 201, "User created" do
|
|
161
|
-
property :id, type: :integer
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
response 422, "Validation failed" do
|
|
165
|
-
property :errors, type: :object do
|
|
166
|
-
property :email, type: :array, items: :string
|
|
167
|
-
end
|
|
168
103
|
end
|
|
169
104
|
end
|
|
170
105
|
end
|
|
106
|
+
```
|
|
171
107
|
|
|
172
|
-
|
|
108
|
+
```ruby
|
|
109
|
+
# app/controllers/api/v1/users_controller.rb
|
|
173
110
|
class Api::V1::UsersController < ApplicationController
|
|
174
111
|
use_docs Api::V1::UsersDocs
|
|
175
112
|
|
|
176
113
|
def index
|
|
177
114
|
# pure business logic
|
|
178
115
|
end
|
|
179
|
-
|
|
180
|
-
def create
|
|
181
|
-
# pure business logic
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
You can also mix both styles — use `use_docs` for most actions and add inline `swagger_doc` for one-offs:
|
|
187
|
-
|
|
188
|
-
```ruby
|
|
189
|
-
class Api::V1::UsersController < ApplicationController
|
|
190
|
-
use_docs Api::V1::UsersDocs # loads :index and :create from doc file
|
|
191
|
-
|
|
192
|
-
swagger_doc :destroy do # inline doc for this one action
|
|
193
|
-
summary "Delete user"
|
|
194
|
-
tags "Users"
|
|
195
|
-
response 204, "Deleted"
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def index; end
|
|
199
|
-
def create; end
|
|
200
|
-
def destroy; end
|
|
201
|
-
end
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### Endpoint documentation DSL
|
|
205
|
-
|
|
206
|
-
The following examples work in both `swagger_doc` blocks and `doc` blocks.
|
|
207
|
-
|
|
208
|
-
### Request bodies
|
|
209
|
-
|
|
210
|
-
```ruby
|
|
211
|
-
swagger_doc :create do
|
|
212
|
-
summary "Create a user"
|
|
213
|
-
tags "Users"
|
|
214
|
-
|
|
215
|
-
request_body required: true do
|
|
216
|
-
property :email, type: :string, required: true, example: "user@example.com"
|
|
217
|
-
property :password, type: :string, required: true, format: :password
|
|
218
|
-
property :name, type: :string, example: "Jane Doe"
|
|
219
|
-
property :profile, type: :object do
|
|
220
|
-
property :bio, type: :string
|
|
221
|
-
property :avatar_url, type: :string, format: :uri
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
response 201, "User created" do
|
|
226
|
-
property :id, type: :integer, example: 1
|
|
227
|
-
property :email, type: :string, example: "user@example.com"
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
response 422, "Validation failed" do
|
|
231
|
-
property :errors, type: :object do
|
|
232
|
-
property :email, type: :array, items: :string
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
def create
|
|
237
|
-
# your code
|
|
238
|
-
end
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### Path parameters
|
|
242
|
-
|
|
243
|
-
```ruby
|
|
244
|
-
swagger_doc :show do
|
|
245
|
-
summary "Get a user"
|
|
246
|
-
tags "Users"
|
|
247
|
-
|
|
248
|
-
parameter :id, location: :path, type: :integer, required: true, description: "User ID"
|
|
249
|
-
|
|
250
|
-
response 200, "User found" do
|
|
251
|
-
property :id, type: :integer, example: 1
|
|
252
|
-
property :email, type: :string
|
|
253
|
-
property :name, type: :string
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
response 404, "User not found" do
|
|
257
|
-
property :error, type: :string, example: "Not found"
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
def show
|
|
261
|
-
# your code
|
|
262
|
-
end
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### Enums
|
|
266
|
-
|
|
267
|
-
```ruby
|
|
268
|
-
swagger_doc :index do
|
|
269
|
-
summary "List orders"
|
|
270
|
-
tags "Orders"
|
|
271
|
-
|
|
272
|
-
parameter :status, location: :query, type: :string,
|
|
273
|
-
enum: %w[pending shipped delivered],
|
|
274
|
-
description: "Filter by status"
|
|
275
|
-
|
|
276
|
-
response 200, "Orders list" do
|
|
277
|
-
property :orders, type: :array do
|
|
278
|
-
property :id, type: :integer
|
|
279
|
-
property :status, type: :string, enum: %w[pending shipped delivered]
|
|
280
|
-
end
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
### Security
|
|
286
|
-
|
|
287
|
-
Mark endpoints as requiring authentication:
|
|
288
|
-
|
|
289
|
-
```ruby
|
|
290
|
-
swagger_doc :destroy do
|
|
291
|
-
summary "Delete a user"
|
|
292
|
-
tags "Users"
|
|
293
|
-
security :bearer_auth # references the scheme from your config
|
|
294
|
-
|
|
295
|
-
response 204, "User deleted"
|
|
296
|
-
response 401, "Unauthorized"
|
|
297
|
-
end
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
### Deprecated endpoints
|
|
301
|
-
|
|
302
|
-
```ruby
|
|
303
|
-
swagger_doc :legacy_search do
|
|
304
|
-
summary "Search (legacy)"
|
|
305
|
-
tags "Search"
|
|
306
|
-
deprecated
|
|
307
|
-
|
|
308
|
-
response 200, "Results"
|
|
309
|
-
end
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
### Nested objects and arrays
|
|
313
|
-
|
|
314
|
-
```ruby
|
|
315
|
-
response 200, "Success" do
|
|
316
|
-
property :user, type: :object do
|
|
317
|
-
property :id, type: :integer
|
|
318
|
-
property :name, type: :string
|
|
319
|
-
property :addresses, type: :array do
|
|
320
|
-
property :street, type: :string
|
|
321
|
-
property :city, type: :string
|
|
322
|
-
property :zip, type: :string
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
end
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
### Response examples
|
|
329
|
-
|
|
330
|
-
```ruby
|
|
331
|
-
response 200, "User found" do
|
|
332
|
-
property :id, type: :integer
|
|
333
|
-
property :email, type: :string
|
|
334
|
-
|
|
335
|
-
example "admin_user",
|
|
336
|
-
{ id: 1, email: "admin@example.com" },
|
|
337
|
-
description: "An admin user"
|
|
338
|
-
|
|
339
|
-
example "regular_user",
|
|
340
|
-
{ id: 2, email: "user@example.com" },
|
|
341
|
-
description: "A regular user"
|
|
342
|
-
end
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
### Shared schemas (`$ref`)
|
|
346
|
-
|
|
347
|
-
Define reusable schemas once and reference them across multiple endpoints:
|
|
348
|
-
|
|
349
|
-
```ruby
|
|
350
|
-
# In config/initializers/docit.rb or a dedicated file:
|
|
351
|
-
Docit.define_schema :User do
|
|
352
|
-
property :id, type: :integer, example: 1
|
|
353
|
-
property :email, type: :string, example: "user@example.com"
|
|
354
|
-
property :name, type: :string, example: "Jane Doe"
|
|
355
|
-
property :address, type: :object do
|
|
356
|
-
property :street, type: :string
|
|
357
|
-
property :city, type: :string
|
|
358
|
-
end
|
|
359
|
-
end
|
|
360
|
-
|
|
361
|
-
Docit.define_schema :Error do
|
|
362
|
-
property :error, type: :string, example: "Not found"
|
|
363
|
-
property :details, type: :array, items: :string
|
|
364
|
-
end
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
Reference them in any endpoint with `schema ref:`:
|
|
368
|
-
|
|
369
|
-
```ruby
|
|
370
|
-
swagger_doc :show do
|
|
371
|
-
summary "Get user"
|
|
372
|
-
tags "Users"
|
|
373
|
-
|
|
374
|
-
response 200, "User found" do
|
|
375
|
-
schema ref: :User
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
response 404, "Not found" do
|
|
379
|
-
schema ref: :Error
|
|
380
|
-
end
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
swagger_doc :create do
|
|
384
|
-
summary "Create user"
|
|
385
|
-
tags "Users"
|
|
386
|
-
|
|
387
|
-
request_body required: true do
|
|
388
|
-
schema ref: :User
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
response 201, "Created" do
|
|
392
|
-
schema ref: :User
|
|
393
|
-
end
|
|
394
|
-
end
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
This outputs `$ref: '#/components/schemas/User'` in the spec — Swagger UI resolves it automatically.
|
|
398
|
-
|
|
399
|
-
### File uploads
|
|
400
|
-
|
|
401
|
-
Use `type: :file` with `content_type: "multipart/form-data"` for file upload endpoints:
|
|
402
|
-
|
|
403
|
-
```ruby
|
|
404
|
-
swagger_doc :upload_avatar do
|
|
405
|
-
summary "Upload avatar"
|
|
406
|
-
tags "Users"
|
|
407
|
-
|
|
408
|
-
request_body required: true, content_type: "multipart/form-data" do
|
|
409
|
-
property :avatar, type: :file, required: true, description: "Avatar image"
|
|
410
|
-
property :caption, type: :string
|
|
411
|
-
end
|
|
412
|
-
|
|
413
|
-
response 201, "Avatar uploaded" do
|
|
414
|
-
property :url, type: :string, format: :uri
|
|
415
|
-
end
|
|
416
116
|
end
|
|
417
117
|
```
|
|
418
118
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
## AI Automatic Documentation
|
|
422
|
-
|
|
423
|
-
Docit can generate complete API documentation using AI. This works with OpenAI, Anthropic, or Groq (free tier available).
|
|
119
|
+
See the [full DSL reference](https://docitruby.dev/docs/dsl-reference) for request bodies, path parameters, enums, security, nested objects, file uploads, shared schemas, and more.
|
|
424
120
|
|
|
425
|
-
|
|
121
|
+
## AI Documentation
|
|
426
122
|
|
|
427
|
-
|
|
123
|
+
Docit can generate complete API documentation using AI. Works with OpenAI, Anthropic, or Groq (free tier available).
|
|
428
124
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
### Standalone commands
|
|
432
|
-
|
|
433
|
-
You can also set up AI docs separately:
|
|
125
|
+
When you run `rails generate docit:install` and choose AI automatic docs, everything is set up automatically — provider configuration, doc generation, controller wiring, and tag injection.
|
|
434
126
|
|
|
435
127
|
```bash
|
|
436
|
-
#
|
|
128
|
+
# Or set up AI docs separately:
|
|
437
129
|
rails generate docit:ai_setup
|
|
438
|
-
|
|
439
|
-
# Generate docs for all undocumented endpoints
|
|
440
130
|
rails docit:autodoc
|
|
441
131
|
|
|
442
|
-
#
|
|
443
|
-
rails docit:autodoc[Api::V1::UsersController]
|
|
444
|
-
|
|
445
|
-
# Preview what would be generated without writing files
|
|
132
|
+
# Preview without writing files:
|
|
446
133
|
DRY_RUN=1 rails docit:autodoc
|
|
447
134
|
```
|
|
448
135
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
| Provider | Notes |
|
|
452
|
-
|------------|-------|
|
|
453
|
-
| OpenAI | Requires API key from platform.openai.com |
|
|
454
|
-
| Anthropic | Requires API key from console.anthropic.com |
|
|
455
|
-
| Groq | Free tier at console.groq.com |
|
|
456
|
-
|
|
457
|
-
All providers automatically retry on rate-limit (429) errors with exponential backoff, so free-tier usage works out of the box.
|
|
458
|
-
|
|
459
|
-
AI configuration is stored in `.docit_ai.yml`.
|
|
460
|
-
If your app does not have a `.gitignore`, add `.docit_ai.yml` manually.
|
|
461
|
-
|
|
462
|
-
### What the AI generates
|
|
463
|
-
|
|
464
|
-
For each undocumented endpoint, Docit:
|
|
465
|
-
|
|
466
|
-
1. Reads the controller source code
|
|
467
|
-
2. Inspects the route (HTTP method, path, parameters)
|
|
468
|
-
3. Writes the generated doc block to `app/docs/`
|
|
469
|
-
4. Injects `use_docs` into the controller
|
|
470
|
-
5. Adds tag descriptions to the initializer
|
|
471
|
-
|
|
472
|
-
Do not use AI autodoc on controllers that contain secrets, proprietary business rules, or internal comments you do not want sent to an external provider.
|
|
136
|
+
See the [full AI documentation guide](https://docitruby.dev/docs/ai-documentation) for provider details, safety considerations, and standalone commands.
|
|
473
137
|
|
|
474
138
|
## Documentation UIs
|
|
475
139
|
|
|
476
|
-
Docit ships with two documentation UIs, both reading from the same OpenAPI spec:
|
|
477
|
-
|
|
478
140
|
| Path | UI | Notes |
|
|
479
141
|
|------|------|-------|
|
|
480
142
|
| `/api-docs` | Default (Scalar) | Configurable via `config.default_ui` |
|
|
481
|
-
| `/api-docs/scalar` | Scalar API Reference | Modern UI with
|
|
143
|
+
| `/api-docs/scalar` | Scalar API Reference | Modern UI with API client, dark mode |
|
|
482
144
|
| `/api-docs/swagger` | Swagger UI | Classic OpenAPI explorer |
|
|
483
145
|
| `/api-docs/spec` | Raw JSON | OpenAPI 3.0.3 spec |
|
|
484
146
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
## How it works
|
|
147
|
+
## How It Works
|
|
488
148
|
|
|
489
|
-
1. `
|
|
149
|
+
1. `doc_for` registers an **Operation** for each controller action in a global **Registry**
|
|
490
150
|
2. When someone visits `/api-docs/spec`, Docit's **SchemaGenerator** combines all registered operations with your Rails routes (via **RouteInspector**) to produce an OpenAPI 3.0.3 JSON document
|
|
491
151
|
3. The **Engine** serves the configured documentation UI at `/api-docs`, pointing it at the generated spec
|
|
492
152
|
|
|
493
|
-
The DSL is included in all controllers automatically via a Rails Engine initializer — no manual `include` needed if you're using `ActionController::API` or `ActionController::Base`.
|
|
494
|
-
|
|
495
|
-
## Mounting at a different path
|
|
496
|
-
|
|
497
|
-
In `config/routes.rb`:
|
|
498
|
-
|
|
499
|
-
```ruby
|
|
500
|
-
mount Docit::Engine => "/docs" # now at /docs instead of /api-docs
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
## JSON spec only
|
|
504
|
-
|
|
505
|
-
If you just want the raw OpenAPI JSON (e.g., for code generation):
|
|
506
|
-
|
|
507
|
-
```
|
|
508
|
-
GET /api-docs/spec
|
|
509
|
-
```
|
|
510
|
-
|
|
511
153
|
## Development
|
|
512
154
|
|
|
513
155
|
```bash
|
|
514
156
|
git clone https://github.com/S13G/docit.git
|
|
515
157
|
cd docit
|
|
516
158
|
bundle install
|
|
517
|
-
bundle exec rspec
|
|
159
|
+
bundle exec rspec
|
|
518
160
|
```
|
|
519
161
|
|
|
520
162
|
## Contributing
|
|
@@ -62,11 +62,11 @@ module Docit
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
routes_file = Rails.root.join("config", "routes.rb")
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
return unless File.exist?(routes_file) && !File.read(routes_file).include?("Docit::Engine")
|
|
66
|
+
|
|
67
|
+
@output.puts "Warning: Docit engine is not mounted in config/routes.rb"
|
|
68
|
+
@output.puts " Run: rails generate docit:install (or add: mount Docit::Engine => \"/api-docs\")"
|
|
69
|
+
@output.puts ""
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def detect_gaps
|
|
@@ -84,20 +84,20 @@ module Docit
|
|
|
84
84
|
@output.puts "Docit will send controller source code to #{config.provider.capitalize} to generate documentation."
|
|
85
85
|
@output.puts "Review the endpoints first if they contain secrets or proprietary logic."
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
87
|
+
return unless @input.respond_to?(:tty?) && @input.tty?
|
|
88
|
+
|
|
89
|
+
loop do
|
|
90
|
+
@output.print "Continue? (y/n): "
|
|
91
|
+
choice = @input.gets.to_s.strip.downcase
|
|
92
|
+
|
|
93
|
+
case choice
|
|
94
|
+
when "y", "yes"
|
|
95
|
+
@output.puts ""
|
|
96
|
+
return
|
|
97
|
+
when "n", "no"
|
|
98
|
+
raise Docit::Error, "Autodoc cancelled."
|
|
99
|
+
else
|
|
100
|
+
@output.puts "Please enter y or n."
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
end
|
|
@@ -138,6 +138,7 @@ module Docit
|
|
|
138
138
|
invalid_output_retries += 1
|
|
139
139
|
if invalid_output_retries <= MAX_INVALID_OUTPUT_RETRIES
|
|
140
140
|
validation_error = e.message
|
|
141
|
+
@output.puts " invalid output, retrying"
|
|
141
142
|
retry
|
|
142
143
|
end
|
|
143
144
|
|
|
@@ -184,11 +185,11 @@ module Docit
|
|
|
184
185
|
|
|
185
186
|
def inject_tags(generated)
|
|
186
187
|
all_tags = generated.values.flatten.join("\n").scan(/tags\s+["']([^"']+)["']/).flatten
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
return unless all_tags.any?
|
|
189
|
+
|
|
190
|
+
injected = TagInjector.new(tags: all_tags).inject
|
|
191
|
+
injected.each { |tag| @output.puts " Added tag \"#{tag}\" to config/initializers/docit.rb" }
|
|
192
|
+
@results[:tags] = injected
|
|
192
193
|
end
|
|
193
194
|
|
|
194
195
|
def strip_markdown_fences(text)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
module Ai
|
|
5
|
+
class InvalidDocBlockError < Error; end
|
|
6
|
+
|
|
7
|
+
class DocBlockValidator
|
|
8
|
+
def initialize(controller:, action:, doc_block:)
|
|
9
|
+
@controller = controller
|
|
10
|
+
@action = action.to_sym
|
|
11
|
+
@doc_block = doc_block
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def validate!
|
|
15
|
+
doc_module = Module.new
|
|
16
|
+
doc_module.extend(Docit::DocFile)
|
|
17
|
+
doc_module.module_eval(@doc_block, "(generated Docit block)", 1)
|
|
18
|
+
|
|
19
|
+
validate_actions!(doc_module.actions)
|
|
20
|
+
|
|
21
|
+
operation = Docit::Operation.new(controller: @controller, action: @action)
|
|
22
|
+
operation.instance_eval(&doc_module[@action])
|
|
23
|
+
|
|
24
|
+
true
|
|
25
|
+
rescue SyntaxError, StandardError => e
|
|
26
|
+
raise InvalidDocBlockError, error_message_for(e)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def validate_actions!(actions)
|
|
32
|
+
return if actions == [@action]
|
|
33
|
+
|
|
34
|
+
raise InvalidDocBlockError, "Generated output did not define a doc block" if actions.empty?
|
|
35
|
+
|
|
36
|
+
action_list = actions.map { |action| ":#{action}" }.join(", ")
|
|
37
|
+
raise InvalidDocBlockError,
|
|
38
|
+
"Generated output must define only doc :#{@action}, got #{action_list}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def error_message_for(error)
|
|
42
|
+
return error.message if error.is_a?(InvalidDocBlockError)
|
|
43
|
+
|
|
44
|
+
"#{error.class}: #{error.message}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -8,7 +8,7 @@ module Docit
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def inject
|
|
11
|
-
return []
|
|
11
|
+
return [] unless initializer_path && File.exist?(initializer_path)
|
|
12
12
|
|
|
13
13
|
content = File.read(initializer_path)
|
|
14
14
|
existing_tags = content.scan(/config\.tag\s+["']([^"']+)["']/).flatten
|
data/lib/docit/configuration.rb
CHANGED
|
@@ -17,6 +17,9 @@ module Docit
|
|
|
17
17
|
@security_schemes = {}
|
|
18
18
|
@tags = []
|
|
19
19
|
@servers = []
|
|
20
|
+
@license = nil
|
|
21
|
+
@contact = nil
|
|
22
|
+
@terms_of_service = nil
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
def default_ui=(value)
|
|
@@ -75,5 +78,35 @@ module Docit
|
|
|
75
78
|
def servers
|
|
76
79
|
@servers.dup
|
|
77
80
|
end
|
|
81
|
+
|
|
82
|
+
def license(name:, url: nil)
|
|
83
|
+
entry = { name: name }
|
|
84
|
+
entry[:url] = url if url
|
|
85
|
+
@license = entry
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def license_info
|
|
89
|
+
@license&.dup
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def contact(name: nil, email: nil, url: nil)
|
|
93
|
+
entry = {}
|
|
94
|
+
entry[:name] = name if name
|
|
95
|
+
entry[:email] = email if email
|
|
96
|
+
entry[:url] = url if url
|
|
97
|
+
@contact = entry
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def contact_info
|
|
101
|
+
@contact&.dup
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def terms_of_service(url)
|
|
105
|
+
@terms_of_service = url
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def terms_of_service_url
|
|
109
|
+
@terms_of_service
|
|
110
|
+
end
|
|
78
111
|
end
|
|
79
112
|
end
|
data/lib/docit/doc_file.rb
CHANGED
data/lib/docit/dsl.rb
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Docit
|
|
4
4
|
# Included in all Rails controllers via the Engine.
|
|
5
|
-
# Provides +
|
|
5
|
+
# Provides +doc_for+ and +use_docs+ class methods.
|
|
6
6
|
module DSL
|
|
7
7
|
def self.included(base)
|
|
8
8
|
base.extend(ClassMethods)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
module ClassMethods
|
|
12
|
-
def
|
|
12
|
+
def doc_for(action, &block)
|
|
13
13
|
operation = Operation.new(
|
|
14
14
|
controller: name,
|
|
15
15
|
action: action
|
|
@@ -18,9 +18,12 @@ module Docit
|
|
|
18
18
|
Registry.register(operation)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
# Backward-compatible alias
|
|
22
|
+
alias swagger_doc doc_for
|
|
23
|
+
|
|
21
24
|
def use_docs(doc_module)
|
|
22
25
|
doc_module.actions.each do |action|
|
|
23
|
-
|
|
26
|
+
doc_for(action, &doc_module[action])
|
|
24
27
|
end
|
|
25
28
|
end
|
|
26
29
|
end
|
data/lib/docit/operation.rb
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Docit
|
|
4
4
|
# Represents the documentation for a single controller action.
|
|
5
|
-
# Created by +
|
|
5
|
+
# Created by +doc_for+ and stored in the {Registry}.
|
|
6
6
|
class Operation
|
|
7
7
|
attr_reader :controller, :action, :_summary, :_description,
|
|
8
8
|
:_tags, :_responses, :_request_body, :_parameters,
|
|
9
|
-
:_security, :_deprecated
|
|
9
|
+
:_security, :_deprecated, :_operation_id
|
|
10
10
|
|
|
11
11
|
def initialize(controller:, action:)
|
|
12
12
|
@controller = controller
|
|
@@ -17,6 +17,7 @@ module Docit
|
|
|
17
17
|
@_request_body = nil
|
|
18
18
|
@_security = []
|
|
19
19
|
@_deprecated = false
|
|
20
|
+
@_operation_id = nil
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def summary(text)
|
|
@@ -35,6 +36,10 @@ module Docit
|
|
|
35
36
|
@_deprecated = value
|
|
36
37
|
end
|
|
37
38
|
|
|
39
|
+
def operation_id(text)
|
|
40
|
+
@_operation_id = text
|
|
41
|
+
end
|
|
42
|
+
|
|
38
43
|
def security(scheme)
|
|
39
44
|
@_security << scheme
|
|
40
45
|
end
|
|
@@ -5,7 +5,7 @@ module Docit
|
|
|
5
5
|
class RouteInspector
|
|
6
6
|
VALID_METHODS = %w[get post put patch delete].freeze
|
|
7
7
|
|
|
8
|
-
# Eagerly loads controller classes so
|
|
8
|
+
# Eagerly loads controller classes so doc_for/use_docs macros run before spec generation.
|
|
9
9
|
def self.eager_load_controllers!
|
|
10
10
|
return if defined?(Rails).nil? || Rails.application.routes.nil?
|
|
11
11
|
|
|
@@ -12,11 +12,7 @@ module Docit
|
|
|
12
12
|
|
|
13
13
|
spec = {
|
|
14
14
|
openapi: "3.0.3",
|
|
15
|
-
info:
|
|
16
|
-
title: config.title,
|
|
17
|
-
version: config.version,
|
|
18
|
-
description: config.description
|
|
19
|
-
},
|
|
15
|
+
info: build_info(config),
|
|
20
16
|
paths: build_paths,
|
|
21
17
|
components: {
|
|
22
18
|
securitySchemes: config.security_schemes
|
|
@@ -37,6 +33,18 @@ module Docit
|
|
|
37
33
|
|
|
38
34
|
private
|
|
39
35
|
|
|
36
|
+
def build_info(config)
|
|
37
|
+
info = {
|
|
38
|
+
title: config.title,
|
|
39
|
+
version: config.version,
|
|
40
|
+
description: config.description
|
|
41
|
+
}
|
|
42
|
+
info[:license] = config.license_info if config.license_info
|
|
43
|
+
info[:contact] = config.contact_info if config.contact_info
|
|
44
|
+
info[:termsOfService] = config.terms_of_service_url if config.terms_of_service_url
|
|
45
|
+
info
|
|
46
|
+
end
|
|
47
|
+
|
|
40
48
|
def build_paths
|
|
41
49
|
paths = {}
|
|
42
50
|
|
|
@@ -58,6 +66,7 @@ module Docit
|
|
|
58
66
|
|
|
59
67
|
def build_operation(operation)
|
|
60
68
|
result = {
|
|
69
|
+
operationId: operation._operation_id || generate_operation_id(operation),
|
|
61
70
|
summary: operation._summary || operation.action.humanize,
|
|
62
71
|
description: operation._description || "",
|
|
63
72
|
tags: operation._tags,
|
|
@@ -183,5 +192,15 @@ module Docit
|
|
|
183
192
|
hash[ex[:name].to_s] = entry
|
|
184
193
|
end
|
|
185
194
|
end
|
|
195
|
+
|
|
196
|
+
def generate_operation_id(operation)
|
|
197
|
+
# "Api::V1::UsersController" → "users" ; "index" → "listUsers"
|
|
198
|
+
resource = operation.controller
|
|
199
|
+
.gsub(/.*::/, "") # strip namespace
|
|
200
|
+
.gsub(/Controller$/, "") # strip suffix
|
|
201
|
+
action = operation.action
|
|
202
|
+
|
|
203
|
+
"#{action}_#{resource}".gsub("::", "_").downcase
|
|
204
|
+
end
|
|
186
205
|
end
|
|
187
206
|
end
|
data/lib/docit/version.rb
CHANGED
|
@@ -102,7 +102,7 @@ module Docit
|
|
|
102
102
|
say ""
|
|
103
103
|
say "Next steps:"
|
|
104
104
|
say " 1. Edit config/initializers/docit.rb to customize your API docs"
|
|
105
|
-
say " 2. Add
|
|
105
|
+
say " 2. Add doc_for blocks or create doc files under app/docs/"
|
|
106
106
|
say " 3. Visit /api-docs to see your Swagger UI"
|
|
107
107
|
say ""
|
|
108
108
|
say "You can set up docs later with:"
|
|
@@ -146,7 +146,7 @@ module Docit
|
|
|
146
146
|
|
|
147
147
|
def update_gitignore
|
|
148
148
|
gitignore = Rails.root.join(".gitignore")
|
|
149
|
-
|
|
149
|
+
unless File.exist?(gitignore)
|
|
150
150
|
say "Warning: .gitignore not found. Add .docit_ai.yml manually to avoid committing your API key.", :yellow
|
|
151
151
|
return
|
|
152
152
|
end
|
|
@@ -167,7 +167,7 @@ module Docit
|
|
|
167
167
|
choice = ask(prompt).to_s.strip
|
|
168
168
|
return choice if choices.include?(choice)
|
|
169
169
|
|
|
170
|
-
say "Invalid choice. Please enter #{choices.join(
|
|
170
|
+
say "Invalid choice. Please enter #{choices.join(", ")}.", :red
|
|
171
171
|
end
|
|
172
172
|
end
|
|
173
173
|
|
data/lib/tasks/docit.rake
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
namespace :docit do
|
|
4
4
|
desc "Generate documentation for undocumented API endpoints using AI"
|
|
5
5
|
task :autodoc, [:controller] => :environment do |_t, args|
|
|
6
|
-
dry_run = ENV.fetch("DRY_RUN", "0") == "1"
|
|
6
|
+
dry_run = ENV.fetch("DRY_RUN", "0") == "1"
|
|
7
7
|
controller_filter = args[:controller]
|
|
8
8
|
|
|
9
9
|
runner = Docit::Ai::AutodocRunner.new(
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: docit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- S13G
|
|
@@ -49,6 +49,7 @@ files:
|
|
|
49
49
|
- lib/docit/ai/autodoc_runner.rb
|
|
50
50
|
- lib/docit/ai/client.rb
|
|
51
51
|
- lib/docit/ai/configuration.rb
|
|
52
|
+
- lib/docit/ai/doc_block_validator.rb
|
|
52
53
|
- lib/docit/ai/doc_writer.rb
|
|
53
54
|
- lib/docit/ai/gap_detector.rb
|
|
54
55
|
- lib/docit/ai/groq_client.rb
|
|
@@ -77,14 +78,14 @@ files:
|
|
|
77
78
|
- lib/generators/docit/install/templates/initializer.rb
|
|
78
79
|
- lib/tasks/docit.rake
|
|
79
80
|
- sig/docit.rbs
|
|
80
|
-
homepage: https://
|
|
81
|
+
homepage: https://docitruby.dev
|
|
81
82
|
licenses:
|
|
82
83
|
- MIT
|
|
83
84
|
metadata:
|
|
84
|
-
homepage_uri: https://
|
|
85
|
+
homepage_uri: https://docitruby.dev
|
|
85
86
|
source_code_uri: https://github.com/S13G/docit
|
|
86
|
-
changelog_uri: https://github.com/S13G/docit/blob/
|
|
87
|
-
documentation_uri: https://
|
|
87
|
+
changelog_uri: https://github.com/S13G/docit/blob/master/CHANGELOG.md
|
|
88
|
+
documentation_uri: https://docitruby.dev/docs
|
|
88
89
|
bug_tracker_uri: https://github.com/S13G/docit/issues
|
|
89
90
|
rubygems_mfa_required: 'true'
|
|
90
91
|
rdoc_options: []
|