explicit 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +75 -67
- data/app/helpers/explicit/application_helper.rb +32 -0
- data/app/views/explicit/documentation/_attribute.html.erb +38 -0
- data/app/views/explicit/documentation/_page.html.erb +166 -0
- data/app/views/explicit/documentation/_request.html.erb +87 -0
- data/app/views/explicit/documentation/request/_examples.html.erb +50 -0
- data/app/views/explicit/documentation/type/_agreement.html.erb +7 -0
- data/app/views/explicit/documentation/type/_array.html.erb +3 -0
- data/app/views/explicit/documentation/type/_big_decimal.html.erb +4 -0
- data/app/views/explicit/documentation/type/_boolean.html.erb +7 -0
- data/app/views/explicit/documentation/type/_date_time_iso8601.html.erb +3 -0
- data/app/views/explicit/documentation/type/_date_time_posix.html.erb +3 -0
- data/app/views/explicit/documentation/type/_enum.html.erb +7 -0
- data/app/views/explicit/documentation/type/_file.html.erb +9 -0
- data/app/views/explicit/documentation/type/_hash.html.erb +4 -0
- data/app/views/explicit/documentation/type/_integer.html.erb +25 -0
- data/app/views/explicit/documentation/type/_one_of.html.erb +11 -0
- data/app/views/explicit/documentation/type/_record.html.erb +9 -0
- data/app/views/explicit/documentation/type/_string.html.erb +21 -0
- data/config/locales/en.yml +27 -11
- data/lib/explicit/configuration.rb +1 -1
- data/lib/explicit/documentation/builder.rb +80 -0
- data/lib/explicit/documentation/markdown.rb +2 -13
- data/lib/explicit/documentation/output/swagger.rb +176 -0
- data/lib/explicit/documentation/output/webpage.rb +31 -0
- data/lib/explicit/documentation/page/partial.rb +20 -0
- data/lib/explicit/documentation/page/request.rb +27 -0
- data/lib/explicit/documentation/section.rb +9 -0
- data/lib/explicit/documentation.rb +12 -145
- data/lib/explicit/request/example.rb +50 -1
- data/lib/explicit/request/invalid_params_error.rb +1 -3
- data/lib/explicit/request/invalid_response_error.rb +2 -15
- data/lib/explicit/request/route.rb +18 -0
- data/lib/explicit/request.rb +43 -24
- data/lib/explicit/test_helper/example_recorder.rb +7 -2
- data/lib/explicit/test_helper.rb +25 -7
- data/lib/explicit/type/agreement.rb +39 -0
- data/lib/explicit/type/array.rb +56 -0
- data/lib/explicit/type/big_decimal.rb +58 -0
- data/lib/explicit/type/boolean.rb +47 -0
- data/lib/explicit/type/date_time_iso8601.rb +41 -0
- data/lib/explicit/type/date_time_posix.rb +44 -0
- data/lib/explicit/type/enum.rb +41 -0
- data/lib/explicit/type/file.rb +60 -0
- data/lib/explicit/type/hash.rb +57 -0
- data/lib/explicit/type/integer.rb +79 -0
- data/lib/explicit/type/literal.rb +45 -0
- data/lib/explicit/type/modifiers/default.rb +24 -0
- data/lib/explicit/type/modifiers/description.rb +11 -0
- data/lib/explicit/type/modifiers/nilable.rb +19 -0
- data/lib/explicit/type/modifiers/param_location.rb +11 -0
- data/lib/explicit/type/one_of.rb +46 -0
- data/lib/explicit/type/record.rb +96 -0
- data/lib/explicit/type/string.rb +68 -0
- data/lib/explicit/type.rb +112 -0
- data/lib/explicit/version.rb +1 -1
- data/lib/explicit.rb +28 -18
- metadata +47 -25
- data/app/views/explicit/application/_documentation.html.erb +0 -136
- data/app/views/explicit/application/_request.html.erb +0 -37
- data/lib/explicit/documentation/property.rb +0 -19
- data/lib/explicit/spec/agreement.rb +0 -17
- data/lib/explicit/spec/array.rb +0 -28
- data/lib/explicit/spec/bigdecimal.rb +0 -27
- data/lib/explicit/spec/boolean.rb +0 -30
- data/lib/explicit/spec/date_time_iso8601.rb +0 -17
- data/lib/explicit/spec/date_time_posix.rb +0 -21
- data/lib/explicit/spec/default.rb +0 -20
- data/lib/explicit/spec/error.rb +0 -63
- data/lib/explicit/spec/hash.rb +0 -30
- data/lib/explicit/spec/inclusion.rb +0 -15
- data/lib/explicit/spec/integer.rb +0 -53
- data/lib/explicit/spec/literal.rb +0 -15
- data/lib/explicit/spec/nilable.rb +0 -15
- data/lib/explicit/spec/one_of.rb +0 -40
- data/lib/explicit/spec/record.rb +0 -33
- data/lib/explicit/spec/string.rb +0 -50
- data/lib/explicit/spec.rb +0 -72
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5adb170708747adfb160afcf5c4248892418ec518302defa17c692fa34f4cfff
|
4
|
+
data.tar.gz: 10258e426028dcbce19a9825b5541edc75d5e288c8846f7d21f36f2a239ce9b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86e5c040ecc814b16d6a8b7b43d9f7b6ee4764a36b5b21e56d504cde8f7229db0ffa1a2eb66e7c2621f8c1a2a453d59823577bf17bac2485551f0ed567b6edb0
|
7
|
+
data.tar.gz: a246c4136d476c61ef8bb59cd09b904918f2ec01ac4812862a6a02f6d051d7982e0ba37bc9577b6d045bb9c39185f274c535c0b9f3e7cfceb21671663117dc68
|
data/README.md
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
# Explicit
|
2
2
|
|
3
|
-
Explicit is a validation and documentation library for
|
4
|
-
documented
|
3
|
+
Explicit is a validation and documentation library for REST APIs that enforces
|
4
|
+
documented types at runtime.
|
5
|
+
|
6
|
+

|
5
7
|
|
6
8
|
1. [Installation](#installation)
|
7
9
|
2. [Defining requests](#defining-requests)
|
8
|
-
3. [Reusing
|
10
|
+
3. [Reusing types](#reusing-types)
|
9
11
|
4. [Reusing requests](#reusing-requests)
|
10
12
|
5. [Writing tests](#writing-tests)
|
11
13
|
6. [Publishing documentation](#publishing-documentation)
|
12
14
|
- [Adding request examples](#adding-request-examples)
|
13
|
-
7.
|
15
|
+
7. Types
|
14
16
|
- [Agreement](#agreement)
|
15
17
|
- [Array](#array)
|
16
18
|
- [BigDecimal](#bigdecimal)
|
@@ -19,8 +21,9 @@ documented specs at runtime.
|
|
19
21
|
- [Date Time Posix](#date-time-posix)
|
20
22
|
- [Default](#default)
|
21
23
|
- [Description](#description)
|
24
|
+
- [Enum](#enum)
|
25
|
+
- [File](#file)
|
22
26
|
- [Hash](#hash)
|
23
|
-
- [Inclusion](#inclusion)
|
24
27
|
- [Integer](#integer)
|
25
28
|
- [Literal](#literal)
|
26
29
|
- [Nilable](#nilable)
|
@@ -31,7 +34,6 @@ documented specs at runtime.
|
|
31
34
|
- [Changing examples file path](#changing-examples-file-path)
|
32
35
|
- [Customizing error messages](#customizing-error-messages)
|
33
36
|
- [Customizing error serialization](#customizing-error-serialization)
|
34
|
-
9. [Performance benchmark](#performance-benchmark)
|
35
37
|
|
36
38
|
# Installation
|
37
39
|
|
@@ -53,10 +55,10 @@ available:
|
|
53
55
|
- `title(text)` - Adds a title to the request. Displayed in documentation.
|
54
56
|
- `description(text)` - Adds a description to the endpoint. Displayed in
|
55
57
|
documentation. Markdown supported.
|
56
|
-
- `header(name,
|
57
|
-
- `param(name,
|
58
|
+
- `header(name, type)` - Adds a type to the request header.
|
59
|
+
- `param(name, type, options = {})` - Adds a type to the request param.
|
58
60
|
It works for params in the request body, query string and path params.
|
59
|
-
- `response(status,
|
61
|
+
- `response(status, type)` - Adds a response type. You can add multiple
|
60
62
|
responses with different formats.
|
61
63
|
- `add_example(params:, headers:, response:)` - Adds an example to the
|
62
64
|
documentation. [See more details here](#adding-request-examples).
|
@@ -72,14 +74,14 @@ class RegistrationsController < ActionController::API
|
|
72
74
|
Request = Explicit::Request.new do
|
73
75
|
post "/api/registrations"
|
74
76
|
|
75
|
-
description
|
77
|
+
description <<~MD
|
76
78
|
Attempts to register a new user in the system. If `payment_type` is not
|
77
79
|
specified a trial period of 30 days is started.
|
78
80
|
MD
|
79
81
|
|
80
82
|
param :name, [:string, empty: false]
|
81
83
|
param :email, [:string, format: URI::MailTo::EMAIL_REGEXP, strip: true]
|
82
|
-
param :payment_type, [:
|
84
|
+
param :payment_type, [:enum, ["free_trial", "credit_card"]], default: "free_trial"
|
83
85
|
param :terms_of_use, :agreement
|
84
86
|
|
85
87
|
response 200, { user: { id: :integer, email: :string } }
|
@@ -98,13 +100,13 @@ class RegistrationsController < ActionController::API
|
|
98
100
|
end
|
99
101
|
```
|
100
102
|
|
101
|
-
# Reusing
|
103
|
+
# Reusing types
|
102
104
|
|
103
|
-
|
105
|
+
Types are just data. You can share types the same way you reuse constants or
|
104
106
|
configs in your app. For example:
|
105
107
|
|
106
108
|
```ruby
|
107
|
-
module MyApp::
|
109
|
+
module MyApp::Type
|
108
110
|
UUID = [:string, format: /^\h{8}-(\h{4}-){3}\h{12}$/].freeze
|
109
111
|
EMAIL = [:string, format: URI::MailTo::EMAIL_REGEXP, strip: true].freeze
|
110
112
|
|
@@ -114,11 +116,11 @@ module MyApp::Spec
|
|
114
116
|
}.freeze
|
115
117
|
end
|
116
118
|
|
117
|
-
# ... and then reference the shared
|
119
|
+
# ... and then reference the shared types when needed
|
118
120
|
Request = Explicit::Request.new do
|
119
|
-
param :customer_uuid, MyApp::
|
120
|
-
param :email, MyApp::
|
121
|
-
param :address, MyApp::
|
121
|
+
param :customer_uuid, MyApp::Type::UUID
|
122
|
+
param :email, MyApp::Type::EMAIL
|
123
|
+
param :address, MyApp::Type::ADDRESS
|
122
124
|
end
|
123
125
|
```
|
124
126
|
|
@@ -147,7 +149,7 @@ end
|
|
147
149
|
Include `Explicit::TestHelper` in your `test/test_helper.rb` or
|
148
150
|
`spec/rails_helper.rb`. This module provides the method
|
149
151
|
`fetch(request, **options)` that let's you verify the endpoint works as
|
150
|
-
expected and that it responds with a valid response according to the
|
152
|
+
expected and that it responds with a valid response according to the docs.
|
151
153
|
|
152
154
|
<details open>
|
153
155
|
<summary>For Minitest users, add the following line to your <code>test/test_helper.rb</code></summary>
|
@@ -179,8 +181,8 @@ end
|
|
179
181
|
</details>
|
180
182
|
|
181
183
|
To test your controller, call `fetch(request, **options)` and write
|
182
|
-
assertions against the response. If the response is invalid
|
183
|
-
|
184
|
+
assertions against the response. If the response is invalid the test fails with
|
185
|
+
`Explicit::Request::InvalidResponseError`.
|
184
186
|
|
185
187
|
The response object has a `status`, an integer value for the http status, and
|
186
188
|
`data`, a hash with the response data. It also provides `dig` for a
|
@@ -190,7 +192,7 @@ slighly shorter syntax when accessing nested attributes.
|
|
190
192
|
> `put "/customers/:customer_id"` you must call as
|
191
193
|
> `fetch(CustomerController::UpdateRequest, { customer_id: 123 })`.
|
192
194
|
|
193
|
-
> Note: Response
|
195
|
+
> Note: Response types are only verified in test environment with no
|
194
196
|
> performance penalty when running in production.
|
195
197
|
|
196
198
|
<details open>
|
@@ -243,6 +245,8 @@ Call `Explicit::Documentation.new` to group, organize and publish the
|
|
243
245
|
documentation for your API. The following methods are available:
|
244
246
|
|
245
247
|
- `page_title(text)` - Sets the web page title.
|
248
|
+
- `company_logo_url(url)` - Shows the company logo above the navigation menu.
|
249
|
+
- `version(semver)` - Sets the version of the API. Default: "1.0"
|
246
250
|
- `section(name, &block)` - Adds a section to the navigation menu.
|
247
251
|
- `add(request)` - Adds a request to the section
|
248
252
|
- `add(title:, partial:)` - Adds a partial to the section
|
@@ -253,6 +257,8 @@ For example:
|
|
253
257
|
module MyApp::API::V1
|
254
258
|
Documentation = Explicit::Documentation.new do
|
255
259
|
page_title "Acme API Docs"
|
260
|
+
company_logo_url "https://my-app.com/logo.png"
|
261
|
+
version "1.0.5"
|
256
262
|
|
257
263
|
section "Introduction" do
|
258
264
|
add title: "About", partial: "api/v1/introduction/about"
|
@@ -293,7 +299,7 @@ You can add request examples in two different ways:
|
|
293
299
|
|
294
300
|
In a request, call `add_example(params:, headers:, response:)` after declaring
|
295
301
|
params and responses. It's important the example comes after params and
|
296
|
-
responses to make sure it actually follows the
|
302
|
+
responses to make sure it actually follows the type definition.
|
297
303
|
|
298
304
|
For example:
|
299
305
|
|
@@ -360,37 +366,36 @@ end
|
|
360
366
|
```
|
361
367
|
|
362
368
|
Whenever you wish to refresh the examples file run the test suite with the ENV
|
363
|
-
`
|
364
|
-
`
|
365
|
-
`
|
366
|
-
|
369
|
+
`UPDATE_REQUEST_EXAMPLES` set. For example
|
370
|
+
`UPDATE_REQUEST_EXAMPLES=true bin/rails test` or
|
371
|
+
`UPDATE_REQUEST_EXAMPLES=true bundle exec rspec`. The file is located at
|
372
|
+
`#{Rails.root}/public/explicit_request_examples.json` by default, but you can
|
367
373
|
[change it here](#request-examples-file-path).
|
368
374
|
|
369
375
|
**Important: be careful not to leak any sensitive data when persisting
|
370
376
|
examples from tests**
|
371
377
|
|
372
|
-
#
|
378
|
+
# Types
|
373
379
|
|
374
380
|
### Agreement
|
375
381
|
|
376
382
|
```ruby
|
377
383
|
:agreement
|
378
|
-
[:agreement, parse: true]
|
379
384
|
```
|
380
385
|
|
381
386
|
A boolean that must always be true. Useful for terms of use or agreement
|
382
|
-
acceptances.
|
383
|
-
|
387
|
+
acceptances. The following values are accepted: `true`, `"true"`, `"on"`, `"1"`
|
388
|
+
and `1`.
|
384
389
|
|
385
390
|
### Array
|
386
391
|
|
387
392
|
```ruby
|
388
|
-
[:array,
|
393
|
+
[:array, subtype, options = {}]
|
389
394
|
[:array, :string]
|
390
395
|
[:array, :integer, empty: false]
|
391
396
|
```
|
392
397
|
|
393
|
-
All items in the array must be valid according to the
|
398
|
+
All items in the array must be valid according to the subtype. If at least one
|
394
399
|
value is invalid then the array is invalid.
|
395
400
|
|
396
401
|
### BigDecimal
|
@@ -402,19 +407,16 @@ value is invalid then the array is invalid.
|
|
402
407
|
```
|
403
408
|
|
404
409
|
Value must be an integer or a string like `"0.2"` to avoid rounding errors.
|
405
|
-
|
406
410
|
[Reference](https://ruby-doc.org/stdlib-3.1.0/libdoc/bigdecimal/rdoc/BigDecimal.html)
|
407
411
|
|
408
412
|
### Boolean
|
409
413
|
|
410
414
|
```ruby
|
411
415
|
:boolean
|
412
|
-
[:boolean, parse: true]
|
413
416
|
```
|
414
417
|
|
415
|
-
|
416
|
-
`"
|
417
|
-
`false`: `"false"`, `"off"`, `"0"` and `0`.
|
418
|
+
The following values are true: `true`, `"true"`, `"on"`, `"1"` and `1`, and the
|
419
|
+
following values are false: `false`, `"false"`, `"off"`, `"0"` and `0`.
|
418
420
|
|
419
421
|
### Date Time ISO8601
|
420
422
|
|
@@ -437,7 +439,7 @@ example: `1733923153`
|
|
437
439
|
### Default
|
438
440
|
|
439
441
|
```ruby
|
440
|
-
[:default, default_value,
|
442
|
+
[:default, default_value, subtype]
|
441
443
|
[:default, "USD", :string]
|
442
444
|
[:default, 0, :integer]
|
443
445
|
[:default, -> { Time.current.iso8601 }, :date_time_iso8601]
|
@@ -453,52 +455,59 @@ called.
|
|
453
455
|
### Description
|
454
456
|
|
455
457
|
```ruby
|
456
|
-
[:description, markdown_text,
|
458
|
+
[:description, markdown_text, subtype]
|
457
459
|
[:description, "Customer full name", :string]
|
458
460
|
[:description, "Rating score from 0 (bad) to 5 (good)", :integer]
|
459
461
|
```
|
460
462
|
|
461
|
-
Adds a description to the
|
463
|
+
Adds a description to the type. Descriptions are displayed in documentation
|
462
464
|
and do not affect validation in any way with. There is no overhead at runtime.
|
463
465
|
Markdown supported.
|
464
466
|
|
465
467
|
### Hash
|
466
468
|
|
467
469
|
```ruby
|
468
|
-
[:hash,
|
470
|
+
[:hash, keytype, valuetype, options = {}]
|
469
471
|
[:hash, :string, :string]
|
470
472
|
[:hash, :string, :integer]
|
471
473
|
[:hash, :string, :integer, empty: false]
|
472
474
|
[:hash, :string, [:array, :date_time_iso8601]]
|
473
475
|
```
|
474
476
|
|
475
|
-
Hashes are key value pairs where all keys must match
|
476
|
-
match
|
477
|
+
Hashes are key value pairs where all keys must match keytype and all values must
|
478
|
+
match valuetype. If you are expecting a hash with a specific set of keys use a
|
477
479
|
[record](#record) instead.
|
478
480
|
|
479
|
-
###
|
481
|
+
### Enum
|
480
482
|
|
481
483
|
```ruby
|
482
|
-
[:
|
483
|
-
[:
|
484
|
-
[:
|
484
|
+
[:enum, allowed_values]
|
485
|
+
[:enum, ["user", "admin"]]
|
486
|
+
[:enum, [10, 20, 30, 40, 50]]
|
485
487
|
```
|
486
488
|
|
487
|
-
|
489
|
+
### File
|
490
|
+
|
491
|
+
```ruby
|
492
|
+
:file
|
493
|
+
[:file, max_size: 2.megabytes]
|
494
|
+
[:file, content_types: %w[image/png image/jpeg]]
|
495
|
+
```
|
496
|
+
|
497
|
+
Value must be an uploaded file using "multipart/form-data" encoding.
|
488
498
|
|
489
499
|
### Integer
|
490
500
|
|
491
501
|
```ruby
|
492
502
|
:integer
|
493
|
-
[:integer, parse: true]
|
494
503
|
[:integer, negative: false]
|
495
504
|
[:integer, positive: false]
|
496
505
|
[:integer, min: 0] # inclusive
|
497
506
|
[:integer, max: 10] # inclusive
|
498
507
|
```
|
499
508
|
|
500
|
-
|
501
|
-
|
509
|
+
Integer encoded string values such as "10" or "-2" are automatically converted
|
510
|
+
to integer.
|
502
511
|
|
503
512
|
### Literal
|
504
513
|
|
@@ -506,48 +515,48 @@ If `parse: true` is specified then integer encoded string values such as "10" or
|
|
506
515
|
[:literal, value]
|
507
516
|
[:literal, 6379]
|
508
517
|
[:literal, "value"]
|
509
|
-
"value" # strings
|
518
|
+
"value" # strings are literal types, so you can use the shorter syntax.
|
510
519
|
```
|
511
520
|
|
512
|
-
A literal value behaves similar to
|
513
|
-
matching against multiple
|
521
|
+
A literal value behaves similar to an enum with a single value. Useful for
|
522
|
+
matching against multiple types in [`one_of`](#one-of).
|
514
523
|
|
515
524
|
### Nilable
|
516
525
|
|
517
526
|
```ruby
|
518
|
-
[:nilable,
|
527
|
+
[:nilable, subtype]
|
519
528
|
[:nilable, :string]
|
520
529
|
[:nilable, [:array, :integer]]
|
521
530
|
```
|
522
531
|
|
523
|
-
Value must either match the
|
532
|
+
Value must either match the subtype or be nil.
|
524
533
|
|
525
534
|
### One of
|
526
535
|
|
527
536
|
```ruby
|
528
|
-
[:one_of,
|
537
|
+
[:one_of, type1, type2, ..., typeN]
|
529
538
|
[:one_of, :string, :integer]
|
530
539
|
[:one_of, { email: :string }, { phone_number: :string }]
|
531
540
|
```
|
532
541
|
|
533
|
-
Attempts to validate against each
|
534
|
-
successfully matches the value. If none of the
|
542
|
+
Attempts to validate against each type in order stopping at the first type that
|
543
|
+
successfully matches the value. If none of the types match, an error is
|
535
544
|
returned.
|
536
545
|
|
537
546
|
### Record
|
538
547
|
|
539
548
|
```ruby
|
540
|
-
|
549
|
+
user_type = {
|
541
550
|
name: :string,
|
542
551
|
email: [:string, format: URI::MailTo::EMAIL_REGEXP]
|
543
552
|
}
|
544
553
|
|
545
|
-
|
554
|
+
address_type = {
|
546
555
|
country_name: :string,
|
547
556
|
zipcode: [:string, { format: /\d{6}-\d{3}/, strip: true }]
|
548
557
|
}
|
549
558
|
|
550
|
-
|
559
|
+
payment_type = {
|
551
560
|
currency: [:nilable, :string], # use :nilable for optional attribute
|
552
561
|
amount: :bigdecimal
|
553
562
|
}
|
@@ -561,8 +570,9 @@ records with array of records, etc.
|
|
561
570
|
|
562
571
|
```ruby
|
563
572
|
:string
|
564
|
-
[:string, strip: true]
|
573
|
+
[:string, strip: true] # " foo " gets transformed to "foo"
|
565
574
|
[:string, empty: false]
|
575
|
+
[:string, downcase: true] # "FOO" gets transformed to "foo"
|
566
576
|
[:string, format: URI::MailTo::EMAIL_REGEXP]
|
567
577
|
[:string, minlength: 8] # inclusive
|
568
578
|
[:string, maxlength: 20] # inclusive
|
@@ -604,9 +614,7 @@ your base controller. Use the following code as a starting point:
|
|
604
614
|
```ruby
|
605
615
|
class ApplicationController < ActionController::API
|
606
616
|
rescue_from Explicit::Request::InvalidParamsError do |err|
|
607
|
-
params
|
608
|
-
|
609
|
-
render json: { error: "invalid_params", params: }, status: 422
|
617
|
+
render json: { error: "invalid_params", params: err.errors }, status: 422
|
610
618
|
end
|
611
619
|
end
|
612
620
|
```
|
@@ -2,5 +2,37 @@
|
|
2
2
|
|
3
3
|
module Explicit
|
4
4
|
module ApplicationHelper
|
5
|
+
def type_render(type)
|
6
|
+
render partial: type.partial, locals: { type: }
|
7
|
+
end
|
8
|
+
|
9
|
+
def type_attribute_render(name:, type:)
|
10
|
+
render partial: "explicit/documentation/attribute", locals: { name:, type: }
|
11
|
+
end
|
12
|
+
|
13
|
+
def type_constraints(&block)
|
14
|
+
content_tag(:div, class: "flex flex-wrap gap-2", &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def type_constraint(name, value)
|
18
|
+
content_tag(:div, class: "bg-neutral-200 px-1 text-sm") do
|
19
|
+
content_tag(:span, name) + " " + content_tag(:span, value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def type_has_details?(type)
|
24
|
+
type.description.present? || type.has_details?
|
25
|
+
end
|
26
|
+
|
27
|
+
def format_request_example(request:, example:)
|
28
|
+
line_break = '<span class="text-white">\</span>'
|
29
|
+
|
30
|
+
<<~BASH.html_safe
|
31
|
+
#{example.to_curl_lines.join(" #{line_break}\n")}
|
32
|
+
|
33
|
+
# #{example.response.status} #{Rack::Utils::HTTP_STATUS_CODES[example.response.status]}
|
34
|
+
#{JSON.pretty_generate(example.response.data)}
|
35
|
+
BASH
|
36
|
+
end
|
5
37
|
end
|
6
38
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<div class="p-3" x-data="{ expanded: false }">
|
2
|
+
<div class="flex items-center gap-2" x-on:click="expanded = !expanded">
|
3
|
+
<span class="font-mono font-bold">
|
4
|
+
<%= name %>
|
5
|
+
</span>
|
6
|
+
|
7
|
+
<span class="text-sm text-neutral-500">
|
8
|
+
<%= type.summary %>
|
9
|
+
</span>
|
10
|
+
|
11
|
+
<% if type.param_location_path? %>
|
12
|
+
<span class="bg-neutral-800 text-white text-xs font-bold rounded px-1">in URL</span>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<% if type.has_details? || type.description.present? %>
|
16
|
+
<div class="record__summary__expand">
|
17
|
+
<span x-show="expanded">▲</span>
|
18
|
+
<span x-show="!expanded">▼</span>
|
19
|
+
</div>
|
20
|
+
<% end %>
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<% if type.has_details? || type.description.present? %>
|
24
|
+
<div class="mt-2" x-show="expanded">
|
25
|
+
<% if (description = type.description) %>
|
26
|
+
<div class="text-neutral-600 markdown">
|
27
|
+
<%= Explicit::Documentation::Markdown.to_html(description) %>
|
28
|
+
</div>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
<% if type.has_details? %>
|
32
|
+
<div class="record__subtype">
|
33
|
+
<%= type_render type %>
|
34
|
+
</div>
|
35
|
+
<% end %>
|
36
|
+
</div>
|
37
|
+
<% end %>
|
38
|
+
</div>
|
@@ -0,0 +1,166 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
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-50: #fafafa;
|
14
|
+
--color-neutral-100: #f5f5f5;
|
15
|
+
--color-neutral-200: #e5e5e5;
|
16
|
+
--color-neutral-300: #d4d4d8;
|
17
|
+
--color-neutral-400: #a3a3a3;
|
18
|
+
--color-neutral-500: #737373;
|
19
|
+
--color-neutral-600: #525252;
|
20
|
+
}
|
21
|
+
|
22
|
+
.page:not(:first-of-type) {
|
23
|
+
border-top: 1px solid var(--color-neutral-200);
|
24
|
+
}
|
25
|
+
.page__url {
|
26
|
+
background: var(--color-neutral-100);
|
27
|
+
border: 1px solid var(--color-neutral-300);
|
28
|
+
font-family: monospace;
|
29
|
+
padding: 0.8rem;
|
30
|
+
}
|
31
|
+
.page__url__shared {
|
32
|
+
color: var(--color-neutral-500);
|
33
|
+
}
|
34
|
+
.page__url__path {
|
35
|
+
font-weight: bold;
|
36
|
+
}
|
37
|
+
.page__container {
|
38
|
+
display: flex;
|
39
|
+
gap: 1rem;
|
40
|
+
margin-top: 1rem;
|
41
|
+
}
|
42
|
+
.page__request {
|
43
|
+
width: 50%;
|
44
|
+
}
|
45
|
+
.page__response {
|
46
|
+
width: 50%;
|
47
|
+
}
|
48
|
+
|
49
|
+
.markdown code {
|
50
|
+
background: var(--color-neutral-100);
|
51
|
+
color: var(--color-neutral-900);
|
52
|
+
}
|
53
|
+
.markdown p:first-of-type {
|
54
|
+
margin-block-start: 0em;
|
55
|
+
}
|
56
|
+
.markdown p:last-of-type {
|
57
|
+
margin-block-end: 0em;
|
58
|
+
}
|
59
|
+
.markdown ul {
|
60
|
+
list-style-type: disc;
|
61
|
+
}
|
62
|
+
.markdown li {
|
63
|
+
margin-left: 2rem;
|
64
|
+
}
|
65
|
+
|
66
|
+
.record__param {
|
67
|
+
padding: 0.8rem;
|
68
|
+
}
|
69
|
+
.record__param:not(:last-of-type) {
|
70
|
+
border-bottom: 1px solid var(--color-neutral-200);
|
71
|
+
}
|
72
|
+
.record__summary {
|
73
|
+
display: flex;
|
74
|
+
align-items: end;
|
75
|
+
gap: 1rem;
|
76
|
+
}
|
77
|
+
.record__summary__expand {
|
78
|
+
margin-left: auto;
|
79
|
+
color: var(--color-neutral-400);
|
80
|
+
}
|
81
|
+
.record__constraint {
|
82
|
+
font-size: 12px;
|
83
|
+
background: var(--color-neutral-200);
|
84
|
+
padding: 1px 4px;
|
85
|
+
border-radius: 1px;
|
86
|
+
}
|
87
|
+
.record__subtype {
|
88
|
+
margin-top: 0.5rem;
|
89
|
+
}
|
90
|
+
|
91
|
+
.responses__tabs {
|
92
|
+
display: flex;
|
93
|
+
}
|
94
|
+
.responses__tab {
|
95
|
+
padding: 10px;
|
96
|
+
}
|
97
|
+
.responses__tab.active {
|
98
|
+
border-left: 1px solid var(--color-neutral-300);
|
99
|
+
border-top: 1px solid var(--color-neutral-300);
|
100
|
+
border-right: 1px solid var(--color-neutral-300);
|
101
|
+
border-bottom: 1px solid #FFF;
|
102
|
+
margin-bottom: -1px;
|
103
|
+
}
|
104
|
+
.responses__types {
|
105
|
+
border: 1px solid var(--color-neutral-300);
|
106
|
+
}
|
107
|
+
</style>
|
108
|
+
|
109
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
110
|
+
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/intersect@3.x.x/dist/cdn.min.js"></script>
|
111
|
+
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
112
|
+
</head>
|
113
|
+
|
114
|
+
<body>
|
115
|
+
<div class="flex" x-data="{ activeLink: null }">
|
116
|
+
<section class="bg-neutral-100 border-r w-[280px] shrink-0 h-screen overflow-y-auto">
|
117
|
+
<% if company_logo_url.present? %>
|
118
|
+
<div class="flex items-center justify-center mt-4 mb-8">
|
119
|
+
<img src="<%= company_logo_url %>" class="max-w-full h-auto" />
|
120
|
+
</div>
|
121
|
+
<% end %>
|
122
|
+
|
123
|
+
<div class="text-center text-xs border-y mb-4 flex divide-x">
|
124
|
+
<div class="p-1 text-neutral-500 w-1/2">
|
125
|
+
Version <%= version %>
|
126
|
+
</div>
|
127
|
+
<div class="p-1 w-1/2">
|
128
|
+
<%= link_to url_helpers.explicit_documentation_swagger_path, target: "_blank", class: "flex items-center justify-center gap-1 text-neutral-900" do %>
|
129
|
+
<span>Swagger</span>
|
130
|
+
|
131
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
|
132
|
+
<path d="M6.22 8.72a.75.75 0 0 0 1.06 1.06l5.22-5.22v1.69a.75.75 0 0 0 1.5 0v-3.5a.75.75 0 0 0-.75-.75h-3.5a.75.75 0 0 0 0 1.5h1.69L6.22 8.72Z" />
|
133
|
+
<path d="M3.5 6.75c0-.69.56-1.25 1.25-1.25H7A.75.75 0 0 0 7 4H4.75A2.75 2.75 0 0 0 2 6.75v4.5A2.75 2.75 0 0 0 4.75 14h4.5A2.75 2.75 0 0 0 12 11.25V9a.75.75 0 0 0-1.5 0v2.25c0 .69-.56 1.25-1.25 1.25h-4.5c-.69 0-1.25-.56-1.25-1.25v-4.5Z" />
|
134
|
+
</svg>
|
135
|
+
<% end %>
|
136
|
+
</div>
|
137
|
+
</div>
|
138
|
+
|
139
|
+
<div class="space-y-4" >
|
140
|
+
<% sections.each do |section| %>
|
141
|
+
<details class="px-4" open>
|
142
|
+
<summary class="font-bold"><%= section.name %></summary>
|
143
|
+
|
144
|
+
<% section.pages.each do |page| %>
|
145
|
+
<%= link_to page.title,
|
146
|
+
"##{page.anchor}",
|
147
|
+
class: "block pl-4 hover:bg-neutral-200",
|
148
|
+
"x-bind:class" => "{ 'bg-neutral-200': activeLink == '#{page.anchor}' }" %>
|
149
|
+
<% end %>
|
150
|
+
</details>
|
151
|
+
<% end %>
|
152
|
+
</div>
|
153
|
+
</section>
|
154
|
+
|
155
|
+
<main class="relative grow h-screen overflow-auto">
|
156
|
+
<% sections.each do |section| %>
|
157
|
+
<% section.pages.each do |page| %>
|
158
|
+
<div class="p-8 border-t">
|
159
|
+
<%= render partial: page.partial, locals: { page: } %>
|
160
|
+
</div>
|
161
|
+
<% end %>
|
162
|
+
<% end %>
|
163
|
+
</main>
|
164
|
+
</div>
|
165
|
+
</body>
|
166
|
+
</html>
|