reqcord 0.1.0 → 0.1.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.
Files changed (249) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/CHANGELOG.md +77 -0
  4. data/Gemfile +5 -0
  5. data/Gemfile.lock +171 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +239 -95
  8. data/Rakefile +13 -0
  9. data/docs/configuration.md +319 -0
  10. data/examples/complex-spec-app/.rspec +1 -0
  11. data/examples/complex-spec-app/README.md +21 -0
  12. data/examples/complex-spec-app/app.rb +363 -0
  13. data/examples/complex-spec-app/docs/api/README.md +104 -0
  14. data/examples/complex-spec-app/docs/api/api/v1/admin/products/create.md +117 -0
  15. data/examples/complex-spec-app/docs/api/api/v1/admin/products/destroy.md +51 -0
  16. data/examples/complex-spec-app/docs/api/api/v1/admin/products/index.md +11 -0
  17. data/examples/complex-spec-app/docs/api/api/v1/admin/products/list.md +106 -0
  18. data/examples/complex-spec-app/docs/api/api/v1/auth/index.md +9 -0
  19. data/examples/complex-spec-app/docs/api/api/v1/auth/login.md +76 -0
  20. data/examples/complex-spec-app/docs/api/api/v1/cart-items/create.md +91 -0
  21. data/examples/complex-spec-app/docs/api/api/v1/cart-items/destroy.md +51 -0
  22. data/examples/complex-spec-app/docs/api/api/v1/cart-items/index.md +10 -0
  23. data/examples/complex-spec-app/docs/api/api/v1/carts/checkout.md +102 -0
  24. data/examples/complex-spec-app/docs/api/api/v1/carts/index.md +10 -0
  25. data/examples/complex-spec-app/docs/api/api/v1/carts/show.md +73 -0
  26. data/examples/complex-spec-app/docs/api/api/v1/home/index.md +9 -0
  27. data/examples/complex-spec-app/docs/api/api/v1/home/list.md +47 -0
  28. data/examples/complex-spec-app/docs/api/api/v1/notes/create.md +94 -0
  29. data/examples/complex-spec-app/docs/api/api/v1/notes/index.md +10 -0
  30. data/examples/complex-spec-app/docs/api/api/v1/notes/list.md +61 -0
  31. data/examples/complex-spec-app/docs/api/api/v1/orders/cancel.md +69 -0
  32. data/examples/complex-spec-app/docs/api/api/v1/orders/create.md +150 -0
  33. data/examples/complex-spec-app/docs/api/api/v1/orders/destroy.md +37 -0
  34. data/examples/complex-spec-app/docs/api/api/v1/orders/index.md +13 -0
  35. data/examples/complex-spec-app/docs/api/api/v1/orders/list.md +78 -0
  36. data/examples/complex-spec-app/docs/api/api/v1/orders/show.md +83 -0
  37. data/examples/complex-spec-app/docs/api/api/v1/products/index.md +11 -0
  38. data/examples/complex-spec-app/docs/api/api/v1/products/list.md +83 -0
  39. data/examples/complex-spec-app/docs/api/api/v1/products/search.md +88 -0
  40. data/examples/complex-spec-app/docs/api/api/v1/products/show.md +71 -0
  41. data/examples/complex-spec-app/docs/api/api/v1/profiles/index.md +10 -0
  42. data/examples/complex-spec-app/docs/api/api/v1/profiles/show.md +61 -0
  43. data/examples/complex-spec-app/docs/api/api/v1/profiles/update.md +91 -0
  44. data/examples/complex-spec-app/docs/api/api/v2/products/index.md +9 -0
  45. data/examples/complex-spec-app/docs/api/api/v2/products/list.md +77 -0
  46. data/examples/complex-spec-app/docs/api/curl/api/v1/admin/products/create.sh +18 -0
  47. data/examples/complex-spec-app/docs/api/curl/api/v1/admin/products/destroy.sh +9 -0
  48. data/examples/complex-spec-app/docs/api/curl/api/v1/admin/products/list.sh +9 -0
  49. data/examples/complex-spec-app/docs/api/curl/api/v1/auth/login.sh +9 -0
  50. data/examples/complex-spec-app/docs/api/curl/api/v1/cart-items/create.sh +16 -0
  51. data/examples/complex-spec-app/docs/api/curl/api/v1/cart-items/destroy.sh +9 -0
  52. data/examples/complex-spec-app/docs/api/curl/api/v1/carts/checkout.sh +13 -0
  53. data/examples/complex-spec-app/docs/api/curl/api/v1/carts/show.sh +9 -0
  54. data/examples/complex-spec-app/docs/api/curl/api/v1/home/list.sh +8 -0
  55. data/examples/complex-spec-app/docs/api/curl/api/v1/notes/create.sh +15 -0
  56. data/examples/complex-spec-app/docs/api/curl/api/v1/notes/list.sh +9 -0
  57. data/examples/complex-spec-app/docs/api/curl/api/v1/orders/cancel.sh +9 -0
  58. data/examples/complex-spec-app/docs/api/curl/api/v1/orders/create.sh +29 -0
  59. data/examples/complex-spec-app/docs/api/curl/api/v1/orders/destroy.sh +9 -0
  60. data/examples/complex-spec-app/docs/api/curl/api/v1/orders/list.sh +9 -0
  61. data/examples/complex-spec-app/docs/api/curl/api/v1/orders/show.sh +9 -0
  62. data/examples/complex-spec-app/docs/api/curl/api/v1/products/list.sh +8 -0
  63. data/examples/complex-spec-app/docs/api/curl/api/v1/products/search.sh +8 -0
  64. data/examples/complex-spec-app/docs/api/curl/api/v1/products/show.sh +8 -0
  65. data/examples/complex-spec-app/docs/api/curl/api/v1/profiles/show.sh +9 -0
  66. data/examples/complex-spec-app/docs/api/curl/api/v1/profiles/update.sh +16 -0
  67. data/examples/complex-spec-app/docs/api/curl/api/v2/products/list.sh +8 -0
  68. data/examples/complex-spec-app/docs/api/dataset.json +5116 -0
  69. data/examples/complex-spec-app/docs/api/postman/collection.json +2519 -0
  70. data/examples/complex-spec-app/generate.rb +14 -0
  71. data/examples/complex-spec-app/reqcord.yml +28 -0
  72. data/examples/complex-spec-app/spec/requests/admin_spec.rb +67 -0
  73. data/examples/complex-spec-app/spec/requests/cart_spec.rb +61 -0
  74. data/examples/complex-spec-app/spec/requests/home_and_auth_spec.rb +49 -0
  75. data/examples/complex-spec-app/spec/requests/orders_spec.rb +124 -0
  76. data/examples/complex-spec-app/spec/requests/products_spec.rb +65 -0
  77. data/examples/complex-spec-app/spec/spec_helper.rb +17 -0
  78. data/examples/complex-test-app/README.md +40 -0
  79. data/examples/complex-test-app/app.rb +384 -0
  80. data/examples/complex-test-app/docs/api/README.md +104 -0
  81. data/examples/complex-test-app/docs/api/api/v1/admin/products/create.md +117 -0
  82. data/examples/complex-test-app/docs/api/api/v1/admin/products/destroy.md +51 -0
  83. data/examples/complex-test-app/docs/api/api/v1/admin/products/index.md +11 -0
  84. data/examples/complex-test-app/docs/api/api/v1/admin/products/list.md +106 -0
  85. data/examples/complex-test-app/docs/api/api/v1/auth/index.md +9 -0
  86. data/examples/complex-test-app/docs/api/api/v1/auth/login.md +76 -0
  87. data/examples/complex-test-app/docs/api/api/v1/cart-items/create.md +91 -0
  88. data/examples/complex-test-app/docs/api/api/v1/cart-items/destroy.md +51 -0
  89. data/examples/complex-test-app/docs/api/api/v1/cart-items/index.md +10 -0
  90. data/examples/complex-test-app/docs/api/api/v1/carts/checkout.md +102 -0
  91. data/examples/complex-test-app/docs/api/api/v1/carts/index.md +10 -0
  92. data/examples/complex-test-app/docs/api/api/v1/carts/show.md +73 -0
  93. data/examples/complex-test-app/docs/api/api/v1/home/index.md +9 -0
  94. data/examples/complex-test-app/docs/api/api/v1/home/list.md +47 -0
  95. data/examples/complex-test-app/docs/api/api/v1/notes/create.md +94 -0
  96. data/examples/complex-test-app/docs/api/api/v1/notes/index.md +10 -0
  97. data/examples/complex-test-app/docs/api/api/v1/notes/list.md +61 -0
  98. data/examples/complex-test-app/docs/api/api/v1/orders/cancel.md +69 -0
  99. data/examples/complex-test-app/docs/api/api/v1/orders/create.md +150 -0
  100. data/examples/complex-test-app/docs/api/api/v1/orders/destroy.md +37 -0
  101. data/examples/complex-test-app/docs/api/api/v1/orders/index.md +13 -0
  102. data/examples/complex-test-app/docs/api/api/v1/orders/list.md +71 -0
  103. data/examples/complex-test-app/docs/api/api/v1/orders/show.md +83 -0
  104. data/examples/complex-test-app/docs/api/api/v1/products/index.md +11 -0
  105. data/examples/complex-test-app/docs/api/api/v1/products/list.md +83 -0
  106. data/examples/complex-test-app/docs/api/api/v1/products/search.md +88 -0
  107. data/examples/complex-test-app/docs/api/api/v1/products/show.md +71 -0
  108. data/examples/complex-test-app/docs/api/api/v1/profiles/index.md +10 -0
  109. data/examples/complex-test-app/docs/api/api/v1/profiles/show.md +61 -0
  110. data/examples/complex-test-app/docs/api/api/v1/profiles/update.md +91 -0
  111. data/examples/complex-test-app/docs/api/api/v2/products/index.md +9 -0
  112. data/examples/complex-test-app/docs/api/api/v2/products/list.md +77 -0
  113. data/examples/complex-test-app/docs/api/curl/api/v1/admin/products/create.sh +18 -0
  114. data/examples/complex-test-app/docs/api/curl/api/v1/admin/products/destroy.sh +9 -0
  115. data/examples/complex-test-app/docs/api/curl/api/v1/admin/products/list.sh +9 -0
  116. data/examples/complex-test-app/docs/api/curl/api/v1/auth/login.sh +9 -0
  117. data/examples/complex-test-app/docs/api/curl/api/v1/cart-items/create.sh +16 -0
  118. data/examples/complex-test-app/docs/api/curl/api/v1/cart-items/destroy.sh +9 -0
  119. data/examples/complex-test-app/docs/api/curl/api/v1/carts/checkout.sh +13 -0
  120. data/examples/complex-test-app/docs/api/curl/api/v1/carts/show.sh +9 -0
  121. data/examples/complex-test-app/docs/api/curl/api/v1/home/list.sh +8 -0
  122. data/examples/complex-test-app/docs/api/curl/api/v1/notes/create.sh +15 -0
  123. data/examples/complex-test-app/docs/api/curl/api/v1/notes/list.sh +9 -0
  124. data/examples/complex-test-app/docs/api/curl/api/v1/orders/cancel.sh +9 -0
  125. data/examples/complex-test-app/docs/api/curl/api/v1/orders/create.sh +29 -0
  126. data/examples/complex-test-app/docs/api/curl/api/v1/orders/destroy.sh +9 -0
  127. data/examples/complex-test-app/docs/api/curl/api/v1/orders/list.sh +9 -0
  128. data/examples/complex-test-app/docs/api/curl/api/v1/orders/show.sh +9 -0
  129. data/examples/complex-test-app/docs/api/curl/api/v1/products/list.sh +8 -0
  130. data/examples/complex-test-app/docs/api/curl/api/v1/products/search.sh +8 -0
  131. data/examples/complex-test-app/docs/api/curl/api/v1/products/show.sh +8 -0
  132. data/examples/complex-test-app/docs/api/curl/api/v1/profiles/show.sh +9 -0
  133. data/examples/complex-test-app/docs/api/curl/api/v1/profiles/update.sh +16 -0
  134. data/examples/complex-test-app/docs/api/curl/api/v2/products/list.sh +8 -0
  135. data/examples/complex-test-app/docs/api/dataset.json +5109 -0
  136. data/examples/complex-test-app/docs/api/postman/collection.json +2568 -0
  137. data/examples/complex-test-app/generate.rb +14 -0
  138. data/examples/complex-test-app/reqcord.yml +28 -0
  139. data/examples/complex-test-app/test/integration/admin_test.rb +70 -0
  140. data/examples/complex-test-app/test/integration/cart_test.rb +64 -0
  141. data/examples/complex-test-app/test/integration/home_and_auth_test.rb +53 -0
  142. data/examples/complex-test-app/test/integration/orders_test.rb +127 -0
  143. data/examples/complex-test-app/test/integration/products_test.rb +66 -0
  144. data/examples/reqcord.yml +58 -0
  145. data/examples/spec-app/.rspec +1 -0
  146. data/examples/spec-app/README.md +36 -0
  147. data/examples/spec-app/app.rb +209 -0
  148. data/examples/spec-app/docs/api/README.md +45 -0
  149. data/examples/spec-app/docs/api/api/v1/customers/create.md +95 -0
  150. data/examples/spec-app/docs/api/api/v1/customers/index.md +11 -0
  151. data/examples/spec-app/docs/api/api/v1/customers/list.md +67 -0
  152. data/examples/spec-app/docs/api/api/v1/customers/show.md +65 -0
  153. data/examples/spec-app/docs/api/api/v1/tasks/complete.md +51 -0
  154. data/examples/spec-app/docs/api/api/v1/tasks/create.md +90 -0
  155. data/examples/spec-app/docs/api/api/v1/tasks/destroy.md +35 -0
  156. data/examples/spec-app/docs/api/api/v1/tasks/index.md +14 -0
  157. data/examples/spec-app/docs/api/api/v1/tasks/list.md +65 -0
  158. data/examples/spec-app/docs/api/api/v1/tasks/show.md +65 -0
  159. data/examples/spec-app/docs/api/api/v1/tasks/update.md +74 -0
  160. data/examples/spec-app/docs/api/api/v1/users/create.md +95 -0
  161. data/examples/spec-app/docs/api/api/v1/users/index.md +11 -0
  162. data/examples/spec-app/docs/api/api/v1/users/list.md +67 -0
  163. data/examples/spec-app/docs/api/api/v1/users/show.md +65 -0
  164. data/examples/spec-app/docs/api/curl/api/v1/customers/create.sh +17 -0
  165. data/examples/spec-app/docs/api/curl/api/v1/customers/list.sh +9 -0
  166. data/examples/spec-app/docs/api/curl/api/v1/customers/show.sh +9 -0
  167. data/examples/spec-app/docs/api/curl/api/v1/tasks/complete.sh +8 -0
  168. data/examples/spec-app/docs/api/curl/api/v1/tasks/create.sh +15 -0
  169. data/examples/spec-app/docs/api/curl/api/v1/tasks/destroy.sh +8 -0
  170. data/examples/spec-app/docs/api/curl/api/v1/tasks/list.sh +8 -0
  171. data/examples/spec-app/docs/api/curl/api/v1/tasks/show.sh +8 -0
  172. data/examples/spec-app/docs/api/curl/api/v1/tasks/update.sh +14 -0
  173. data/examples/spec-app/docs/api/curl/api/v1/users/create.sh +17 -0
  174. data/examples/spec-app/docs/api/curl/api/v1/users/list.sh +9 -0
  175. data/examples/spec-app/docs/api/curl/api/v1/users/show.sh +9 -0
  176. data/examples/spec-app/docs/api/dataset.json +2402 -0
  177. data/examples/spec-app/docs/api/postman/collection.json +1290 -0
  178. data/examples/spec-app/generate.rb +14 -0
  179. data/examples/spec-app/reqcord.yml +27 -0
  180. data/examples/spec-app/spec/requests/customers_spec.rb +71 -0
  181. data/examples/spec-app/spec/requests/tasks_spec.rb +78 -0
  182. data/examples/spec-app/spec/requests/users_spec.rb +68 -0
  183. data/examples/spec-app/spec/spec_helper.rb +17 -0
  184. data/examples/test-app/README.md +57 -0
  185. data/examples/test-app/app.rb +212 -0
  186. data/examples/test-app/docs/api/README.md +45 -0
  187. data/examples/test-app/docs/api/api/v1/customers/create.md +95 -0
  188. data/examples/test-app/docs/api/api/v1/customers/index.md +11 -0
  189. data/examples/test-app/docs/api/api/v1/customers/list.md +67 -0
  190. data/examples/test-app/docs/api/api/v1/customers/show.md +65 -0
  191. data/examples/test-app/docs/api/api/v1/tasks/complete.md +51 -0
  192. data/examples/test-app/docs/api/api/v1/tasks/create.md +90 -0
  193. data/examples/test-app/docs/api/api/v1/tasks/destroy.md +35 -0
  194. data/examples/test-app/docs/api/api/v1/tasks/index.md +14 -0
  195. data/examples/test-app/docs/api/api/v1/tasks/list.md +65 -0
  196. data/examples/test-app/docs/api/api/v1/tasks/show.md +65 -0
  197. data/examples/test-app/docs/api/api/v1/tasks/update.md +74 -0
  198. data/examples/test-app/docs/api/api/v1/users/create.md +95 -0
  199. data/examples/test-app/docs/api/api/v1/users/index.md +11 -0
  200. data/examples/test-app/docs/api/api/v1/users/list.md +67 -0
  201. data/examples/test-app/docs/api/api/v1/users/show.md +65 -0
  202. data/examples/test-app/docs/api/curl/api/v1/customers/create.sh +17 -0
  203. data/examples/test-app/docs/api/curl/api/v1/customers/list.sh +9 -0
  204. data/examples/test-app/docs/api/curl/api/v1/customers/show.sh +9 -0
  205. data/examples/test-app/docs/api/curl/api/v1/tasks/complete.sh +8 -0
  206. data/examples/test-app/docs/api/curl/api/v1/tasks/create.sh +15 -0
  207. data/examples/test-app/docs/api/curl/api/v1/tasks/destroy.sh +8 -0
  208. data/examples/test-app/docs/api/curl/api/v1/tasks/list.sh +8 -0
  209. data/examples/test-app/docs/api/curl/api/v1/tasks/show.sh +8 -0
  210. data/examples/test-app/docs/api/curl/api/v1/tasks/update.sh +14 -0
  211. data/examples/test-app/docs/api/curl/api/v1/users/create.sh +17 -0
  212. data/examples/test-app/docs/api/curl/api/v1/users/list.sh +9 -0
  213. data/examples/test-app/docs/api/curl/api/v1/users/show.sh +9 -0
  214. data/examples/test-app/docs/api/dataset.json +2402 -0
  215. data/examples/test-app/docs/api/postman/collection.json +1290 -0
  216. data/examples/test-app/generate.rb +14 -0
  217. data/examples/test-app/reqcord.yml +28 -0
  218. data/examples/test-app/test/integration/customers_test.rb +72 -0
  219. data/examples/test-app/test/integration/tasks_test.rb +80 -0
  220. data/examples/test-app/test/integration/users_test.rb +72 -0
  221. data/lib/reqcord/capture/collector.rb +31 -0
  222. data/lib/reqcord/capture/integration_patch.rb +209 -0
  223. data/lib/reqcord/capture/minitest_context.rb +34 -0
  224. data/lib/reqcord/capture/rspec_context.rb +42 -0
  225. data/lib/reqcord/capture/test_context.rb +25 -0
  226. data/lib/reqcord/capture.rb +19 -0
  227. data/lib/reqcord/configuration.rb +198 -0
  228. data/lib/reqcord/dataset.rb +176 -0
  229. data/lib/reqcord/endpoint.rb +263 -0
  230. data/lib/reqcord/errors.rb +9 -0
  231. data/lib/reqcord/exporters/curl.rb +68 -0
  232. data/lib/reqcord/exporters/markdown.rb +295 -0
  233. data/lib/reqcord/exporters/postman.rb +206 -0
  234. data/lib/reqcord/exporters.rb +32 -0
  235. data/lib/reqcord/generator.rb +364 -0
  236. data/lib/reqcord/railtie.rb +51 -0
  237. data/lib/reqcord/renderers/curl.rb +56 -0
  238. data/lib/reqcord/renderers/payload.rb +69 -0
  239. data/lib/reqcord/request_example.rb +104 -0
  240. data/lib/reqcord/response_example.rb +72 -0
  241. data/lib/reqcord/route_collector.rb +242 -0
  242. data/lib/reqcord/sanitizers/sanitizer.rb +140 -0
  243. data/lib/reqcord/schema.rb +187 -0
  244. data/lib/reqcord/support.rb +58 -0
  245. data/lib/reqcord/version.rb +5 -0
  246. data/lib/reqcord.rb +78 -0
  247. data/lib/tasks/reqcord.rake +99 -0
  248. data/reqcord.gemspec +46 -0
  249. metadata +317 -3
data/README.md CHANGED
@@ -19,9 +19,7 @@ Minitest Integration Tests
19
19
 
20
20
  Canonical Dataset
21
21
 
22
- Markdown API Docs
23
-
24
- cURL Examples
22
+ Markdown · cURL · Postman
25
23
  ```
26
24
 
27
25
  ## Why Reqcord?
@@ -92,13 +90,22 @@ It can generate documentation such as:
92
90
  | X-Account-Id | `{{account_id}}` |
93
91
  | Content-Type | `application/json` |
94
92
 
95
- ## Request Body
93
+ ## Body Parameters
94
+
95
+ | Field | Type | Required | Values |
96
+ | --- | --- | --- | --- |
97
+ | `customer.name` | string | yes | `"John Doe"` |
98
+ | `customer.email` | string | yes | `"john@example.com"` |
99
+ | `customer.status` | string | yes | `"active"` \| `"passive"` |
100
+
101
+ ## Example Request
96
102
 
97
103
  ```json
98
104
  {
99
105
  "customer": {
100
106
  "name": "John Doe",
101
- "email": "john@example.com"
107
+ "email": "john@example.com",
108
+ "status": "active"
102
109
  }
103
110
  }
104
111
  ```
@@ -107,27 +114,34 @@ It can generate documentation such as:
107
114
 
108
115
  ```bash
109
116
  curl --request POST \
110
- --url "{{base_url}}/api/v2/customers" \
117
+ --url "http://localhost:3000/api/v2/customers" \
111
118
  --header "Authorization: Bearer {{token}}" \
112
119
  --header "X-Account-Id: {{account_id}}" \
113
120
  --header "Content-Type: application/json" \
114
121
  --data '{
115
- "customer": {
116
- "name": "John Doe",
117
- "email": "john@example.com"
118
- }
119
- }'
122
+ "customer": {
123
+ "name": "John Doe",
124
+ "email": "john@example.com",
125
+ "status": "active"
126
+ }
127
+ }'
120
128
  ```
121
129
 
122
130
  ## Responses
123
131
 
124
132
  ### 201 Created
125
133
 
134
+ #### Fields
135
+
136
+ | Field | Type | Required | Values |
137
+ | --- | --- | --- | --- |
138
+ | `id` | integer | yes | `42` |
139
+ | `name` | string | yes | `"John Doe"` |
140
+
126
141
  ```json
127
142
  {
128
143
  "id": 42,
129
- "name": "John Doe",
130
- "email": "john@example.com"
144
+ "name": "John Doe"
131
145
  }
132
146
  ```
133
147
 
@@ -139,7 +153,7 @@ curl --request POST \
139
153
  }
140
154
  ```
141
155
 
142
- ### 422 Unprocessable Entity
156
+ ### 422 Unprocessable Content
143
157
 
144
158
  ```json
145
159
  {
@@ -152,6 +166,28 @@ curl --request POST \
152
166
  ```
153
167
  ````
154
168
 
169
+ The parameter tables are inferred from the requests the application
170
+ **accepted**: two passing tests sent `"active"` and `"passive"`, a third sent
171
+ `"inactive"` and got a `422`, so the documentation lists the two values that
172
+ work and keeps the rejection only as a response example. The same run also
173
+ writes a runnable `curl/api/v2/customers/create.sh` and a Postman collection
174
+ with this request and its three saved responses.
175
+
176
+ ## Examples
177
+
178
+ Two runnable examples live in [`examples/`](examples):
179
+
180
+ | Example | Test framework | What it shows |
181
+ | --- | --- | --- |
182
+ | [`examples/test-app`](examples/test-app) | Minitest | three small resources: auth, closed value sets, PATCH/PUT folding, a member action |
183
+ | [`examples/spec-app`](examples/spec-app) | RSpec | the same API, documented from request specs |
184
+ | [`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 |
185
+ | [`examples/complex-spec-app`](examples/complex-spec-app) | RSpec | the store API from request specs |
186
+
187
+ Each one ships the documentation it generates, so you can read the output
188
+ before running anything. [`examples/reqcord.yml`](examples/reqcord.yml) is an
189
+ annotated configuration file.
190
+
155
191
  ## Core Idea
156
192
 
157
193
  Reqcord separates **capturing API behavior** from **rendering documentation**.
@@ -167,12 +203,9 @@ Minitest ──────► Test Adapter
167
203
 
168
204
  Reqcord Dataset
169
205
 
170
- ┌─────────┴─────────┐
171
-
172
- Markdown JSON
173
-
174
-
175
- cURL
206
+ ┌────────────┼────────────┐
207
+
208
+ Markdown cURL Postman
176
209
  ```
177
210
 
178
211
  The internal dataset is framework-independent and output-independent.
@@ -211,13 +244,15 @@ docs/
211
244
 
212
245
  ## Configuration
213
246
 
214
- Reqcord reads its configuration from `reqcord.yml` in the project root.
247
+ Reqcord reads its configuration from `reqcord.yml` in the project root. Every
248
+ key, default and environment override is described in
249
+ [docs/configuration.md](docs/configuration.md); the short version:
215
250
 
216
251
  ```yaml
217
252
  version: 1
218
253
 
219
254
  test:
220
- framework: minitest
255
+ framework: minitest # or: rspec
221
256
 
222
257
  routes:
223
258
  prefix: /api
@@ -226,7 +261,9 @@ output:
226
261
  directory: docs/api
227
262
 
228
263
  exporters:
264
+ - curl
229
265
  - markdown
266
+ - postman
230
267
 
231
268
  variables:
232
269
  base_url: http://localhost:3000
@@ -279,28 +316,122 @@ Combine filters:
279
316
  bin/rails reqcord:generate RESOURCE=customers VERSION=v2
280
317
  ```
281
318
 
319
+ `reqcord:generate` runs the test suite itself, in a subprocess, with capture
320
+ enabled:
321
+
322
+ ```text
323
+ bin/rails reqcord:generate
324
+ |
325
+ +-- collects the application's routes
326
+ |
327
+ +-- runs `test.command` with REQCORD_CAPTURE=1
328
+ | |
329
+ | +-- each request appends a JSON line to the capture file
330
+ |
331
+ +-- reads the capture file, sanitizes, writes the documentation
332
+ ```
333
+
334
+ Because capture is driven by `REQCORD_CAPTURE` and `REQCORD_CAPTURE_FILE`, an
335
+ ordinary `bin/rails test` patches nothing and writes nothing. The capture file
336
+ is append-only and locked per write, so parallel test workers can share it.
337
+
338
+ Point Reqcord at the tests that exercise the API — a directory is enough, it
339
+ picks the runner (`bin/rails test`, `rspec`, or a plain Ruby runner when the
340
+ project has no `bin/rails`):
341
+
342
+ ```yaml
343
+ test:
344
+ framework: minitest
345
+ paths:
346
+ - test/integration
347
+ - test/api
348
+ ```
349
+
350
+ Or spell the command out; it wins over `paths`, and globs are expanded:
351
+
352
+ ```yaml
353
+ test:
354
+ command: bin/rails test test/integration test/api/*_test.rb
355
+ ```
356
+
357
+ The run ends with a reconciliation of the whole route table, so nothing can
358
+ go missing quietly:
359
+
360
+ ```text
361
+ [reqcord] captured 87 request(s), 85 matched a documented route
362
+ [reqcord] captured a successful 2xx request for 15 of 16 endpoint(s)
363
+ [reqcord] routes: 18 = 15 documented + 1 uncovered + 2 skipped
364
+ [reqcord] skipped 2 route(s) that cannot be documented: 1 redirect, 1 mount
365
+ ```
366
+
367
+ Every route is in exactly one bucket: *documented* (a test got a `2xx`),
368
+ *uncovered* (listed in the index, no page), or *skipped* with its reason.
369
+
282
370
  ## Generated Files
283
371
 
284
- A typical output looks like:
372
+ Directories follow the controller path, so `admin/customers` and
373
+ `api/v2/customers` never collide:
285
374
 
286
375
  ```text
287
376
  docs/api/
288
377
  ├── dataset.json
289
378
  ├── README.md
290
- ├── customers/
379
+ ├── api/v2/customers/
291
380
  │ ├── index.md
292
381
  │ ├── create.md
293
382
  │ ├── show.md
294
- ├── update.md
295
- │ └── destroy.md
296
- └── surveys/
297
- ├── index.md
298
- └── create.md
383
+ └── update.md
384
+ ├── api/v2/surveys/
385
+ │ ├── index.md
386
+ │ └── list.md
387
+ ├── curl/
388
+ │ └── api/v2/customers/
389
+ │ ├── create.sh
390
+ │ └── show.sh
391
+ └── postman/
392
+ └── collection.json
299
393
  ```
300
394
 
301
- `dataset.json` contains Reqcord's normalized representation of the captured API.
395
+ `dataset.json` contains Reqcord's normalized representation of the captured
396
+ API; every exporter reads that and nothing else.
397
+
398
+ ## Route coverage
399
+
400
+ The documented surface is the route table, not only `resources`. These all
401
+ become endpoints:
302
402
 
303
- Markdown files are generated from that dataset.
403
+ | Route | Documented as |
404
+ | --- | --- |
405
+ | `resources :customers` | one endpoint per action |
406
+ | `resource :cart` | `GET /cart`, `PATCH /cart` (also `PUT`) |
407
+ | `match "/echo", via: [:get, :post]` | `GET /echo` and `POST /echo` |
408
+ | `match "/anything", via: :all` | one endpoint per verb the tests used |
409
+ | `root to: "home#index"` | `GET /`, titled "Home" |
410
+ | `get "/items(/:id)"` | one endpoint, `:id` optional |
411
+ | `get "/files/*path"` | `path` as a path parameter |
412
+ | `mount Billing => "/billing"` | the engine's own routes, under `/billing` |
413
+ | `namespace :admin { resources :customers }` | `admin/customers/`, apart from `api/v2/customers/` |
414
+
415
+ `redirect(...)` routes and plain Rack mounts cannot be documented from a test;
416
+ they are counted as *skipped* in the report rather than dropped.
417
+
418
+ ## Postman and Hoppscotch
419
+
420
+ `postman/collection.json` is a Postman Collection v2.1:
421
+
422
+ * one folder per controller namespace (`Api › V2 › Customers`),
423
+ * one request per documented endpoint, built from the successful captured
424
+ example — JSON bodies as `raw`, form bodies as `urlencoded`,
425
+ * every captured status saved as a response example on that request,
426
+ * collection variables for `base_url` and every placeholder the sanitizer
427
+ wrote (`{{token}}`, `{{api_key}}` …) — Postman's variable syntax is the
428
+ same, so the collection is usable as soon as the variables are filled in,
429
+ * `Authorization: Bearer {{token}}` lifted to collection-level bearer auth;
430
+ requests that were made without credentials are marked `noauth`, so they
431
+ replay exactly as their tests did.
432
+
433
+ Hoppscotch imports Postman v2.1 collections directly: *Import → Postman* and
434
+ point it at the same file.
304
435
 
305
436
  ## Request Capture
306
437
 
@@ -365,18 +496,18 @@ Responses
365
496
  └── 422 Unprocessable Entity
366
497
  ```
367
498
 
368
- Multiple examples for the same status code are also preserved.
369
-
370
- For example:
499
+ Every distinct body captured for a status is kept in `dataset.json`, and the
500
+ fields of a response are inferred from all of them:
371
501
 
372
502
  ```text
373
- 422 Unprocessable Entity
503
+ 422 Unprocessable Content
374
504
  ├── Email already exists
375
505
  ├── Name is required
376
506
  └── Invalid phone number
377
507
  ```
378
508
 
379
- Reqcord does not overwrite one `422` example with another.
509
+ The Markdown page shows one example body per status plus the inferred field
510
+ table; Reqcord does not overwrite one `422` example with another.
380
511
 
381
512
  ## Sanitization
382
513
 
@@ -408,7 +539,15 @@ Authorization: Bearer {{token}}
408
539
 
409
540
  Sensitive headers such as authorization credentials, cookies and API keys are treated specially by Reqcord.
410
541
 
411
- Request and response body sanitization will follow the same principle.
542
+ Request and response bodies follow the same principle, matched by key at any
543
+ depth:
544
+
545
+ ```yaml
546
+ sanitize:
547
+ body:
548
+ password: "{{password}}"
549
+ access_token: "{{token}}"
550
+ ```
412
551
 
413
552
  ## Canonical Dataset
414
553
 
@@ -430,45 +569,43 @@ A simplified endpoint representation looks like:
430
569
 
431
570
  ```json
432
571
  {
572
+ "name": "Create Customer",
433
573
  "method": "POST",
434
574
  "path": "/api/v2/customers",
435
- "request_examples": [
436
- {
437
- "headers": {
438
- "Authorization": "Bearer {{token}}"
439
- },
440
- "body": {
441
- "customer": {
442
- "name": "John Doe"
443
- }
444
- }
445
- }
446
- ],
447
- "response_examples": [
575
+ "controller": "api/v2/customers",
576
+ "action": "create",
577
+ "parameters": {
578
+ "path": [],
579
+ "query": [],
580
+ "body": [
581
+ { "path": "customer.name", "type": "string", "required": true, "values": ["John Doe"] },
582
+ { "path": "customer.status", "type": "string", "required": true, "values": ["active", "passive"] }
583
+ ]
584
+ },
585
+ "responses": [
448
586
  {
449
- "name": "Created",
450
587
  "status": 201,
451
- "body": {
452
- "id": 42,
453
- "name": "John Doe"
454
- }
588
+ "schema": [
589
+ { "path": "id", "type": "integer", "required": true, "values": [42] }
590
+ ],
591
+ "example": { "id": 42, "name": "John Doe" }
455
592
  },
456
- {
457
- "name": "Unauthorized",
458
- "status": 401,
459
- "body": {
460
- "error": "Unauthorized"
461
- }
462
- }
463
- ]
593
+ { "status": 401, "schema": [ { "path": "error", "type": "string", "required": true, "values": ["Unauthorized"] } ], "example": { "error": "Unauthorized" } }
594
+ ],
595
+ "request_examples": [ "… every captured request, sanitized" ],
596
+ "response_examples": [ "… every captured response, sanitized" ]
464
597
  }
465
598
  ```
466
599
 
600
+ `parameters` and `responses[].schema` are inferred only from requests the
601
+ application accepted; `request_examples` keeps everything that was captured.
602
+ Routes no test reached are listed separately under `uncovered_routes`.
603
+
467
604
  Every dataset contains a schema version so the internal format can evolve safely.
468
605
 
469
606
  ```json
470
607
  {
471
- "schema_version": 1
608
+ "schema_version": 2
472
609
  }
473
610
  ```
474
611
 
@@ -478,26 +615,33 @@ Every dataset contains a schema version so the internal format can evolve safely
478
615
  Reqcord
479
616
  ├── Configuration
480
617
  ├── Dataset
481
- │ ├── Resource
618
+ │ ├── Resource (one controller path, nested directories/folders)
482
619
  │ ├── Endpoint
483
620
  │ ├── RequestExample
484
- └── ResponseExample
621
+ ├── ResponseExample
622
+ │ └── Schema (fields, types, required, closed value sets)
485
623
 
486
- ├── RouteCollector
624
+ ├── RouteCollector (every route kind, engines walked, skips counted)
487
625
 
488
- ├── TestAdapters
489
- └── Minitest
626
+ ├── Capture
627
+ ├── Collector (NDJSON, one line per exchange)
628
+ │ ├── TestContext
629
+ │ ├── MinitestContext / RSpecContext
630
+ │ └── IntegrationPatch
631
+
632
+ ├── Generator (run tests → dataset → exporters → report)
490
633
 
491
634
  ├── Sanitizers
492
- ├── Headers
493
- │ ├── RequestBody
494
- │ └── ResponseBody
635
+ └── Sanitizer (headers and bodies)
495
636
 
496
637
  ├── Renderers
638
+ │ ├── Payload (JSON vs form, nested query flattening)
497
639
  │ └── Curl
498
640
 
499
641
  └── Exporters
500
- └── Markdown
642
+ ├── Markdown
643
+ ├── Curl (one .sh per endpoint)
644
+ └── Postman (Collection v2.1, also for Hoppscotch)
501
645
  ```
502
646
 
503
647
  Test adapters are responsible only for converting test execution into Reqcord's canonical model.
@@ -508,9 +652,9 @@ Exporters know nothing about Minitest or Rails test internals.
508
652
  Minitest ──┐
509
653
 
510
654
  RSpec ─────┼──► Dataset ──► Markdown
511
- │ ├─► OpenAPI
512
- Other ─────┘ ├─► Postman
513
- └─► ...
655
+ │ ├─► cURL
656
+ Other ─────┘ ├─► Postman (→ Hoppscotch)
657
+ └─► OpenAPI (0.2)
514
658
  ```
515
659
 
516
660
  ## v0.1 Scope
@@ -520,28 +664,28 @@ The first Reqcord release focuses on proving the capture pipeline.
520
664
  ### Included
521
665
 
522
666
  * Rails 8
523
- * Minitest integration/request tests
667
+ * Minitest integration tests
668
+ * RSpec request specs
524
669
  * Rails route discovery
525
670
  * `reqcord.yml`
526
671
  * Request capture
527
672
  * Response capture
528
673
  * Multiple response scenarios
529
674
  * Sensitive data sanitization
530
- * Canonical `dataset.json`
675
+ * Canonical `dataset.json` with inferred request parameters and response fields
531
676
  * Markdown documentation
532
- * Generated cURL requests
677
+ * Generated cURL requests (in the Markdown and as runnable `.sh` files)
678
+ * Postman Collection v2.1 (imports into Hoppscotch as well)
679
+ * The whole route table: custom actions, `match via:`, `via: :all`,
680
+ singular resources, optional segments and globs, mounted engines
533
681
  * Resource filtering
534
682
  * API version filtering
535
683
 
536
684
  ### Not included yet
537
685
 
538
- * RSpec adapter
539
686
  * OpenAPI generation
540
687
  * Scalar integration
541
- * Postman collections
542
- * Hoppscotch collections
543
688
  * Multipart requests
544
- * Advanced schema inference
545
689
  * CI documentation drift detection
546
690
 
547
691
  These features belong to later releases rather than expanding the initial scope.
@@ -568,22 +712,13 @@ http://localhost:3000/api-docs
568
712
 
569
713
  ### v0.3
570
714
 
571
- RSpec request spec adapter.
572
-
573
- Both test frameworks will produce the exact same Reqcord dataset:
574
-
575
- ```text
576
- Minitest ─┐
577
- ├──► Reqcord Dataset
578
- RSpec ────┘
579
- ```
715
+ Rack::Test capture, so frameworks other than Rails (Sinatra, Roda, Hanami) can
716
+ be documented from the same dataset.
580
717
 
581
718
  ### Future
582
719
 
583
720
  Potential exporters and integrations include:
584
721
 
585
- * Postman
586
- * Hoppscotch
587
722
  * Bruno
588
723
  * Insomnia
589
724
  * `llms.txt`
@@ -619,10 +754,19 @@ Reqcord is currently in early development.
619
754
 
620
755
  The initial goal is intentionally narrow:
621
756
 
622
- > Capture real Rails API requests and responses from Minitest and generate accurate, sanitized Markdown documentation with executable cURL examples.
757
+ > 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
758
 
624
759
  Once that pipeline is reliable, additional adapters and exporters can be built on top of the same dataset.
625
760
 
626
761
  ## License
627
762
 
628
763
  Reqcord is available as open source under the terms of the MIT License.
764
+
765
+ ## cURL source of truth
766
+
767
+ Reqcord does not invent request payloads. For Rails integration tests, the
768
+ arguments passed to `get`, `post`, `put`, `patch`, and `delete` are captured at
769
+ runtime. Generated cURL commands use a successful `2xx` test case whenever one
770
+ exists, including its concrete URL, request headers, query parameters, and
771
+ payload. Error-case payloads remain available as examples but do not replace
772
+ the canonical successful request.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new do |task|
7
+ task.libs << "test"
8
+ task.libs << "lib"
9
+
10
+ task.pattern = "test/**/*_test.rb"
11
+ end
12
+
13
+ task default: :test