explicit 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +282 -59
- data/app/views/explicit/application/_documentation.html.erb +127 -1
- data/app/views/explicit/application/_request.html.erb +37 -0
- data/config/locales/en.yml +3 -0
- data/lib/explicit/configuration.rb +51 -0
- data/lib/explicit/documentation/markdown.rb +23 -0
- data/lib/explicit/documentation/property.rb +19 -0
- data/lib/explicit/documentation.rb +132 -14
- data/lib/explicit/engine.rb +4 -2
- data/lib/explicit/request/example.rb +5 -0
- data/lib/explicit/request/{invalid_params.rb → invalid_params_error.rb} +6 -8
- data/lib/explicit/request/invalid_response_error.rb +19 -5
- data/lib/explicit/request/response.rb +7 -0
- data/lib/explicit/request/route.rb +9 -0
- data/lib/explicit/request.rb +105 -54
- data/lib/explicit/spec/agreement.rb +5 -16
- data/lib/explicit/spec/bigdecimal.rb +27 -0
- data/lib/explicit/spec/boolean.rb +9 -10
- data/lib/explicit/spec/error.rb +3 -0
- data/lib/explicit/spec/one_of.rb +20 -2
- data/lib/explicit/spec/record.rb +2 -0
- data/lib/explicit/spec.rb +8 -0
- data/lib/explicit/test_helper/example_recorder.rb +48 -0
- data/lib/explicit/test_helper.rb +42 -7
- data/lib/explicit/version.rb +1 -1
- data/lib/explicit.rb +13 -6
- metadata +29 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0ccbe69649ce5ca8fac5e0419943c1357206950a3436e279670cb62f5a359fe
|
4
|
+
data.tar.gz: 556861f1b18e7b900753ddc3aba962501a4991047dae5e1260a120d7de6c15e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50d01f40937fe47d381d5807bc823b1abbb63e6929a09215da81167bad53276e96584ee581ff945eebf25568ca57c3234827102ab0e73fb4cfc62eb846631d3a
|
7
|
+
data.tar.gz: 4219bebcffce9aac144a739137aa3670f036f7224991fda4acc2db973cc8f11fa25532c604c9c26bf164cb77a3dc18174605b589b0afe9fe031a8ceee67bd1b9
|
data/README.md
CHANGED
@@ -1,21 +1,24 @@
|
|
1
1
|
# Explicit
|
2
2
|
|
3
3
|
Explicit is a validation and documentation library for JSON APIs that enforces
|
4
|
-
documented specs
|
4
|
+
documented specs at runtime.
|
5
5
|
|
6
6
|
1. [Installation](#installation)
|
7
7
|
2. [Defining requests](#defining-requests)
|
8
8
|
3. [Reusing specs](#reusing-specs)
|
9
|
-
4. [
|
10
|
-
5. [Writing
|
11
|
-
6. [
|
9
|
+
4. [Reusing requests](#reusing-requests)
|
10
|
+
5. [Writing tests](#writing-tests)
|
11
|
+
6. [Publishing documentation](#publishing-documentation)
|
12
|
+
- [Adding request examples](#adding-request-examples)
|
12
13
|
7. Specs
|
13
14
|
- [Agreement](#agreement)
|
14
15
|
- [Array](#array)
|
16
|
+
- [BigDecimal](#bigdecimal)
|
15
17
|
- [Boolean](#boolean)
|
16
18
|
- [Date Time ISO8601](#date-time-iso8601)
|
17
19
|
- [Date Time Posix](#date-time-posix)
|
18
20
|
- [Default](#default)
|
21
|
+
- [Description](#description)
|
19
22
|
- [Hash](#hash)
|
20
23
|
- [Inclusion](#inclusion)
|
21
24
|
- [Integer](#integer)
|
@@ -24,40 +27,49 @@ documented specs during runtime.
|
|
24
27
|
- [One of](#one-of)
|
25
28
|
- [Record](#record)
|
26
29
|
- [String](#string)
|
27
|
-
8.
|
30
|
+
8. [Configuration](#configuration)
|
31
|
+
- [Changing examples file path](#changing-examples-file-path)
|
28
32
|
- [Customizing error messages](#customizing-error-messages)
|
29
33
|
- [Customizing error serialization](#customizing-error-serialization)
|
34
|
+
9. [Performance benchmark](#performance-benchmark)
|
30
35
|
|
31
36
|
# Installation
|
32
37
|
|
33
38
|
Add the following line to your Gemfile and then run `bundle install`:
|
34
39
|
|
35
40
|
```ruby
|
36
|
-
gem "explicit", "~> 0.
|
41
|
+
gem "explicit", "~> 0.2"
|
37
42
|
```
|
38
43
|
|
39
44
|
# Defining requests
|
40
45
|
|
41
|
-
|
42
|
-
|
46
|
+
Call `Explicit::Request.new` to define a request. The following methods are
|
47
|
+
available:
|
43
48
|
|
44
|
-
- `get(path)` - Adds a route to the request. Use the syntax
|
45
|
-
params.
|
49
|
+
- `get(path)` - Adds a route to the request. Use the syntax `/:param` for path
|
50
|
+
params.
|
46
51
|
- There is also `head`, `post`, `put`, `delete`, `options` and `patch` for
|
47
52
|
other HTTP verbs.
|
48
|
-
- `title(text)` - Adds a title to the request. Displayed in
|
49
|
-
- `description(text)` - Adds a description to the endpoint.
|
50
|
-
|
51
|
-
- `
|
53
|
+
- `title(text)` - Adds a title to the request. Displayed in documentation.
|
54
|
+
- `description(text)` - Adds a description to the endpoint. Displayed in
|
55
|
+
documentation. Markdown supported.
|
56
|
+
- `header(name, spec)` - Adds a spec to the request header.
|
57
|
+
- `param(name, spec, options = {})` - Adds a spec to the request param.
|
52
58
|
It works for params in the request body, query string and path params.
|
53
59
|
- `response(status, spec)` - Adds a response spec. You can add multiple
|
54
60
|
responses with different formats.
|
61
|
+
- `add_example(params:, headers:, response:)` - Adds an example to the
|
62
|
+
documentation. [See more details here](#adding-request-examples).
|
63
|
+
- `base_url(url)` - Sets the host for this API. For example: "https://api.myapp.com".
|
64
|
+
Meant to be used with [request composition](#reusing-requests).
|
65
|
+
- `base_path(prefix)` - Sets a prefix for the routes. For example: "/api/v1".
|
66
|
+
Meant to be used with [request composition](#reusing-requests).
|
55
67
|
|
56
68
|
For example:
|
57
69
|
|
58
70
|
```ruby
|
59
71
|
class RegistrationsController < ActionController::API
|
60
|
-
|
72
|
+
Request = Explicit::Request.new do
|
61
73
|
post "/api/registrations"
|
62
74
|
|
63
75
|
description <<-MD
|
@@ -79,7 +91,7 @@ class RegistrationsController < ActionController::API
|
|
79
91
|
|
80
92
|
user = User.create!(name:, email:, payment_type:)
|
81
93
|
|
82
|
-
render json: user:
|
94
|
+
render json: { user: user.as_json(only: %[id email]) }
|
83
95
|
rescue ActiveRecord::RecordNotUnique
|
84
96
|
render json: { error: "email_already_taken" }, status: 422
|
85
97
|
end
|
@@ -88,7 +100,7 @@ end
|
|
88
100
|
|
89
101
|
# Reusing specs
|
90
102
|
|
91
|
-
Specs are just data. You can
|
103
|
+
Specs are just data. You can share specs the same way you reuse constants or
|
92
104
|
configs in your app. For example:
|
93
105
|
|
94
106
|
```ruby
|
@@ -97,27 +109,48 @@ module MyApp::Spec
|
|
97
109
|
EMAIL = [:string, format: URI::MailTo::EMAIL_REGEXP, strip: true].freeze
|
98
110
|
|
99
111
|
ADDRESS = {
|
100
|
-
country_name: :string,
|
112
|
+
country_name: [:string, empty: false],
|
101
113
|
zipcode: [:string, format: /\d{6}-\d{3}/]
|
102
114
|
}.freeze
|
103
115
|
end
|
104
116
|
|
105
117
|
# ... and then reference the shared specs when needed
|
106
|
-
|
118
|
+
Request = Explicit::Request.new do
|
107
119
|
param :customer_uuid, MyApp::Spec::UUID
|
108
120
|
param :email, MyApp::Spec::EMAIL
|
109
121
|
param :address, MyApp::Spec::ADDRESS
|
110
122
|
end
|
111
123
|
```
|
112
124
|
|
125
|
+
# Reusing requests
|
126
|
+
|
127
|
+
Sometimes it is useful to share a group of params, headers or responses between
|
128
|
+
requests. You can achieve this by instantiating requests from an existing
|
129
|
+
request instead of `Explicit::Request`. For example:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
AuthenticatedRequest = Explicit::Request.new do
|
133
|
+
header "Authorization", [:string, format: /Bearer [a-zA-Z0-9]{20}/]
|
134
|
+
|
135
|
+
response 403, { error: "unauthorized" }
|
136
|
+
end
|
137
|
+
|
138
|
+
Request = AuthenticatedRequest.new do
|
139
|
+
# Request inherits all definitions from AuthenticatedRequest.
|
140
|
+
# Any change you make to params, headers, responses or examples will add to
|
141
|
+
# existing definitions.
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
113
145
|
# Writing tests
|
114
146
|
|
115
147
|
Include `Explicit::TestHelper` in your `test/test_helper.rb` or
|
116
148
|
`spec/rails_helper.rb`. This module provides the method
|
117
|
-
`fetch(request,
|
149
|
+
`fetch(request, **options)` that let's you verify the endpoint works as
|
118
150
|
expected and that it responds with a valid response according to the spec.
|
119
151
|
|
120
|
-
|
152
|
+
<details open>
|
153
|
+
<summary>For Minitest users, add the following line to your <code>test/test_helper.rb</code></summary>
|
121
154
|
|
122
155
|
```diff
|
123
156
|
module ActiveSupport
|
@@ -132,13 +165,26 @@ module ActiveSupport
|
|
132
165
|
end
|
133
166
|
```
|
134
167
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
168
|
+
</details>
|
169
|
+
|
170
|
+
<details open>
|
171
|
+
<summary>For RSpec users, add the following line to your <code>spec/rails_helper.rb</code></summary>
|
172
|
+
|
173
|
+
```diff
|
174
|
+
RSpec.configure do |config|
|
175
|
+
+ config.include Explicit::TestHelper
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
</details>
|
180
|
+
|
181
|
+
To test your controller, call `fetch(request, **options)` and write
|
182
|
+
assertions against the response. If the response is invalid according to the
|
183
|
+
spec the test fails with `Explicit::Request::InvalidResponseError`.
|
139
184
|
|
140
185
|
The response object has a `status`, an integer value for the http status, and
|
141
|
-
`data`, a hash with the response data.
|
186
|
+
`data`, a hash with the response data. It also provides `dig` for a
|
187
|
+
slighly shorter syntax when accessing nested attributes.
|
142
188
|
|
143
189
|
> Path params are matched by name, so if you have an endpoint configured with
|
144
190
|
> `put "/customers/:customer_id"` you must call as
|
@@ -147,8 +193,11 @@ The response object has a `status`, an integer value for the http status, and
|
|
147
193
|
> Note: Response specs are only verified in test environment with no
|
148
194
|
> performance penalty when running in production.
|
149
195
|
|
196
|
+
<details open>
|
197
|
+
<summary>Minitest example</summary>
|
198
|
+
|
150
199
|
```ruby
|
151
|
-
class RegistrationsControllerTest < ActionDispatch::IntegrationTest
|
200
|
+
class API::V1::RegistrationsControllerTest < ActionDispatch::IntegrationTest
|
152
201
|
test "successful registration" do
|
153
202
|
response = fetch(RegistrationsController::Request, params: {
|
154
203
|
name: "Bilbo Baggins",
|
@@ -158,54 +207,73 @@ class RegistrationsControllerTest < ActionDispatch::IntegrationTest
|
|
158
207
|
})
|
159
208
|
|
160
209
|
assert_equal 200, response.status
|
210
|
+
assert_equal "bilbo@shire.com", response.dig(:user, :email)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
```
|
161
214
|
|
162
|
-
|
163
|
-
|
215
|
+
</details>
|
216
|
+
|
217
|
+
<details open>
|
218
|
+
<summary>RSpec example</summary>
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
describe RegistrationsController::Request, type: :request do
|
222
|
+
context "when request params are valid" do
|
223
|
+
it "successfully registers a new user" do
|
224
|
+
response = fetch(described_class, params: {
|
225
|
+
name: "Bilbo Baggins",
|
226
|
+
email: "bilbo@shire.com",
|
227
|
+
payment_type: "free_trial",
|
228
|
+
terms_of_use: true
|
229
|
+
})
|
230
|
+
|
231
|
+
expect(response.status).to eql(200)
|
232
|
+
expect(response.dig(:user, :email)).to eql("bilbo@shire.com")
|
233
|
+
end
|
164
234
|
end
|
165
235
|
end
|
166
236
|
```
|
167
237
|
|
168
|
-
|
238
|
+
</details>
|
169
239
|
|
170
|
-
|
171
|
-
you must configure it via `Explicit::Documentation.build` and then publish it
|
172
|
-
mounting it in `routes.rb`.
|
240
|
+
# Publishing documentation
|
173
241
|
|
174
|
-
|
242
|
+
Call `Explicit::Documentation.new` to group, organize and publish the
|
243
|
+
documentation for your API. The following methods are available:
|
175
244
|
|
176
|
-
- `page_title(text)`
|
177
|
-
- `
|
178
|
-
- `
|
179
|
-
- `add(
|
180
|
-
- `add(title:, partial:)`
|
245
|
+
- `page_title(text)` - Sets the web page title.
|
246
|
+
- `section(name, &block)` - Adds a section to the navigation menu.
|
247
|
+
- `add(request)` - Adds a request to the section
|
248
|
+
- `add(title:, partial:)` - Adds a partial to the section
|
181
249
|
|
182
250
|
For example:
|
183
251
|
|
184
252
|
```ruby
|
185
253
|
module MyApp::API::V1
|
186
|
-
Documentation = Explicit::Documentation.
|
187
|
-
page_title "Acme
|
188
|
-
primary_color "#6366f1"
|
254
|
+
Documentation = Explicit::Documentation.new do
|
255
|
+
page_title "Acme API Docs"
|
189
256
|
|
190
257
|
section "Introduction" do
|
191
258
|
add title: "About", partial: "api/v1/introduction/about"
|
192
259
|
end
|
193
260
|
|
194
261
|
section "Auth" do
|
195
|
-
add SessionsController::CreateRequest
|
196
262
|
add RegistrationsController::CreateRequest
|
263
|
+
add SessionsController::CreateRequest
|
264
|
+
add SessionsController::DestroyRequest
|
197
265
|
end
|
198
266
|
|
199
|
-
section "
|
200
|
-
add
|
201
|
-
add
|
202
|
-
add
|
267
|
+
section "Articles" do
|
268
|
+
add ArticlesController::CreateRequest
|
269
|
+
add ArticlesController::UpdateRequest
|
270
|
+
add ArticlesController::DestroyRequest
|
203
271
|
end
|
204
272
|
end
|
205
273
|
end
|
206
274
|
```
|
207
275
|
|
208
|
-
`Explicit::Documentation.
|
276
|
+
`Explicit::Documentation.new` returns a rails engine that you can mount in
|
209
277
|
your `config/routes.rb`. For example:
|
210
278
|
|
211
279
|
```ruby
|
@@ -214,6 +282,93 @@ Rails.application.routes.draw do
|
|
214
282
|
end
|
215
283
|
```
|
216
284
|
|
285
|
+
## Adding request examples
|
286
|
+
|
287
|
+
You can add request examples in two different ways:
|
288
|
+
|
289
|
+
1. Manually add an example with `add_example(params:, headers:, response:)`
|
290
|
+
2. Automatically save examples from tests
|
291
|
+
|
292
|
+
### 1. Manually adding examples
|
293
|
+
|
294
|
+
In a request, call `add_example(params:, headers:, response:)` after declaring
|
295
|
+
params and responses. It's important the example comes after params and
|
296
|
+
responses to make sure it actually follows the spec.
|
297
|
+
|
298
|
+
For example:
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
Request = Explicit::Request.new do
|
302
|
+
# ... other configs, params and responses
|
303
|
+
|
304
|
+
add_example(
|
305
|
+
params: {
|
306
|
+
name: "Bilbo baggins",
|
307
|
+
email: "bilbo@shire.com",
|
308
|
+
payment_type: "free_trial",
|
309
|
+
terms_of_use: true
|
310
|
+
}
|
311
|
+
response: {
|
312
|
+
status: 200,
|
313
|
+
data: {
|
314
|
+
user: {
|
315
|
+
id: 15123,
|
316
|
+
email: "bilbo@shire.com"
|
317
|
+
}
|
318
|
+
}
|
319
|
+
}
|
320
|
+
)
|
321
|
+
end
|
322
|
+
```
|
323
|
+
|
324
|
+
Request examples are just data, so you can extract and reference them in any
|
325
|
+
way you like. For example:
|
326
|
+
|
327
|
+
```ruby
|
328
|
+
Request = Explicit::Request.new do
|
329
|
+
# ... other configs, params and responses
|
330
|
+
|
331
|
+
add_example MyApp::Examples::REQUEST_1
|
332
|
+
add_example MyApp::Examples::REQUEST_2
|
333
|
+
end
|
334
|
+
```
|
335
|
+
|
336
|
+
### 2. Automatically saving examples from tests
|
337
|
+
|
338
|
+
The `fetch` method provided by `Explicit::TestHelper` accepts the option
|
339
|
+
`add_as_example`. When set to true, the request example is persisted to a local
|
340
|
+
file. For example:
|
341
|
+
|
342
|
+
```ruby
|
343
|
+
class RegistrationsControllerTest < ActionDispatch::IntegrationTest
|
344
|
+
test "successful registration" do
|
345
|
+
response = fetch(
|
346
|
+
RegistrationsController::Request,
|
347
|
+
params: {
|
348
|
+
name: "Bilbo Baggins",
|
349
|
+
email: "bilbo@shire.com",
|
350
|
+
payment_type: "free_trial",
|
351
|
+
terms_of_use: true
|
352
|
+
},
|
353
|
+
add_as_example: true # <-- add this line
|
354
|
+
)
|
355
|
+
|
356
|
+
assert_equal 200, response.status
|
357
|
+
assert_equal "bilbo@shire.com", response.dig(:user, :email)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
```
|
361
|
+
|
362
|
+
Whenever you wish to refresh the examples file run the test suite with the ENV
|
363
|
+
`EXPLICIT_PERSIST_EXAMPLES` set. For example
|
364
|
+
`EXPLICIT_PERSIST_EXAMPLES=true bin/rails test` or
|
365
|
+
`EXPLICIT_PERSIST_EXAMPLES=true bundle exec rspec`. The examples file is located
|
366
|
+
at `#{Rails.root}/public/explicit_request_examples.json` by default, but you can
|
367
|
+
[change it here](#request-examples-file-path).
|
368
|
+
|
369
|
+
**Important: be careful not to leak any sensitive data when persisting
|
370
|
+
examples from tests**
|
371
|
+
|
217
372
|
# Specs
|
218
373
|
|
219
374
|
### Agreement
|
@@ -223,9 +378,9 @@ end
|
|
223
378
|
[:agreement, parse: true]
|
224
379
|
```
|
225
380
|
|
226
|
-
|
227
|
-
|
228
|
-
values are accepted
|
381
|
+
A boolean that must always be true. Useful for terms of use or agreement
|
382
|
+
acceptances. If `parse: true` is specified then the following
|
383
|
+
values are accepted: `true`, `"true"`, `"on"`, `"1"` and `1`.
|
229
384
|
|
230
385
|
### Array
|
231
386
|
|
@@ -238,6 +393,18 @@ values are accepted alongisde `true`: "`true`", `"on"` and `"1"`.
|
|
238
393
|
All items in the array must be valid according to the subspec. If at least one
|
239
394
|
value is invalid then the array is invalid.
|
240
395
|
|
396
|
+
### BigDecimal
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
:bigdecimal
|
400
|
+
[:bigdecimal, min: 0] # inclusive
|
401
|
+
[:bigdecimal, max: 100] # inclusive
|
402
|
+
```
|
403
|
+
|
404
|
+
Value must be an integer or a string like `"0.2"` to avoid rounding errors.
|
405
|
+
|
406
|
+
[Reference](https://ruby-doc.org/stdlib-3.1.0/libdoc/bigdecimal/rdoc/BigDecimal.html)
|
407
|
+
|
241
408
|
### Boolean
|
242
409
|
|
243
410
|
```ruby
|
@@ -246,8 +413,8 @@ value is invalid then the array is invalid.
|
|
246
413
|
```
|
247
414
|
|
248
415
|
If `parse: true` is specified then the following values are converted to `true`:
|
249
|
-
`"true"`, `"on"` and `
|
250
|
-
`"false"`, `"off"` and `
|
416
|
+
`"true"`, `"on"`, `"1"` and `1`, and the following values are converted to
|
417
|
+
`false`: `"false"`, `"off"`, `"0"` and `0`.
|
251
418
|
|
252
419
|
### Date Time ISO8601
|
253
420
|
|
@@ -280,7 +447,20 @@ Provides a default value for the param if the value is not present or it is
|
|
280
447
|
`nil`. Other falsy values such as empty string or zero have precedence over
|
281
448
|
the default value.
|
282
449
|
|
283
|
-
If you provide a lambda it will execute
|
450
|
+
If you provide a lambda it will execute every time `Request.validate!` is
|
451
|
+
called.
|
452
|
+
|
453
|
+
### Description
|
454
|
+
|
455
|
+
```ruby
|
456
|
+
[:description, markdown_text, subspec]
|
457
|
+
[:description, "Customer full name", :string]
|
458
|
+
[:description, "Rating score from 0 (bad) to 5 (good)", :integer]
|
459
|
+
```
|
460
|
+
|
461
|
+
Adds a description to the spec. Descriptions are displayed in documentation
|
462
|
+
and do not affect validation in any way with. There is no overhead at runtime.
|
463
|
+
Markdown supported.
|
284
464
|
|
285
465
|
### Hash
|
286
466
|
|
@@ -293,8 +473,8 @@ If you provide a lambda it will execute in every `validate!` call.
|
|
293
473
|
```
|
294
474
|
|
295
475
|
Hashes are key value pairs where all keys must match keyspec and all values must
|
296
|
-
match valuespec. If you are expecting a hash with a specific set of keys
|
297
|
-
|
476
|
+
match valuespec. If you are expecting a hash with a specific set of keys use a
|
477
|
+
[record](#record) instead.
|
298
478
|
|
299
479
|
### Inclusion
|
300
480
|
|
@@ -326,11 +506,11 @@ If `parse: true` is specified then integer encoded string values such as "10" or
|
|
326
506
|
[:literal, value]
|
327
507
|
[:literal, 6379]
|
328
508
|
[:literal, "value"]
|
329
|
-
"value" # literal
|
509
|
+
"value" # strings work like a literal specs, so you can use this shorter syntax.
|
330
510
|
```
|
331
511
|
|
332
512
|
A literal value behaves similar to inclusion with a single value. Useful for
|
333
|
-
|
513
|
+
matching against multiple specs in [`one_of`](#one-of).
|
334
514
|
|
335
515
|
### Nilable
|
336
516
|
|
@@ -340,7 +520,7 @@ declaring multiple types in `one_of`.
|
|
340
520
|
[:nilable, [:array, :integer]]
|
341
521
|
```
|
342
522
|
|
343
|
-
Value must
|
523
|
+
Value must either match the subspec or be nil.
|
344
524
|
|
345
525
|
### One of
|
346
526
|
|
@@ -387,3 +567,46 @@ records with array of records, etc.
|
|
387
567
|
[:string, minlength: 8] # inclusive
|
388
568
|
[:string, maxlength: 20] # inclusive
|
389
569
|
```
|
570
|
+
|
571
|
+
# Configuration
|
572
|
+
|
573
|
+
Add an initializer `config/initializers/explicit.rb` with the following
|
574
|
+
code, and then make the desired changes to the config.
|
575
|
+
|
576
|
+
```ruby
|
577
|
+
Explicit.configure do |config|
|
578
|
+
# change config here...
|
579
|
+
end
|
580
|
+
```
|
581
|
+
|
582
|
+
### Changing examples file path
|
583
|
+
|
584
|
+
```ruby
|
585
|
+
config.request_examples_file_path = Rails.root.join("public/request_examples.json")
|
586
|
+
```
|
587
|
+
|
588
|
+
### Customizing error messages
|
589
|
+
|
590
|
+
Copy the [default error messages translations](https://github.com/luizpvas/explicit/blob/main/config/locales/en.yml)
|
591
|
+
to your project and make the desired changes.
|
592
|
+
|
593
|
+
### Customizing error serialization
|
594
|
+
|
595
|
+
First disable the default response:
|
596
|
+
|
597
|
+
```ruby
|
598
|
+
config.rescue_from_invalid_params = false
|
599
|
+
```
|
600
|
+
|
601
|
+
and then add a custom `rescue_from Explicit::Request::InvalidParamsError` to
|
602
|
+
your base controller. Use the following code as a starting point:
|
603
|
+
|
604
|
+
```ruby
|
605
|
+
class ApplicationController < ActionController::API
|
606
|
+
rescue_from Explicit::Request::InvalidParamsError do |err|
|
607
|
+
params = Explicit::Spec::Error.translate(err.errors)
|
608
|
+
|
609
|
+
render json: { error: "invalid_params", params: }, status: 422
|
610
|
+
end
|
611
|
+
end
|
612
|
+
```
|
@@ -2,9 +2,135 @@
|
|
2
2
|
<html>
|
3
3
|
<head>
|
4
4
|
<title><%= local_assigns[:page_title] || "API Documentation" %></title>
|
5
|
+
|
6
|
+
<style>
|
7
|
+
html, body {
|
8
|
+
font-family: sans-serif;
|
9
|
+
font-size: 14px;
|
10
|
+
margin: 0;
|
11
|
+
padding: 0;
|
12
|
+
|
13
|
+
--color-neutral-100: #f5f5f5;
|
14
|
+
--color-neutral-200: #e5e5e5;
|
15
|
+
--color-neutral-300: #d4d4d8;
|
16
|
+
--color-neutral-400: #a3a3a3;
|
17
|
+
--color-neutral-500: #737373;
|
18
|
+
--color-neutral-600: #525252;
|
19
|
+
}
|
20
|
+
|
21
|
+
.container {
|
22
|
+
display: flex;
|
23
|
+
}
|
24
|
+
|
25
|
+
.navigation {
|
26
|
+
background-color: var(--color-neutral-100);
|
27
|
+
border-right: 1px solid var(--color-neutral-300);
|
28
|
+
width: 380px;
|
29
|
+
height: 100vh;
|
30
|
+
}
|
31
|
+
.navigation__section {
|
32
|
+
}
|
33
|
+
.navigation__section__page {
|
34
|
+
display: block;
|
35
|
+
}
|
36
|
+
|
37
|
+
.main {
|
38
|
+
flex-grow: 1;
|
39
|
+
width: 100%;
|
40
|
+
height: 100vh;
|
41
|
+
overflow: auto;
|
42
|
+
}
|
43
|
+
|
44
|
+
.page {
|
45
|
+
padding: 0 2rem;
|
46
|
+
margin-bottom: 100px;
|
47
|
+
}
|
48
|
+
.page:not(:first-of-type) {
|
49
|
+
border-top: 1px solid var(--color-neutral-200);
|
50
|
+
}
|
51
|
+
.page__container {
|
52
|
+
display: flex;
|
53
|
+
gap: 1rem;
|
54
|
+
}
|
55
|
+
.page__request {
|
56
|
+
width: 50%;
|
57
|
+
}
|
58
|
+
.page__request__description {
|
59
|
+
line-height: 1.6rem;
|
60
|
+
}
|
61
|
+
.page__response {
|
62
|
+
width: 50%;
|
63
|
+
}
|
64
|
+
|
65
|
+
.markdown code {
|
66
|
+
background: var(--color-neutral-100);
|
67
|
+
color: var(--color-neutral-900);
|
68
|
+
}
|
69
|
+
.markdown p:first-of-type {
|
70
|
+
margin-block-start: 0em;
|
71
|
+
}
|
72
|
+
.markdown p:last-of-type {
|
73
|
+
margin-block-end: 0em;
|
74
|
+
}
|
75
|
+
|
76
|
+
.params {
|
77
|
+
margin-top: 1rem;
|
78
|
+
border: 1px solid var(--color-neutral-200);
|
79
|
+
border-radius: 8px;
|
80
|
+
}
|
81
|
+
.params__header {
|
82
|
+
padding: 5px;
|
83
|
+
text-align: center;
|
84
|
+
|
85
|
+
font-size: 12px;
|
86
|
+
text-transform: uppercase;
|
87
|
+
color: var(--color-neutral-500);
|
88
|
+
border-bottom: 1px solid var(--color-neutral-200);
|
89
|
+
|
90
|
+
background-color: var(--color-neutral-100);
|
91
|
+
border-top-left-radius: 8px;
|
92
|
+
border-top-right-radius: 8px;
|
93
|
+
}
|
94
|
+
.params__param {
|
95
|
+
padding: 0.8rem;
|
96
|
+
}
|
97
|
+
.params__param:not(:last-of-type) {
|
98
|
+
border-bottom: 1px solid var(--color-neutral-200);
|
99
|
+
}
|
100
|
+
.params__param__name {
|
101
|
+
font-family: monospace;
|
102
|
+
font-weight: bold;
|
103
|
+
}
|
104
|
+
.params__param__description {
|
105
|
+
margin-top: 0.5rem;
|
106
|
+
color: var(--color-neutral-500);
|
107
|
+
}
|
108
|
+
</style>
|
5
109
|
</head>
|
6
110
|
|
7
111
|
<body>
|
8
|
-
<
|
112
|
+
<div class="container">
|
113
|
+
<section class="navigation">
|
114
|
+
<% sections.each do |section| %>
|
115
|
+
<details class="navigation__section" open>
|
116
|
+
<summary><%= section.name %></summary>
|
117
|
+
|
118
|
+
<% section.pages.each do |page| %>
|
119
|
+
<%= link_to page.title, "##{page.anchor}", class: "navigation__section__page" %>
|
120
|
+
<% end %>
|
121
|
+
</details>
|
122
|
+
<% end %>
|
123
|
+
</section>
|
124
|
+
|
125
|
+
<main class="main">
|
126
|
+
<% sections.each do |section| %>
|
127
|
+
<% section.pages.each do |page| %>
|
128
|
+
<div class="page">
|
129
|
+
<%= render partial: page.partial, locals: { page: } %>
|
130
|
+
</div>
|
131
|
+
<% end %>
|
132
|
+
<% end %>
|
133
|
+
</main>
|
134
|
+
</div>
|
9
135
|
</body>
|
10
136
|
</html>
|