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
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A tiny Rails API, in one file, so the example stays readable. In a real
4
+ # application this is just your app; Reqcord does not care how it is built.
5
+ #
6
+ # The same three resources as test-app — customers, users, tasks — so the two
7
+ # examples can be compared endpoint for endpoint.
8
+ ENV["RAILS_ENV"] ||= "test"
9
+
10
+ # Only needed because this example lives inside the Reqcord repository.
11
+ # In your own application, `gem "reqcord"` in the Gemfile is enough.
12
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
13
+
14
+ require "rails"
15
+ require "action_controller/railtie"
16
+ require "reqcord"
17
+
18
+ class ExampleApp < Rails::Application
19
+ config.root = __dir__
20
+ config.eager_load = false
21
+ config.logger = Logger.new(IO::NULL)
22
+ config.secret_key_base = "a" * 64
23
+ config.hosts.clear
24
+ end
25
+
26
+ Rails.application.initialize!
27
+
28
+ module Api
29
+ module V1
30
+ class CustomersController < ActionController::API
31
+ CUSTOMERS = [
32
+ { id: 1, name: "John Doe", email: "john@example.com" },
33
+ { id: 2, name: "Jane Roe", email: "jane@example.com" }
34
+ ].freeze
35
+
36
+ before_action :authenticate!
37
+
38
+ def index
39
+ render json: CUSTOMERS.first(params.fetch(:per_page, 25).to_i)
40
+ end
41
+
42
+ def show
43
+ customer = CUSTOMERS.find { |record| record[:id] == params[:id].to_i }
44
+
45
+ return render(json: { error: "Not Found" }, status: :not_found) unless customer
46
+
47
+ render json: customer
48
+ end
49
+
50
+ STATUSES = %w[active passive].freeze
51
+
52
+ def create
53
+ if params.dig(:customer, :email).to_s.empty?
54
+ return render(
55
+ json: { errors: { email: ["can't be blank"] } },
56
+ status: :unprocessable_entity
57
+ )
58
+ end
59
+
60
+ unless STATUSES.include?(params.dig(:customer, :status).to_s)
61
+ return render(
62
+ json: { errors: { status: ["is not included in the list"] } },
63
+ status: :unprocessable_entity
64
+ )
65
+ end
66
+
67
+ render json: { id: 3, **customer_params }, status: :created
68
+ end
69
+
70
+ private
71
+
72
+ def authenticate!
73
+ return if request.headers["Authorization"].present?
74
+
75
+ render json: { error: "Unauthorized" }, status: :unauthorized
76
+ end
77
+
78
+ def customer_params
79
+ params.require(:customer).permit(:name, :email, :status).to_h.symbolize_keys
80
+ end
81
+ end
82
+
83
+ class UsersController < ActionController::API
84
+ USERS = [
85
+ { id: 1, name: "John Doe", email: "john@example.com" },
86
+ { id: 2, name: "Jane Roe", email: "jane@example.com" }
87
+ ].freeze
88
+
89
+ before_action :authenticate!
90
+
91
+ def index
92
+ render json: USERS.first(params.fetch(:per_page, 25).to_i)
93
+ end
94
+
95
+ def show
96
+ user = USERS.find { |record| record[:id] == params[:id].to_i }
97
+
98
+ return render(json: { error: "Not Found" }, status: :not_found) unless user
99
+
100
+ render json: user
101
+ end
102
+
103
+ STATUSES = %w[active inactive].freeze
104
+
105
+ def create
106
+ if params.dig(:user, :email).to_s.empty?
107
+ return render(
108
+ json: { errors: { email: ["can't be blank"] } },
109
+ status: :unprocessable_entity
110
+ )
111
+ end
112
+
113
+ unless STATUSES.include?(params.dig(:user, :status).to_s)
114
+ return render(
115
+ json: { errors: { status: ["is not included in the list"] } },
116
+ status: :unprocessable_entity
117
+ )
118
+ end
119
+
120
+ render json: { id: 3, **user_params }, status: :created
121
+ end
122
+
123
+ private
124
+
125
+ def authenticate!
126
+ return if request.headers["Authorization"].present?
127
+
128
+ render json: { error: "Unauthorized" }, status: :unauthorized
129
+ end
130
+
131
+ def user_params
132
+ params.require(:user).permit(:name, :email, :status).to_h.symbolize_keys
133
+ end
134
+ end
135
+
136
+ class TasksController < ActionController::API
137
+ TASKS = [
138
+ { id: 1, title: "Write the docs", status: "open", priority: "high" },
139
+ { id: 2, title: "Ship 0.1.1", status: "done", priority: "high" },
140
+ { id: 3, title: "Tidy the backlog", status: "open", priority: "low" }
141
+ ].freeze
142
+
143
+ STATUSES = %w[open done].freeze
144
+ PRIORITIES = %w[low high].freeze
145
+
146
+ def index
147
+ tasks = TASKS
148
+ tasks = tasks.select { |task| task[:status] == params[:status] } if params[:status].present?
149
+
150
+ render json: tasks
151
+ end
152
+
153
+ before_action :find_task, only: %i[show update complete destroy]
154
+
155
+ def show
156
+ render json: @task
157
+ end
158
+
159
+ def create
160
+ if params.dig(:task, :title).to_s.empty?
161
+ return render(json: { errors: { title: ["can't be blank"] } }, status: :unprocessable_entity)
162
+ end
163
+
164
+ unless PRIORITIES.include?(params.dig(:task, :priority).to_s)
165
+ return render(json: { errors: { priority: ["is not included in the list"] } }, status: :unprocessable_entity)
166
+ end
167
+
168
+ render json: { id: 4, status: "open", **task_params }, status: :created
169
+ end
170
+
171
+ def update
172
+ render json: @task.merge(task_params)
173
+ end
174
+
175
+ def complete
176
+ render json: @task.merge(status: "done")
177
+ end
178
+
179
+ def destroy
180
+ head :no_content
181
+ end
182
+
183
+ private
184
+
185
+ # Rendering in a before_action halts the chain, so the action never runs.
186
+ def find_task
187
+ @task = TASKS.find { |task| task[:id] == params[:id].to_i }
188
+
189
+ render(json: { error: "Not Found" }, status: :not_found) unless @task
190
+ end
191
+
192
+ def task_params
193
+ params.require(:task).permit(:title, :priority, :status).to_h.symbolize_keys
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ Rails.application.routes.draw do
200
+ namespace :api do
201
+ namespace :v1 do
202
+ resources :customers, only: %i[index show create]
203
+ resources :users, only: %i[index show create]
204
+ resources :tasks, except: %i[new edit] do
205
+ post :complete, on: :member
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,45 @@
1
+ # API Documentation
2
+
3
+ Generated by Reqcord from the application's integration tests.
4
+
5
+ Base URL: `http://localhost:3000`
6
+
7
+ ## Placeholders
8
+
9
+ | Header | Replace with |
10
+ | --- | --- |
11
+ | Authorization | `Bearer {{token}}` |
12
+ | X-Api-Key | `{{api_key}}` |
13
+
14
+ ## Customers
15
+
16
+ Namespace: `api/v1`
17
+
18
+ | Method | Path | Description |
19
+ | --- | --- | --- |
20
+ | `GET` | [`/api/v1/customers`](api/v1/customers/list.md) | List Customers |
21
+ | `POST` | [`/api/v1/customers`](api/v1/customers/create.md) | Create Customer |
22
+ | `GET` | [`/api/v1/customers/:id`](api/v1/customers/show.md) | Get Customer |
23
+
24
+ ## Tasks
25
+
26
+ Namespace: `api/v1`
27
+
28
+ | Method | Path | Description |
29
+ | --- | --- | --- |
30
+ | `GET` | [`/api/v1/tasks`](api/v1/tasks/list.md) | List Tasks |
31
+ | `POST` | [`/api/v1/tasks`](api/v1/tasks/create.md) | Create Task |
32
+ | `DELETE` | [`/api/v1/tasks/:id`](api/v1/tasks/destroy.md) | Delete Task |
33
+ | `GET` | [`/api/v1/tasks/:id`](api/v1/tasks/show.md) | Get Task |
34
+ | `PATCH` | [`/api/v1/tasks/:id`](api/v1/tasks/update.md) | Update Task |
35
+ | `POST` | [`/api/v1/tasks/:id/complete`](api/v1/tasks/complete.md) | Complete Task |
36
+
37
+ ## Users
38
+
39
+ Namespace: `api/v1`
40
+
41
+ | Method | Path | Description |
42
+ | --- | --- | --- |
43
+ | `GET` | [`/api/v1/users`](api/v1/users/list.md) | List Users |
44
+ | `POST` | [`/api/v1/users`](api/v1/users/create.md) | Create User |
45
+ | `GET` | [`/api/v1/users/:id`](api/v1/users/show.md) | Get User |
@@ -0,0 +1,95 @@
1
+ # Create Customer
2
+
3
+ `POST /api/v1/customers`
4
+
5
+ Namespace: `api/v1`
6
+
7
+ ## Headers
8
+
9
+ | Header | Value |
10
+ | --- | --- |
11
+ | Authorization | `Bearer {{token}}` |
12
+ | Content-Type | `application/json` |
13
+ | Accept | `application/json` |
14
+
15
+ ## Body Parameters
16
+
17
+ | Field | Type | Required | Values |
18
+ | --- | --- | --- | --- |
19
+ | `customer.name` | string | yes | `"Ada Lovelace"` |
20
+ | `customer.email` | string | yes | `"ada@example.com"` |
21
+ | `customer.status` | string | yes | `"active"` \| `"passive"` |
22
+
23
+ ## Example Request
24
+
25
+ ```json
26
+ {
27
+ "customer": {
28
+ "name": "Ada Lovelace",
29
+ "email": "ada@example.com",
30
+ "status": "active"
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## cURL
36
+
37
+ ```bash
38
+ curl --request POST \
39
+ --url "http://localhost:3000/api/v1/customers" \
40
+ --header "Authorization: Bearer {{token}}" \
41
+ --header "Content-Type: application/json" \
42
+ --header "Accept: application/json" \
43
+ --data '{
44
+ "customer": {
45
+ "name": "Ada Lovelace",
46
+ "email": "ada@example.com",
47
+ "status": "active"
48
+ }
49
+ }'
50
+ ```
51
+
52
+ ## Responses
53
+
54
+ ### 201 Created
55
+
56
+ #### Fields
57
+
58
+ | Field | Type | Required | Values |
59
+ | --- | --- | --- | --- |
60
+ | `id` | integer | yes | `3` |
61
+ | `name` | string | yes | `"Ada Lovelace"` |
62
+ | `email` | string | yes | `"ada@example.com"` |
63
+ | `status` | string | yes | `"active"` \| `"passive"` |
64
+
65
+ ```json
66
+ {
67
+ "id": 3,
68
+ "name": "Ada Lovelace",
69
+ "email": "ada@example.com",
70
+ "status": "active"
71
+ }
72
+ ```
73
+
74
+ ### 422 Unprocessable Content
75
+
76
+ #### Fields
77
+
78
+ | Field | Type | Required | Values |
79
+ | --- | --- | --- | --- |
80
+ | `errors.status[]` | string | no | `"is not included in the list"` |
81
+ | `errors.email[]` | string | no | `"can't be blank"` |
82
+
83
+ ```json
84
+ {
85
+ "errors": {
86
+ "status": [
87
+ "is not included in the list"
88
+ ]
89
+ }
90
+ }
91
+ ```
92
+
93
+ ---
94
+
95
+ Resource: [Customers](index.md)
@@ -0,0 +1,11 @@
1
+ # Customers
2
+
3
+ Namespace: `api/v1`
4
+
5
+ API version: `v1`
6
+
7
+ | Method | Path | Description |
8
+ | --- | --- | --- |
9
+ | `GET` | [`/api/v1/customers`](list.md) | List Customers |
10
+ | `POST` | [`/api/v1/customers`](create.md) | Create Customer |
11
+ | `GET` | [`/api/v1/customers/:id`](show.md) | Get Customer |
@@ -0,0 +1,67 @@
1
+ # List Customers
2
+
3
+ `GET /api/v1/customers`
4
+
5
+ Namespace: `api/v1`
6
+
7
+ ## Headers
8
+
9
+ | Header | Value |
10
+ | --- | --- |
11
+ | Authorization | `Bearer {{token}}` |
12
+ | Accept | `application/json` |
13
+
14
+ ## Query Parameters
15
+
16
+ | Field | Type | Required | Values |
17
+ | --- | --- | --- | --- |
18
+ | `per_page` | integer | yes | `1` |
19
+
20
+ ## cURL
21
+
22
+ ```bash
23
+ curl --request GET \
24
+ --url "http://localhost:3000/api/v1/customers?per_page=1" \
25
+ --header "Authorization: Bearer {{token}}" \
26
+ --header "Accept: application/json"
27
+ ```
28
+
29
+ ## Responses
30
+
31
+ ### 200 OK
32
+
33
+ #### Fields
34
+
35
+ | Field | Type | Required | Values |
36
+ | --- | --- | --- | --- |
37
+ | `[].id` | integer | yes | `1` |
38
+ | `[].name` | string | yes | `"John Doe"` |
39
+ | `[].email` | string | yes | `"john@example.com"` |
40
+
41
+ ```json
42
+ [
43
+ {
44
+ "id": 1,
45
+ "name": "John Doe",
46
+ "email": "john@example.com"
47
+ }
48
+ ]
49
+ ```
50
+
51
+ ### 401 Unauthorized
52
+
53
+ #### Fields
54
+
55
+ | Field | Type | Required | Values |
56
+ | --- | --- | --- | --- |
57
+ | `error` | string | yes | `"Unauthorized"` |
58
+
59
+ ```json
60
+ {
61
+ "error": "Unauthorized"
62
+ }
63
+ ```
64
+
65
+ ---
66
+
67
+ Resource: [Customers](index.md)
@@ -0,0 +1,65 @@
1
+ # Get Customer
2
+
3
+ `GET /api/v1/customers/:id`
4
+
5
+ Namespace: `api/v1`
6
+
7
+ ## Headers
8
+
9
+ | Header | Value |
10
+ | --- | --- |
11
+ | Authorization | `Bearer {{token}}` |
12
+ | Accept | `application/json` |
13
+
14
+ ## Path Parameters
15
+
16
+ | Field | Type | Required | Values |
17
+ | --- | --- | --- | --- |
18
+ | `id` | string | yes | `"1"` |
19
+
20
+ ## cURL
21
+
22
+ ```bash
23
+ curl --request GET \
24
+ --url "http://localhost:3000/api/v1/customers/1" \
25
+ --header "Authorization: Bearer {{token}}" \
26
+ --header "Accept: application/json"
27
+ ```
28
+
29
+ ## Responses
30
+
31
+ ### 200 OK
32
+
33
+ #### Fields
34
+
35
+ | Field | Type | Required | Values |
36
+ | --- | --- | --- | --- |
37
+ | `id` | integer | yes | `1` |
38
+ | `name` | string | yes | `"John Doe"` |
39
+ | `email` | string | yes | `"john@example.com"` |
40
+
41
+ ```json
42
+ {
43
+ "id": 1,
44
+ "name": "John Doe",
45
+ "email": "john@example.com"
46
+ }
47
+ ```
48
+
49
+ ### 404 Not Found
50
+
51
+ #### Fields
52
+
53
+ | Field | Type | Required | Values |
54
+ | --- | --- | --- | --- |
55
+ | `error` | string | yes | `"Not Found"` |
56
+
57
+ ```json
58
+ {
59
+ "error": "Not Found"
60
+ }
61
+ ```
62
+
63
+ ---
64
+
65
+ Resource: [Customers](index.md)
@@ -0,0 +1,51 @@
1
+ # Complete Task
2
+
3
+ `POST /api/v1/tasks/:id/complete`
4
+
5
+ Namespace: `api/v1`
6
+
7
+ ## Headers
8
+
9
+ | Header | Value |
10
+ | --- | --- |
11
+ | Accept | `application/json` |
12
+
13
+ ## Path Parameters
14
+
15
+ | Field | Type | Required | Values |
16
+ | --- | --- | --- | --- |
17
+ | `id` | string | yes | `"1"` |
18
+
19
+ ## cURL
20
+
21
+ ```bash
22
+ curl --request POST \
23
+ --url "http://localhost:3000/api/v1/tasks/1/complete" \
24
+ --header "Accept: application/json"
25
+ ```
26
+
27
+ ## Responses
28
+
29
+ ### 200 OK
30
+
31
+ #### Fields
32
+
33
+ | Field | Type | Required | Values |
34
+ | --- | --- | --- | --- |
35
+ | `id` | integer | yes | `1` |
36
+ | `title` | string | yes | `"Write the docs"` |
37
+ | `status` | string | yes | `"done"` |
38
+ | `priority` | string | yes | `"high"` |
39
+
40
+ ```json
41
+ {
42
+ "id": 1,
43
+ "title": "Write the docs",
44
+ "status": "done",
45
+ "priority": "high"
46
+ }
47
+ ```
48
+
49
+ ---
50
+
51
+ Resource: [Tasks](index.md)
@@ -0,0 +1,90 @@
1
+ # Create Task
2
+
3
+ `POST /api/v1/tasks`
4
+
5
+ Namespace: `api/v1`
6
+
7
+ ## Headers
8
+
9
+ | Header | Value |
10
+ | --- | --- |
11
+ | Content-Type | `application/json` |
12
+ | Accept | `application/json` |
13
+
14
+ ## Body Parameters
15
+
16
+ | Field | Type | Required | Values |
17
+ | --- | --- | --- | --- |
18
+ | `task.title` | string | yes | `"Review the release"` |
19
+ | `task.priority` | string | yes | `"high"` \| `"low"` |
20
+
21
+ ## Example Request
22
+
23
+ ```json
24
+ {
25
+ "task": {
26
+ "title": "Review the release",
27
+ "priority": "high"
28
+ }
29
+ }
30
+ ```
31
+
32
+ ## cURL
33
+
34
+ ```bash
35
+ curl --request POST \
36
+ --url "http://localhost:3000/api/v1/tasks" \
37
+ --header "Content-Type: application/json" \
38
+ --header "Accept: application/json" \
39
+ --data '{
40
+ "task": {
41
+ "title": "Review the release",
42
+ "priority": "high"
43
+ }
44
+ }'
45
+ ```
46
+
47
+ ## Responses
48
+
49
+ ### 201 Created
50
+
51
+ #### Fields
52
+
53
+ | Field | Type | Required | Values |
54
+ | --- | --- | --- | --- |
55
+ | `id` | integer | yes | `4` |
56
+ | `status` | string | yes | `"open"` |
57
+ | `title` | string | yes | `"Review the release"` |
58
+ | `priority` | string | yes | `"high"` \| `"low"` |
59
+
60
+ ```json
61
+ {
62
+ "id": 4,
63
+ "status": "open",
64
+ "title": "Review the release",
65
+ "priority": "high"
66
+ }
67
+ ```
68
+
69
+ ### 422 Unprocessable Content
70
+
71
+ #### Fields
72
+
73
+ | Field | Type | Required | Values |
74
+ | --- | --- | --- | --- |
75
+ | `errors.title[]` | string | no | `"can't be blank"` |
76
+ | `errors.priority[]` | string | no | `"is not included in the list"` |
77
+
78
+ ```json
79
+ {
80
+ "errors": {
81
+ "title": [
82
+ "can't be blank"
83
+ ]
84
+ }
85
+ }
86
+ ```
87
+
88
+ ---
89
+
90
+ Resource: [Tasks](index.md)
@@ -0,0 +1,35 @@
1
+ # Delete Task
2
+
3
+ `DELETE /api/v1/tasks/:id`
4
+
5
+ Namespace: `api/v1`
6
+
7
+ ## Headers
8
+
9
+ | Header | Value |
10
+ | --- | --- |
11
+ | Accept | `application/json` |
12
+
13
+ ## Path Parameters
14
+
15
+ | Field | Type | Required | Values |
16
+ | --- | --- | --- | --- |
17
+ | `id` | string | yes | `"3"` |
18
+
19
+ ## cURL
20
+
21
+ ```bash
22
+ curl --request DELETE \
23
+ --url "http://localhost:3000/api/v1/tasks/3" \
24
+ --header "Accept: application/json"
25
+ ```
26
+
27
+ ## Responses
28
+
29
+ ### 204 No Content
30
+
31
+ _No response body._
32
+
33
+ ---
34
+
35
+ Resource: [Tasks](index.md)
@@ -0,0 +1,14 @@
1
+ # Tasks
2
+
3
+ Namespace: `api/v1`
4
+
5
+ API version: `v1`
6
+
7
+ | Method | Path | Description |
8
+ | --- | --- | --- |
9
+ | `GET` | [`/api/v1/tasks`](list.md) | List Tasks |
10
+ | `POST` | [`/api/v1/tasks`](create.md) | Create Task |
11
+ | `DELETE` | [`/api/v1/tasks/:id`](destroy.md) | Delete Task |
12
+ | `GET` | [`/api/v1/tasks/:id`](show.md) | Get Task |
13
+ | `PATCH` | [`/api/v1/tasks/:id`](update.md) | Update Task |
14
+ | `POST` | [`/api/v1/tasks/:id/complete`](complete.md) | Complete Task |