reqcord 0.1.4 → 0.3.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.
data/README.md CHANGED
@@ -1,97 +1,108 @@
1
1
  # Reqcord
2
2
 
3
3
  [![CI](https://github.com/ahmetsaridogan/reqcord/actions/workflows/ci.yml/badge.svg)](https://github.com/ahmetsaridogan/reqcord/actions/workflows/ci.yml)
4
+ [![Gem](https://img.shields.io/gem/v/reqcord)](https://rubygems.org/gems/reqcord)
5
+
6
+ **Turn your Rails integration tests into API documentation.**
7
+
8
+ Reqcord runs your test suite, watches the HTTP requests and responses the
9
+ tests make, and writes the documentation from what it saw — Markdown,
10
+ runnable cURL, a Postman collection and an OpenAPI document. No DSL, no
11
+ annotations, no second copy of every request: the tests are the source of
12
+ truth.
13
+
14
+ ```mermaid
15
+ flowchart LR
16
+ subgraph tests["Your integration tests"]
17
+ direction TB
18
+ T1["creates customer<br/>POST /api/v2/customers → 201"]
19
+ T2["rejects unknown status<br/>POST /api/v2/customers → 422"]
20
+ T3["requires authentication<br/>GET /api/v2/customers → 401"]
21
+ end
22
+
23
+ R["Rails route table"]
24
+
25
+ subgraph reqcord["Reqcord"]
26
+ direction TB
27
+ C["capture · sanitize · infer"]
28
+ D[("dataset.json")]
29
+ C --> D
30
+ end
31
+
32
+ subgraph out["Generated"]
33
+ direction TB
34
+ MD["Markdown pages"]
35
+ CU["cURL scripts"]
36
+ PM["Postman collection<br/>(Hoppscotch)"]
37
+ OA["OpenAPI 3.1"]
38
+ end
39
+
40
+ SC["Scalar<br/>mount Reqcord::Web"]
41
+
42
+ tests --> C
43
+ R --> C
44
+ D --> MD
45
+ D --> CU
46
+ D --> PM
47
+ D --> OA
48
+ OA --> SC
49
+ ```
50
+
51
+ ## Quick start
4
52
 
5
- **Turn your Rails integration tests into living API documentation.**
6
-
7
- Reqcord observes real HTTP requests and responses executed by your Rails test suite and converts them into static, readable API documentation.
53
+ ```ruby
54
+ # Gemfile
55
+ group :development, :test do
56
+ gem "reqcord"
57
+ end
58
+ ```
8
59
 
9
- Instead of maintaining API documentation separately from your tests, Reqcord uses the requests your application already executes as the source of truth.
60
+ ```bash
61
+ bundle install
62
+ bin/rails reqcord:init # writes reqcord.yml — point test.paths at your API tests
63
+ bin/rails reqcord:generate # runs them with capture on, writes docs/api/
64
+ ```
10
65
 
11
66
  ```text
12
- Rails Routes
13
- +
14
- Minitest Integration Tests
15
-
16
- Request Capture
17
-
18
- Response Capture
19
-
20
- Sanitization
21
-
22
- Canonical Dataset
23
-
24
- Markdown · cURL · Postman
67
+ [reqcord] captured 87 request(s), 85 matched a documented route
68
+ [reqcord] captured a successful 2xx request for 15 of 16 endpoint(s)
69
+ [reqcord] routes: 18 = 15 documented + 1 uncovered + 2 skipped
25
70
  ```
26
71
 
27
- ## Why Reqcord?
28
-
29
- API documentation tends to drift away from the application it describes.
30
-
31
- A request changes.
32
-
33
- A header is added.
34
-
35
- A validation rule changes.
72
+ Every route ends in exactly one bucket, so nothing goes missing quietly. In
73
+ CI, `bin/rails reqcord:check` fails when the committed docs are behind the
74
+ tests.
36
75
 
37
- A new `422` response appears.
76
+ Optionally, browse it inside the app with Scalar:
38
77
 
39
- The tests are updated, but the documentation is forgotten.
40
-
41
- Reqcord takes a different approach:
42
-
43
- > If your tests already know how to call your API, they already contain most of the information required to document it.
44
-
45
- Reqcord captures that information and turns it into static API documentation.
46
-
47
- No separate documentation DSL.
48
-
49
- No duplicate request definitions.
50
-
51
- No manually maintained cURL examples.
52
-
53
- Your tests remain normal Rails tests.
78
+ ```ruby
79
+ # config/routes.rb
80
+ mount Reqcord::Web => "/api-docs" if Rails.env.development?
81
+ ```
54
82
 
55
- ## Example
83
+ ## What you get
56
84
 
57
- Given an existing Rails integration test:
85
+ From an ordinary test
58
86
 
59
87
  ```ruby
60
88
  test "creates customer" do
61
89
  post "/api/v2/customers",
62
- params: {
63
- customer: {
64
- name: "John Doe",
65
- email: "john@example.com"
66
- }
67
- },
68
- headers: {
69
- "Authorization" => "Bearer test-token",
70
- "X-Account-Id" => "42"
71
- },
90
+ params: { customer: { name: "John Doe", email: "john@example.com", status: "active" } },
91
+ headers: { "Authorization" => "Bearer test-token" },
72
92
  as: :json
73
93
 
74
94
  assert_response :created
75
95
  end
76
96
  ```
77
97
 
78
- Reqcord captures the request and response while the test executes.
79
-
80
- It can generate documentation such as:
98
+ a page like this, plus a `create.sh`, a Postman request and an OpenAPI
99
+ operation built from the same captured request:
81
100
 
82
101
  ````markdown
83
102
  # Create Customer
84
103
 
85
104
  `POST /api/v2/customers`
86
105
 
87
- ## Headers
88
-
89
- | Header | Value |
90
- | --- | --- |
91
- | Authorization | `Bearer {{token}}` |
92
- | X-Account-Id | `{{account_id}}` |
93
- | Content-Type | `application/json` |
94
-
95
106
  ## Body Parameters
96
107
 
97
108
  | Field | Type | Required | Values |
@@ -100,119 +111,57 @@ It can generate documentation such as:
100
111
  | `customer.email` | string | yes | `"john@example.com"` |
101
112
  | `customer.status` | string | yes | `"active"` \| `"passive"` |
102
113
 
103
- ## Example Request
104
-
105
- ```json
106
- {
107
- "customer": {
108
- "name": "John Doe",
109
- "email": "john@example.com",
110
- "status": "active"
111
- }
112
- }
113
- ```
114
-
115
114
  ## cURL
116
115
 
117
116
  ```bash
118
117
  curl --request POST \
119
118
  --url "http://localhost:3000/api/v2/customers" \
120
119
  --header "Authorization: Bearer {{token}}" \
121
- --header "X-Account-Id: {{account_id}}" \
122
120
  --header "Content-Type: application/json" \
123
- --data '{
124
- "customer": {
125
- "name": "John Doe",
126
- "email": "john@example.com",
127
- "status": "active"
128
- }
129
- }'
121
+ --data '{"customer":{"name":"John Doe","email":"john@example.com","status":"active"}}'
130
122
  ```
131
123
 
132
124
  ## Responses
133
125
 
134
126
  ### 201 Created
135
-
136
- #### Fields
137
-
138
- | Field | Type | Required | Values |
139
- | --- | --- | --- | --- |
140
- | `id` | integer | yes | `42` |
141
- | `name` | string | yes | `"John Doe"` |
142
-
143
- ```json
144
- {
145
- "id": 42,
146
- "name": "John Doe"
147
- }
148
- ```
149
-
150
127
  ### 401 Unauthorized
151
-
152
- ```json
153
- {
154
- "error": "Unauthorized"
155
- }
156
- ```
157
-
158
128
  ### 422 Unprocessable Content
159
-
160
- ```json
161
- {
162
- "errors": {
163
- "email": [
164
- "has already been taken"
165
- ]
166
- }
167
- }
168
- ```
169
129
  ````
170
130
 
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):
131
+ The parameter table is inferred from the requests the application
132
+ **accepted**: two tests sent `"active"` and `"passive"`, a third sent
133
+ `"inactive"` and got a `422`, so the page lists the two values that work and
134
+ keeps the rejection as a response example. Credentials never reach a file —
135
+ `Bearer test-token` became `Bearer {{token}}` before anything was stored.
181
136
 
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.
137
+ ## Documentation
192
138
 
193
- ## Core Idea
139
+ | | |
140
+ | --- | --- |
141
+ | [Getting started](docs/getting-started.md) | install, configure, generate, read the output |
142
+ | [Configuration](docs/configuration.md) | every key in `reqcord.yml`, defaults and environment overrides |
143
+ | [Exporters](docs/exporters.md) | Markdown, cURL, Postman / Hoppscotch, OpenAPI — what each contains |
144
+ | [Reqcord::Web](docs/web.md) | serve the docs from the app with Scalar |
145
+ | [Route coverage](docs/route-coverage.md) | how the route table becomes endpoints; `resource`, `match via:`, engines, filters |
146
+ | [Capture and inference](docs/capture.md) | what is captured, sanitization, how parameter tables and response fields are derived, the dataset |
147
+ | [Troubleshooting](docs/troubleshooting.md) | when the output looks thin |
148
+ | [Architecture](docs/architecture.md) | pipeline, modules, design principles, working on Reqcord |
149
+ | [Changelog](CHANGELOG.md) | |
194
150
 
195
- Reqcord separates **capturing API behavior** from **rendering documentation**.
151
+ ## Examples
196
152
 
197
- ```text
198
- Rails Routes
199
-
200
-
201
- Route Collector
202
-
203
- Minitest ──────► Test Adapter
204
-
205
-
206
- Reqcord Dataset
207
-
208
- ┌────────────┼────────────┐
209
- ▼ ▼ ▼
210
- Markdown cURL Postman
211
- ```
153
+ Four runnable applications under [`examples/`](examples), each with the
154
+ documentation it generates committed next to it:
212
155
 
213
- The internal dataset is framework-independent and output-independent.
156
+ | Example | Tests | Shows |
157
+ | --- | --- | --- |
158
+ | [`test-app`](examples/test-app) | Minitest | three small resources: auth, closed value sets, PATCH/PUT folding, a member action |
159
+ | [`spec-app`](examples/spec-app) | RSpec | the same API from request specs |
160
+ | [`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 |
161
+ | [`complex-spec-app`](examples/complex-spec-app) | RSpec | the store API from request specs |
214
162
 
215
- This allows Reqcord to support additional test frameworks and documentation formats without coupling them together.
163
+ [`examples/reqcord.yml`](examples/reqcord.yml) is an annotated configuration
164
+ file.
216
165
 
217
166
  ## Supported versions
218
167
 
@@ -220,566 +169,19 @@ This allows Reqcord to support additional test frameworks and documentation form
220
169
  | --- | --- |
221
170
  | Ruby | 3.2, 3.3, 3.4 |
222
171
  | 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
-
228
- ## Installation
229
-
230
- Add Reqcord to the development and test groups:
231
-
232
- ```ruby
233
- group :development, :test do
234
- gem "reqcord"
235
- end
236
- ```
237
-
238
- Then run:
239
-
240
- ```bash
241
- bundle install
242
- ```
243
-
244
- Initialize Reqcord:
245
-
246
- ```bash
247
- bin/rails reqcord:init
248
- ```
249
-
250
- This creates:
251
-
252
- ```text
253
- reqcord.yml
254
- docs/
255
- └── api/
256
- ```
257
-
258
- ## Configuration
259
-
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:
263
-
264
- ```yaml
265
- version: 1
266
-
267
- test:
268
- framework: minitest # or: rspec
269
-
270
- routes:
271
- prefix: /api
272
-
273
- output:
274
- directory: docs/api
275
-
276
- exporters:
277
- - curl
278
- - markdown
279
- - postman
280
-
281
- variables:
282
- base_url: http://localhost:3000
283
-
284
- sanitize:
285
- headers:
286
- Authorization: "Bearer {{token}}"
287
- X-Account-Id: "{{account_id}}"
288
- ```
289
-
290
- Configuration precedence:
291
-
292
- ```text
293
- CLI / Environment
294
-
295
- reqcord.yml
296
-
297
- Reqcord defaults
298
- ```
299
-
300
- ## Generating Documentation
301
-
302
- Generate documentation for the entire API:
303
-
304
- ```bash
305
- bin/rails reqcord:generate
306
- ```
307
-
308
- Generate documentation for a specific resource:
309
-
310
- ```bash
311
- bin/rails reqcord:generate RESOURCE=customers
312
- ```
313
-
314
- Multiple resources:
315
-
316
- ```bash
317
- bin/rails reqcord:generate RESOURCE=customers,surveys
318
- ```
319
-
320
- Filter by API version:
321
-
322
- ```bash
323
- bin/rails reqcord:generate VERSION=v2
324
- ```
325
-
326
- Combine filters:
327
-
328
- ```bash
329
- bin/rails reqcord:generate RESOURCE=customers VERSION=v2
330
- ```
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
-
383
- ## Generated Files
384
-
385
- Directories follow the controller path, so `admin/customers` and
386
- `api/v2/customers` never collide:
387
-
388
- ```text
389
- docs/api/
390
- ├── dataset.json
391
- ├── README.md
392
- ├── api/v2/customers/
393
- │ ├── index.md
394
- │ ├── create.md
395
- │ ├── show.md
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
406
- ```
407
-
408
- `dataset.json` contains Reqcord's normalized representation of the captured
409
- API; every exporter reads that and nothing else.
410
-
411
- ## Route coverage
172
+ | Tests | Minitest integration tests, RSpec request specs |
412
173
 
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.
448
-
449
- ## Request Capture
450
-
451
- Reqcord captures HTTP information from Rails integration tests.
452
-
453
- The initial version supports:
454
-
455
- * HTTP method
456
- * Request path
457
- * Path parameters
458
- * Query parameters
459
- * Request headers
460
- * JSON request bodies
461
- * Content type
462
- * Response status
463
- * Response headers
464
- * JSON response bodies
465
- * Test name and source
466
- * Multiple request/response examples per endpoint
467
-
468
- Supported HTTP methods:
469
-
470
- ```text
471
- GET
472
- POST
473
- PUT
474
- PATCH
475
- DELETE
476
- ```
477
-
478
- ## Multiple Responses
479
-
480
- Reqcord does not assume that an endpoint has only one response.
481
-
482
- For example:
483
-
484
- ```ruby
485
- test "creates customer" do
486
- # ...
487
- assert_response :created
488
- end
489
-
490
- test "requires authentication" do
491
- # ...
492
- assert_response :unauthorized
493
- end
174
+ Every Ruby × Rails pair Rails itself supports runs in CI.
494
175
 
495
- test "rejects duplicate email" do
496
- # ...
497
- assert_response :unprocessable_entity
498
- end
499
- ```
500
-
501
- can produce:
502
-
503
- ```text
504
- POST /api/v2/customers
505
-
506
- Responses
507
- ├── 201 Created
508
- ├── 401 Unauthorized
509
- └── 422 Unprocessable Entity
510
- ```
511
-
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:
514
-
515
- ```text
516
- 422 Unprocessable Content
517
- ├── Email already exists
518
- ├── Name is required
519
- └── Invalid phone number
520
- ```
521
-
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.
524
-
525
- ## Sanitization
526
-
527
- Captured tests may contain credentials or other sensitive values.
528
-
529
- Reqcord must never blindly write those values into generated documentation.
530
-
531
- Sensitive headers can be replaced with variables:
532
-
533
- ```yaml
534
- sanitize:
535
- headers:
536
- Authorization: "Bearer {{token}}"
537
- X-Api-Key: "{{api_key}}"
538
- X-Account-Id: "{{account_id}}"
539
- ```
540
-
541
- A captured request such as:
542
-
543
- ```text
544
- Authorization: Bearer eyJhbGciOi...
545
- ```
546
-
547
- becomes:
548
-
549
- ```text
550
- Authorization: Bearer {{token}}
551
- ```
552
-
553
- Sensitive headers such as authorization credentials, cookies and API keys are treated specially by Reqcord.
554
-
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
- ```
176
+ ## Principles
564
177
 
565
- ## Canonical Dataset
566
-
567
- Reqcord does not directly convert Minitest tests into Markdown.
568
-
569
- Instead:
570
-
571
- ```text
572
- Minitest
573
-
574
- Test Adapter
575
-
576
- Canonical Dataset
577
-
578
- Exporter
579
- ```
580
-
581
- A simplified endpoint representation looks like:
582
-
583
- ```json
584
- {
585
- "name": "Create Customer",
586
- "method": "POST",
587
- "path": "/api/v2/customers",
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": [
599
- {
600
- "status": 201,
601
- "schema": [
602
- { "path": "id", "type": "integer", "required": true, "values": [42] }
603
- ],
604
- "example": { "id": 42, "name": "John Doe" }
605
- },
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" ]
610
- }
611
- ```
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
-
617
- Every dataset contains a schema version so the internal format can evolve safely.
618
-
619
- ```json
620
- {
621
- "schema_version": 2
622
- }
623
- ```
624
-
625
- ## Architecture
626
-
627
- ```text
628
- Reqcord
629
- ├── Configuration
630
- ├── Dataset
631
- │ ├── Resource (one controller path, nested directories/folders)
632
- │ ├── Endpoint
633
- │ ├── RequestExample
634
- │ ├── ResponseExample
635
- │ └── Schema (fields, types, required, closed value sets)
636
-
637
- ├── RouteCollector (every route kind, engines walked, skips counted)
638
-
639
- ├── Capture
640
- │ ├── Collector (NDJSON, one line per exchange)
641
- │ ├── TestContext
642
- │ ├── MinitestContext / RSpecContext
643
- │ └── IntegrationPatch
644
-
645
- ├── Generator (run tests → dataset → exporters → report)
646
-
647
- ├── Sanitizers
648
- │ └── Sanitizer (headers and bodies)
649
-
650
- ├── Renderers
651
- │ ├── Payload (JSON vs form, nested query flattening)
652
- │ └── Curl
653
-
654
- └── Exporters
655
- ├── Markdown
656
- ├── Curl (one .sh per endpoint)
657
- └── Postman (Collection v2.1, also for Hoppscotch)
658
- ```
659
-
660
- Test adapters are responsible only for converting test execution into Reqcord's canonical model.
661
-
662
- Exporters know nothing about Minitest or Rails test internals.
663
-
664
- ```text
665
- Minitest ──┐
666
-
667
- RSpec ─────┼──► Dataset ──► Markdown
668
- │ ├─► cURL
669
- Other ─────┘ ├─► Postman (→ Hoppscotch)
670
- └─► OpenAPI (0.2)
671
- ```
672
-
673
- ## v0.1 Scope
674
-
675
- The first Reqcord release focuses on proving the capture pipeline.
676
-
677
- ### Included
678
-
679
- * Rails 8
680
- * Minitest integration tests
681
- * RSpec request specs
682
- * Rails route discovery
683
- * `reqcord.yml`
684
- * Request capture
685
- * Response capture
686
- * Multiple response scenarios
687
- * Sensitive data sanitization
688
- * Canonical `dataset.json` with inferred request parameters and response fields
689
- * Markdown documentation
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
694
- * Resource filtering
695
- * API version filtering
696
-
697
- ### Not included yet
698
-
699
- * OpenAPI generation
700
- * Scalar integration
701
- * Multipart requests
702
- * CI documentation drift detection
703
-
704
- These features belong to later releases rather than expanding the initial scope.
705
-
706
- ## Roadmap
707
-
708
- ### v0.2
709
-
710
- OpenAPI 3.1 export and Scalar integration.
711
-
712
- ```text
713
- Reqcord Dataset
714
-
715
- OpenAPI 3.1
716
-
717
- Scalar
718
- ```
719
-
720
- This will allow a development application to expose documentation such as:
721
-
722
- ```text
723
- http://localhost:3000/api-docs
724
- ```
725
-
726
- ### v0.3
727
-
728
- Rack::Test capture, so frameworks other than Rails (Sinatra, Roda, Hanami) can
729
- be documented from the same dataset.
730
-
731
- ### Future
732
-
733
- Potential exporters and integrations include:
734
-
735
- * Bruno
736
- * Insomnia
737
- * `llms.txt`
738
- * Static HTML documentation
739
- * JSON Schema
740
- * CI documentation drift detection
741
-
742
- ## Design Principles
743
-
744
- **Tests are the source of truth.**
745
-
746
- Reqcord should observe existing tests instead of forcing developers to rewrite them using a documentation-specific DSL.
747
-
748
- **Capture once, export anywhere.**
749
-
750
- Test execution produces a framework-independent dataset. Exporters operate exclusively on that dataset.
751
-
752
- **Generated documentation must be safe.**
753
-
754
- Credentials and sensitive data must not leak into generated files.
755
-
756
- **Generated documentation must be useful without a server.**
757
-
758
- Markdown and cURL output should remain readable directly from GitHub or a local checkout.
759
-
760
- **Adapters stay isolated.**
761
-
762
- Minitest, RSpec, Markdown, OpenAPI and other integrations should not depend directly on each other.
763
-
764
- ## Status
765
-
766
- Reqcord is currently in early development.
767
-
768
- The initial goal is intentionally narrow:
769
-
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.
771
-
772
- Once that pipeline is reliable, additional adapters and exporters can be built on top of the same dataset.
178
+ * **Tests are the source of truth.** Reqcord never reads models, serializers
179
+ or contracts; what the application accepted and answered is the spec.
180
+ * **Capture once, export anywhere.** One dataset, any number of formats.
181
+ * **Nothing is lost silently.** `routes = documented + uncovered + skipped`,
182
+ reconciled on every run.
183
+ * **Generated docs are safe.** Sanitization runs before anything is stored.
773
184
 
774
185
  ## License
775
186
 
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.
187
+ MIT.