reqcord 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +90 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +252 -95
- data/Rakefile +13 -0
- data/docs/configuration.md +319 -0
- data/examples/reqcord.yml +58 -0
- data/gemfiles/rails_7.1.gemfile +13 -0
- data/gemfiles/rails_7.2.gemfile +13 -0
- data/gemfiles/rails_8.0.gemfile +13 -0
- data/gemfiles/rails_8.1.gemfile +13 -0
- data/lib/reqcord/capture/collector.rb +31 -0
- data/lib/reqcord/capture/integration_patch.rb +209 -0
- data/lib/reqcord/capture/minitest_context.rb +34 -0
- data/lib/reqcord/capture/rspec_context.rb +42 -0
- data/lib/reqcord/capture/test_context.rb +25 -0
- data/lib/reqcord/capture.rb +19 -0
- data/lib/reqcord/configuration.rb +198 -0
- data/lib/reqcord/dataset.rb +176 -0
- data/lib/reqcord/endpoint.rb +263 -0
- data/lib/reqcord/errors.rb +9 -0
- data/lib/reqcord/exporters/curl.rb +68 -0
- data/lib/reqcord/exporters/markdown.rb +295 -0
- data/lib/reqcord/exporters/postman.rb +206 -0
- data/lib/reqcord/exporters.rb +32 -0
- data/lib/reqcord/generator.rb +364 -0
- data/lib/reqcord/railtie.rb +51 -0
- data/lib/reqcord/renderers/curl.rb +56 -0
- data/lib/reqcord/renderers/payload.rb +69 -0
- data/lib/reqcord/request_example.rb +104 -0
- data/lib/reqcord/response_example.rb +72 -0
- data/lib/reqcord/route_collector.rb +242 -0
- data/lib/reqcord/sanitizers/sanitizer.rb +140 -0
- data/lib/reqcord/schema.rb +187 -0
- data/lib/reqcord/support.rb +58 -0
- data/lib/reqcord/version.rb +5 -0
- data/lib/reqcord.rb +78 -0
- data/lib/tasks/reqcord.rake +99 -0
- data/reqcord.gemspec +60 -0
- metadata +165 -3
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Reqcord
|
|
2
2
|
|
|
3
|
+
[](https://github.com/ahmetsaridogan/reqcord/actions/workflows/ci.yml)
|
|
4
|
+
|
|
3
5
|
**Turn your Rails integration tests into living API documentation.**
|
|
4
6
|
|
|
5
7
|
Reqcord observes real HTTP requests and responses executed by your Rails test suite and converts them into static, readable API documentation.
|
|
@@ -19,9 +21,7 @@ Minitest Integration Tests
|
|
|
19
21
|
↓
|
|
20
22
|
Canonical Dataset
|
|
21
23
|
↓
|
|
22
|
-
Markdown
|
|
23
|
-
↓
|
|
24
|
-
cURL Examples
|
|
24
|
+
Markdown · cURL · Postman
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
## Why Reqcord?
|
|
@@ -92,13 +92,22 @@ It can generate documentation such as:
|
|
|
92
92
|
| X-Account-Id | `{{account_id}}` |
|
|
93
93
|
| Content-Type | `application/json` |
|
|
94
94
|
|
|
95
|
-
##
|
|
95
|
+
## Body Parameters
|
|
96
|
+
|
|
97
|
+
| Field | Type | Required | Values |
|
|
98
|
+
| --- | --- | --- | --- |
|
|
99
|
+
| `customer.name` | string | yes | `"John Doe"` |
|
|
100
|
+
| `customer.email` | string | yes | `"john@example.com"` |
|
|
101
|
+
| `customer.status` | string | yes | `"active"` \| `"passive"` |
|
|
102
|
+
|
|
103
|
+
## Example Request
|
|
96
104
|
|
|
97
105
|
```json
|
|
98
106
|
{
|
|
99
107
|
"customer": {
|
|
100
108
|
"name": "John Doe",
|
|
101
|
-
"email": "john@example.com"
|
|
109
|
+
"email": "john@example.com",
|
|
110
|
+
"status": "active"
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
113
|
```
|
|
@@ -107,27 +116,34 @@ It can generate documentation such as:
|
|
|
107
116
|
|
|
108
117
|
```bash
|
|
109
118
|
curl --request POST \
|
|
110
|
-
--url "
|
|
119
|
+
--url "http://localhost:3000/api/v2/customers" \
|
|
111
120
|
--header "Authorization: Bearer {{token}}" \
|
|
112
121
|
--header "X-Account-Id: {{account_id}}" \
|
|
113
122
|
--header "Content-Type: application/json" \
|
|
114
123
|
--data '{
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
124
|
+
"customer": {
|
|
125
|
+
"name": "John Doe",
|
|
126
|
+
"email": "john@example.com",
|
|
127
|
+
"status": "active"
|
|
128
|
+
}
|
|
129
|
+
}'
|
|
120
130
|
```
|
|
121
131
|
|
|
122
132
|
## Responses
|
|
123
133
|
|
|
124
134
|
### 201 Created
|
|
125
135
|
|
|
136
|
+
#### Fields
|
|
137
|
+
|
|
138
|
+
| Field | Type | Required | Values |
|
|
139
|
+
| --- | --- | --- | --- |
|
|
140
|
+
| `id` | integer | yes | `42` |
|
|
141
|
+
| `name` | string | yes | `"John Doe"` |
|
|
142
|
+
|
|
126
143
|
```json
|
|
127
144
|
{
|
|
128
145
|
"id": 42,
|
|
129
|
-
"name": "John Doe"
|
|
130
|
-
"email": "john@example.com"
|
|
146
|
+
"name": "John Doe"
|
|
131
147
|
}
|
|
132
148
|
```
|
|
133
149
|
|
|
@@ -139,7 +155,7 @@ curl --request POST \
|
|
|
139
155
|
}
|
|
140
156
|
```
|
|
141
157
|
|
|
142
|
-
### 422 Unprocessable
|
|
158
|
+
### 422 Unprocessable Content
|
|
143
159
|
|
|
144
160
|
```json
|
|
145
161
|
{
|
|
@@ -152,6 +168,28 @@ curl --request POST \
|
|
|
152
168
|
```
|
|
153
169
|
````
|
|
154
170
|
|
|
171
|
+
The parameter tables are inferred from the requests the application
|
|
172
|
+
**accepted**: two passing tests sent `"active"` and `"passive"`, a third sent
|
|
173
|
+
`"inactive"` and got a `422`, so the documentation lists the two values that
|
|
174
|
+
work and keeps the rejection only as a response example. The same run also
|
|
175
|
+
writes a runnable `curl/api/v2/customers/create.sh` and a Postman collection
|
|
176
|
+
with this request and its three saved responses.
|
|
177
|
+
|
|
178
|
+
## Examples
|
|
179
|
+
|
|
180
|
+
Two runnable examples live in [`examples/`](examples):
|
|
181
|
+
|
|
182
|
+
| Example | Test framework | What it shows |
|
|
183
|
+
| --- | --- | --- |
|
|
184
|
+
| [`examples/test-app`](examples/test-app) | Minitest | three small resources: auth, closed value sets, PATCH/PUT folding, a member action |
|
|
185
|
+
| [`examples/spec-app`](examples/spec-app) | RSpec | the same API, documented from request specs |
|
|
186
|
+
| [`examples/complex-test-app`](examples/complex-test-app) | Minitest | a store API: products, cart, orders, nested notes, array bodies, `filter[category]`, a form login, `X-Api-Key` admin namespace, two API versions, 400/403/404/409 |
|
|
187
|
+
| [`examples/complex-spec-app`](examples/complex-spec-app) | RSpec | the store API from request specs |
|
|
188
|
+
|
|
189
|
+
Each one ships the documentation it generates, so you can read the output
|
|
190
|
+
before running anything. [`examples/reqcord.yml`](examples/reqcord.yml) is an
|
|
191
|
+
annotated configuration file.
|
|
192
|
+
|
|
155
193
|
## Core Idea
|
|
156
194
|
|
|
157
195
|
Reqcord separates **capturing API behavior** from **rendering documentation**.
|
|
@@ -167,18 +205,26 @@ Minitest ──────► Test Adapter
|
|
|
167
205
|
▼
|
|
168
206
|
Reqcord Dataset
|
|
169
207
|
│
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
│
|
|
174
|
-
▼
|
|
175
|
-
cURL
|
|
208
|
+
┌────────────┼────────────┐
|
|
209
|
+
▼ ▼ ▼
|
|
210
|
+
Markdown cURL Postman
|
|
176
211
|
```
|
|
177
212
|
|
|
178
213
|
The internal dataset is framework-independent and output-independent.
|
|
179
214
|
|
|
180
215
|
This allows Reqcord to support additional test frameworks and documentation formats without coupling them together.
|
|
181
216
|
|
|
217
|
+
## Supported versions
|
|
218
|
+
|
|
219
|
+
| | |
|
|
220
|
+
| --- | --- |
|
|
221
|
+
| Ruby | 3.2, 3.3, 3.4 |
|
|
222
|
+
| Rails | 7.1, 7.2, 8.0, 8.1 |
|
|
223
|
+
| Test frameworks | Minitest integration tests, RSpec request specs |
|
|
224
|
+
|
|
225
|
+
Every Ruby × Rails pair that Rails itself supports runs in CI
|
|
226
|
+
(`gemfiles/rails_*.gemfile`).
|
|
227
|
+
|
|
182
228
|
## Installation
|
|
183
229
|
|
|
184
230
|
Add Reqcord to the development and test groups:
|
|
@@ -211,13 +257,15 @@ docs/
|
|
|
211
257
|
|
|
212
258
|
## Configuration
|
|
213
259
|
|
|
214
|
-
Reqcord reads its configuration from `reqcord.yml` in the project root.
|
|
260
|
+
Reqcord reads its configuration from `reqcord.yml` in the project root. Every
|
|
261
|
+
key, default and environment override is described in
|
|
262
|
+
[docs/configuration.md](docs/configuration.md); the short version:
|
|
215
263
|
|
|
216
264
|
```yaml
|
|
217
265
|
version: 1
|
|
218
266
|
|
|
219
267
|
test:
|
|
220
|
-
framework: minitest
|
|
268
|
+
framework: minitest # or: rspec
|
|
221
269
|
|
|
222
270
|
routes:
|
|
223
271
|
prefix: /api
|
|
@@ -226,7 +274,9 @@ output:
|
|
|
226
274
|
directory: docs/api
|
|
227
275
|
|
|
228
276
|
exporters:
|
|
277
|
+
- curl
|
|
229
278
|
- markdown
|
|
279
|
+
- postman
|
|
230
280
|
|
|
231
281
|
variables:
|
|
232
282
|
base_url: http://localhost:3000
|
|
@@ -279,28 +329,122 @@ Combine filters:
|
|
|
279
329
|
bin/rails reqcord:generate RESOURCE=customers VERSION=v2
|
|
280
330
|
```
|
|
281
331
|
|
|
332
|
+
`reqcord:generate` runs the test suite itself, in a subprocess, with capture
|
|
333
|
+
enabled:
|
|
334
|
+
|
|
335
|
+
```text
|
|
336
|
+
bin/rails reqcord:generate
|
|
337
|
+
|
|
|
338
|
+
+-- collects the application's routes
|
|
339
|
+
|
|
|
340
|
+
+-- runs `test.command` with REQCORD_CAPTURE=1
|
|
341
|
+
| |
|
|
342
|
+
| +-- each request appends a JSON line to the capture file
|
|
343
|
+
|
|
|
344
|
+
+-- reads the capture file, sanitizes, writes the documentation
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Because capture is driven by `REQCORD_CAPTURE` and `REQCORD_CAPTURE_FILE`, an
|
|
348
|
+
ordinary `bin/rails test` patches nothing and writes nothing. The capture file
|
|
349
|
+
is append-only and locked per write, so parallel test workers can share it.
|
|
350
|
+
|
|
351
|
+
Point Reqcord at the tests that exercise the API — a directory is enough, it
|
|
352
|
+
picks the runner (`bin/rails test`, `rspec`, or a plain Ruby runner when the
|
|
353
|
+
project has no `bin/rails`):
|
|
354
|
+
|
|
355
|
+
```yaml
|
|
356
|
+
test:
|
|
357
|
+
framework: minitest
|
|
358
|
+
paths:
|
|
359
|
+
- test/integration
|
|
360
|
+
- test/api
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Or spell the command out; it wins over `paths`, and globs are expanded:
|
|
364
|
+
|
|
365
|
+
```yaml
|
|
366
|
+
test:
|
|
367
|
+
command: bin/rails test test/integration test/api/*_test.rb
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
The run ends with a reconciliation of the whole route table, so nothing can
|
|
371
|
+
go missing quietly:
|
|
372
|
+
|
|
373
|
+
```text
|
|
374
|
+
[reqcord] captured 87 request(s), 85 matched a documented route
|
|
375
|
+
[reqcord] captured a successful 2xx request for 15 of 16 endpoint(s)
|
|
376
|
+
[reqcord] routes: 18 = 15 documented + 1 uncovered + 2 skipped
|
|
377
|
+
[reqcord] skipped 2 route(s) that cannot be documented: 1 redirect, 1 mount
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Every route is in exactly one bucket: *documented* (a test got a `2xx`),
|
|
381
|
+
*uncovered* (listed in the index, no page), or *skipped* with its reason.
|
|
382
|
+
|
|
282
383
|
## Generated Files
|
|
283
384
|
|
|
284
|
-
|
|
385
|
+
Directories follow the controller path, so `admin/customers` and
|
|
386
|
+
`api/v2/customers` never collide:
|
|
285
387
|
|
|
286
388
|
```text
|
|
287
389
|
docs/api/
|
|
288
390
|
├── dataset.json
|
|
289
391
|
├── README.md
|
|
290
|
-
├── customers/
|
|
392
|
+
├── api/v2/customers/
|
|
291
393
|
│ ├── index.md
|
|
292
394
|
│ ├── create.md
|
|
293
395
|
│ ├── show.md
|
|
294
|
-
│
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
396
|
+
│ └── update.md
|
|
397
|
+
├── api/v2/surveys/
|
|
398
|
+
│ ├── index.md
|
|
399
|
+
│ └── list.md
|
|
400
|
+
├── curl/
|
|
401
|
+
│ └── api/v2/customers/
|
|
402
|
+
│ ├── create.sh
|
|
403
|
+
│ └── show.sh
|
|
404
|
+
└── postman/
|
|
405
|
+
└── collection.json
|
|
299
406
|
```
|
|
300
407
|
|
|
301
|
-
`dataset.json` contains Reqcord's normalized representation of the captured
|
|
408
|
+
`dataset.json` contains Reqcord's normalized representation of the captured
|
|
409
|
+
API; every exporter reads that and nothing else.
|
|
410
|
+
|
|
411
|
+
## Route coverage
|
|
302
412
|
|
|
303
|
-
|
|
413
|
+
The documented surface is the route table, not only `resources`. These all
|
|
414
|
+
become endpoints:
|
|
415
|
+
|
|
416
|
+
| Route | Documented as |
|
|
417
|
+
| --- | --- |
|
|
418
|
+
| `resources :customers` | one endpoint per action |
|
|
419
|
+
| `resource :cart` | `GET /cart`, `PATCH /cart` (also `PUT`) |
|
|
420
|
+
| `match "/echo", via: [:get, :post]` | `GET /echo` and `POST /echo` |
|
|
421
|
+
| `match "/anything", via: :all` | one endpoint per verb the tests used |
|
|
422
|
+
| `root to: "home#index"` | `GET /`, titled "Home" |
|
|
423
|
+
| `get "/items(/:id)"` | one endpoint, `:id` optional |
|
|
424
|
+
| `get "/files/*path"` | `path` as a path parameter |
|
|
425
|
+
| `mount Billing => "/billing"` | the engine's own routes, under `/billing` |
|
|
426
|
+
| `namespace :admin { resources :customers }` | `admin/customers/`, apart from `api/v2/customers/` |
|
|
427
|
+
|
|
428
|
+
`redirect(...)` routes and plain Rack mounts cannot be documented from a test;
|
|
429
|
+
they are counted as *skipped* in the report rather than dropped.
|
|
430
|
+
|
|
431
|
+
## Postman and Hoppscotch
|
|
432
|
+
|
|
433
|
+
`postman/collection.json` is a Postman Collection v2.1:
|
|
434
|
+
|
|
435
|
+
* one folder per controller namespace (`Api › V2 › Customers`),
|
|
436
|
+
* one request per documented endpoint, built from the successful captured
|
|
437
|
+
example — JSON bodies as `raw`, form bodies as `urlencoded`,
|
|
438
|
+
* every captured status saved as a response example on that request,
|
|
439
|
+
* collection variables for `base_url` and every placeholder the sanitizer
|
|
440
|
+
wrote (`{{token}}`, `{{api_key}}` …) — Postman's variable syntax is the
|
|
441
|
+
same, so the collection is usable as soon as the variables are filled in,
|
|
442
|
+
* `Authorization: Bearer {{token}}` lifted to collection-level bearer auth;
|
|
443
|
+
requests that were made without credentials are marked `noauth`, so they
|
|
444
|
+
replay exactly as their tests did.
|
|
445
|
+
|
|
446
|
+
Hoppscotch imports Postman v2.1 collections directly: *Import → Postman* and
|
|
447
|
+
point it at the same file.
|
|
304
448
|
|
|
305
449
|
## Request Capture
|
|
306
450
|
|
|
@@ -365,18 +509,18 @@ Responses
|
|
|
365
509
|
└── 422 Unprocessable Entity
|
|
366
510
|
```
|
|
367
511
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
For example:
|
|
512
|
+
Every distinct body captured for a status is kept in `dataset.json`, and the
|
|
513
|
+
fields of a response are inferred from all of them:
|
|
371
514
|
|
|
372
515
|
```text
|
|
373
|
-
422 Unprocessable
|
|
516
|
+
422 Unprocessable Content
|
|
374
517
|
├── Email already exists
|
|
375
518
|
├── Name is required
|
|
376
519
|
└── Invalid phone number
|
|
377
520
|
```
|
|
378
521
|
|
|
379
|
-
|
|
522
|
+
The Markdown page shows one example body per status plus the inferred field
|
|
523
|
+
table; Reqcord does not overwrite one `422` example with another.
|
|
380
524
|
|
|
381
525
|
## Sanitization
|
|
382
526
|
|
|
@@ -408,7 +552,15 @@ Authorization: Bearer {{token}}
|
|
|
408
552
|
|
|
409
553
|
Sensitive headers such as authorization credentials, cookies and API keys are treated specially by Reqcord.
|
|
410
554
|
|
|
411
|
-
Request and response
|
|
555
|
+
Request and response bodies follow the same principle, matched by key at any
|
|
556
|
+
depth:
|
|
557
|
+
|
|
558
|
+
```yaml
|
|
559
|
+
sanitize:
|
|
560
|
+
body:
|
|
561
|
+
password: "{{password}}"
|
|
562
|
+
access_token: "{{token}}"
|
|
563
|
+
```
|
|
412
564
|
|
|
413
565
|
## Canonical Dataset
|
|
414
566
|
|
|
@@ -430,45 +582,43 @@ A simplified endpoint representation looks like:
|
|
|
430
582
|
|
|
431
583
|
```json
|
|
432
584
|
{
|
|
585
|
+
"name": "Create Customer",
|
|
433
586
|
"method": "POST",
|
|
434
587
|
"path": "/api/v2/customers",
|
|
435
|
-
"
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
],
|
|
447
|
-
"response_examples": [
|
|
588
|
+
"controller": "api/v2/customers",
|
|
589
|
+
"action": "create",
|
|
590
|
+
"parameters": {
|
|
591
|
+
"path": [],
|
|
592
|
+
"query": [],
|
|
593
|
+
"body": [
|
|
594
|
+
{ "path": "customer.name", "type": "string", "required": true, "values": ["John Doe"] },
|
|
595
|
+
{ "path": "customer.status", "type": "string", "required": true, "values": ["active", "passive"] }
|
|
596
|
+
]
|
|
597
|
+
},
|
|
598
|
+
"responses": [
|
|
448
599
|
{
|
|
449
|
-
"name": "Created",
|
|
450
600
|
"status": 201,
|
|
451
|
-
"
|
|
452
|
-
"id": 42
|
|
453
|
-
|
|
454
|
-
}
|
|
601
|
+
"schema": [
|
|
602
|
+
{ "path": "id", "type": "integer", "required": true, "values": [42] }
|
|
603
|
+
],
|
|
604
|
+
"example": { "id": 42, "name": "John Doe" }
|
|
455
605
|
},
|
|
456
|
-
{
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
"error": "Unauthorized"
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
]
|
|
606
|
+
{ "status": 401, "schema": [ { "path": "error", "type": "string", "required": true, "values": ["Unauthorized"] } ], "example": { "error": "Unauthorized" } }
|
|
607
|
+
],
|
|
608
|
+
"request_examples": [ "… every captured request, sanitized" ],
|
|
609
|
+
"response_examples": [ "… every captured response, sanitized" ]
|
|
464
610
|
}
|
|
465
611
|
```
|
|
466
612
|
|
|
613
|
+
`parameters` and `responses[].schema` are inferred only from requests the
|
|
614
|
+
application accepted; `request_examples` keeps everything that was captured.
|
|
615
|
+
Routes no test reached are listed separately under `uncovered_routes`.
|
|
616
|
+
|
|
467
617
|
Every dataset contains a schema version so the internal format can evolve safely.
|
|
468
618
|
|
|
469
619
|
```json
|
|
470
620
|
{
|
|
471
|
-
"schema_version":
|
|
621
|
+
"schema_version": 2
|
|
472
622
|
}
|
|
473
623
|
```
|
|
474
624
|
|
|
@@ -478,26 +628,33 @@ Every dataset contains a schema version so the internal format can evolve safely
|
|
|
478
628
|
Reqcord
|
|
479
629
|
├── Configuration
|
|
480
630
|
├── Dataset
|
|
481
|
-
│ ├── Resource
|
|
631
|
+
│ ├── Resource (one controller path, nested directories/folders)
|
|
482
632
|
│ ├── Endpoint
|
|
483
633
|
│ ├── RequestExample
|
|
484
|
-
│
|
|
634
|
+
│ ├── ResponseExample
|
|
635
|
+
│ └── Schema (fields, types, required, closed value sets)
|
|
636
|
+
│
|
|
637
|
+
├── RouteCollector (every route kind, engines walked, skips counted)
|
|
485
638
|
│
|
|
486
|
-
├──
|
|
639
|
+
├── Capture
|
|
640
|
+
│ ├── Collector (NDJSON, one line per exchange)
|
|
641
|
+
│ ├── TestContext
|
|
642
|
+
│ ├── MinitestContext / RSpecContext
|
|
643
|
+
│ └── IntegrationPatch
|
|
487
644
|
│
|
|
488
|
-
├──
|
|
489
|
-
│ └── Minitest
|
|
645
|
+
├── Generator (run tests → dataset → exporters → report)
|
|
490
646
|
│
|
|
491
647
|
├── Sanitizers
|
|
492
|
-
│
|
|
493
|
-
│ ├── RequestBody
|
|
494
|
-
│ └── ResponseBody
|
|
648
|
+
│ └── Sanitizer (headers and bodies)
|
|
495
649
|
│
|
|
496
650
|
├── Renderers
|
|
651
|
+
│ ├── Payload (JSON vs form, nested query flattening)
|
|
497
652
|
│ └── Curl
|
|
498
653
|
│
|
|
499
654
|
└── Exporters
|
|
500
|
-
|
|
655
|
+
├── Markdown
|
|
656
|
+
├── Curl (one .sh per endpoint)
|
|
657
|
+
└── Postman (Collection v2.1, also for Hoppscotch)
|
|
501
658
|
```
|
|
502
659
|
|
|
503
660
|
Test adapters are responsible only for converting test execution into Reqcord's canonical model.
|
|
@@ -508,9 +665,9 @@ Exporters know nothing about Minitest or Rails test internals.
|
|
|
508
665
|
Minitest ──┐
|
|
509
666
|
│
|
|
510
667
|
RSpec ─────┼──► Dataset ──► Markdown
|
|
511
|
-
│ ├─►
|
|
512
|
-
Other ─────┘ ├─► Postman
|
|
513
|
-
└─►
|
|
668
|
+
│ ├─► cURL
|
|
669
|
+
Other ─────┘ ├─► Postman (→ Hoppscotch)
|
|
670
|
+
└─► OpenAPI (0.2)
|
|
514
671
|
```
|
|
515
672
|
|
|
516
673
|
## v0.1 Scope
|
|
@@ -520,28 +677,28 @@ The first Reqcord release focuses on proving the capture pipeline.
|
|
|
520
677
|
### Included
|
|
521
678
|
|
|
522
679
|
* Rails 8
|
|
523
|
-
* Minitest integration
|
|
680
|
+
* Minitest integration tests
|
|
681
|
+
* RSpec request specs
|
|
524
682
|
* Rails route discovery
|
|
525
683
|
* `reqcord.yml`
|
|
526
684
|
* Request capture
|
|
527
685
|
* Response capture
|
|
528
686
|
* Multiple response scenarios
|
|
529
687
|
* Sensitive data sanitization
|
|
530
|
-
* Canonical `dataset.json`
|
|
688
|
+
* Canonical `dataset.json` with inferred request parameters and response fields
|
|
531
689
|
* Markdown documentation
|
|
532
|
-
* Generated cURL requests
|
|
690
|
+
* Generated cURL requests (in the Markdown and as runnable `.sh` files)
|
|
691
|
+
* Postman Collection v2.1 (imports into Hoppscotch as well)
|
|
692
|
+
* The whole route table: custom actions, `match via:`, `via: :all`,
|
|
693
|
+
singular resources, optional segments and globs, mounted engines
|
|
533
694
|
* Resource filtering
|
|
534
695
|
* API version filtering
|
|
535
696
|
|
|
536
697
|
### Not included yet
|
|
537
698
|
|
|
538
|
-
* RSpec adapter
|
|
539
699
|
* OpenAPI generation
|
|
540
700
|
* Scalar integration
|
|
541
|
-
* Postman collections
|
|
542
|
-
* Hoppscotch collections
|
|
543
701
|
* Multipart requests
|
|
544
|
-
* Advanced schema inference
|
|
545
702
|
* CI documentation drift detection
|
|
546
703
|
|
|
547
704
|
These features belong to later releases rather than expanding the initial scope.
|
|
@@ -568,22 +725,13 @@ http://localhost:3000/api-docs
|
|
|
568
725
|
|
|
569
726
|
### v0.3
|
|
570
727
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
Both test frameworks will produce the exact same Reqcord dataset:
|
|
574
|
-
|
|
575
|
-
```text
|
|
576
|
-
Minitest ─┐
|
|
577
|
-
├──► Reqcord Dataset
|
|
578
|
-
RSpec ────┘
|
|
579
|
-
```
|
|
728
|
+
Rack::Test capture, so frameworks other than Rails (Sinatra, Roda, Hanami) can
|
|
729
|
+
be documented from the same dataset.
|
|
580
730
|
|
|
581
731
|
### Future
|
|
582
732
|
|
|
583
733
|
Potential exporters and integrations include:
|
|
584
734
|
|
|
585
|
-
* Postman
|
|
586
|
-
* Hoppscotch
|
|
587
735
|
* Bruno
|
|
588
736
|
* Insomnia
|
|
589
737
|
* `llms.txt`
|
|
@@ -619,10 +767,19 @@ Reqcord is currently in early development.
|
|
|
619
767
|
|
|
620
768
|
The initial goal is intentionally narrow:
|
|
621
769
|
|
|
622
|
-
> Capture real Rails API requests and responses from
|
|
770
|
+
> Capture real Rails API requests and responses from the test suite and generate accurate, sanitized Markdown documentation, executable cURL examples and a Postman collection — without the developer writing any of them by hand.
|
|
623
771
|
|
|
624
772
|
Once that pipeline is reliable, additional adapters and exporters can be built on top of the same dataset.
|
|
625
773
|
|
|
626
774
|
## License
|
|
627
775
|
|
|
628
776
|
Reqcord is available as open source under the terms of the MIT License.
|
|
777
|
+
|
|
778
|
+
## cURL source of truth
|
|
779
|
+
|
|
780
|
+
Reqcord does not invent request payloads. For Rails integration tests, the
|
|
781
|
+
arguments passed to `get`, `post`, `put`, `patch`, and `delete` are captured at
|
|
782
|
+
runtime. Generated cURL commands use a successful `2xx` test case whenever one
|
|
783
|
+
exists, including its concrete URL, request headers, query parameters, and
|
|
784
|
+
payload. Error-case payloads remain available as examples but do not replace
|
|
785
|
+
the canonical successful request.
|
data/Rakefile
ADDED