explicit 0.2.4 → 0.2.5
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 +65 -13
- data/app/views/explicit/documentation/_attribute.html.erb +2 -2
- data/app/views/explicit/documentation/_page.html.erb +7 -82
- data/app/views/explicit/documentation/_request.html.erb +3 -6
- data/app/views/explicit/documentation/type/_array.html.erb +1 -1
- data/app/views/explicit/documentation/type/_date.html.erb +1 -0
- data/app/views/explicit/documentation/type/_date_range.html.erb +6 -0
- data/app/views/explicit/documentation/type/_date_time_iso8601_range.html.erb +8 -0
- data/app/views/explicit/documentation/type/_hash.html.erb +2 -2
- data/app/views/explicit/documentation/type/_string.html.erb +4 -4
- data/config/locales/en.yml +34 -7
- data/lib/explicit/documentation/builder.rb +3 -0
- data/lib/explicit/documentation/output/swagger.rb +4 -0
- data/lib/explicit/documentation/output/webpage.rb +5 -0
- data/lib/explicit/request/invalid_response_error.rb +1 -1
- data/lib/explicit/request.rb +18 -16
- data/lib/explicit/test_helper.rb +13 -0
- data/lib/explicit/type/agreement.rb +5 -5
- data/lib/explicit/type/array.rb +12 -13
- data/lib/explicit/type/big_decimal.rb +14 -14
- data/lib/explicit/type/boolean.rb +4 -5
- data/lib/explicit/type/date.rb +66 -0
- data/lib/explicit/type/date_range.rb +95 -0
- data/lib/explicit/type/date_time_iso8601.rb +33 -8
- data/lib/explicit/type/date_time_iso8601_range.rb +108 -0
- data/lib/explicit/type/date_time_unix_epoch.rb +69 -0
- data/lib/explicit/type/enum.rb +4 -5
- data/lib/explicit/type/file.rb +7 -7
- data/lib/explicit/type/hash.rb +14 -14
- data/lib/explicit/type/integer.rb +9 -9
- data/lib/explicit/type/literal.rb +4 -5
- data/lib/explicit/type/modifiers/default.rb +1 -1
- data/lib/explicit/type/modifiers/description.rb +1 -1
- data/lib/explicit/type/modifiers/nilable.rb +1 -1
- data/lib/explicit/type/one_of.rb +59 -1
- data/lib/explicit/type/record.rb +5 -6
- data/lib/explicit/type/string.rb +19 -19
- data/lib/explicit/type.rb +58 -23
- data/lib/explicit/version.rb +1 -1
- data/lib/explicit.rb +4 -1
- metadata +10 -4
- data/lib/explicit/type/date_time_posix.rb +0 -44
- /data/app/views/explicit/documentation/type/{_date_time_posix.html.erb → _date_time_unix_epoch.html.erb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5c59437da284708551b719ae8b40a62e98e38886b6327807edef311068b7634
|
4
|
+
data.tar.gz: 9175b15ba7d61853e56ec823a781565b413886793f32efeb8f5ae37459511ff4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ebe27efff3c7a9e1379e2e2025a9c75499905efaa1bdaaacff51ff17e963175e1cfc2d2e81f98f4fce615f6161be792ebad385a38471e2a9901bb076e17f3d3
|
7
|
+
data.tar.gz: 1da9daabfa4d8e59c85051eff97fce1f9091fb744ec3294403fc7baff972c50cf0b965ea0670f92fe26a9d8bbc01337a8a7631f1a32465e2f8e9ce764f036657
|
data/README.md
CHANGED
@@ -3,7 +3,9 @@
|
|
3
3
|
Explicit is a validation and documentation library for REST APIs that enforces
|
4
4
|
documented types at runtime.
|
5
5
|
|
6
|
-

|
6
|
+
|  |
|
7
|
+
| :-----------------------------------------------------------------------------------------------------------------------------------: |
|
8
|
+
| [Click here](https://luizpvas.github.io/explicit_documentation.html) to visit the example documentation page |
|
7
9
|
|
8
10
|
1. [Installation](#installation)
|
9
11
|
2. [Defining requests](#defining-requests)
|
@@ -15,9 +17,12 @@ documented types at runtime.
|
|
15
17
|
7. Types
|
16
18
|
- [Agreement](#agreement)
|
17
19
|
- [Array](#array)
|
18
|
-
- [BigDecimal](#
|
20
|
+
- [BigDecimal](#big_decimal)
|
19
21
|
- [Boolean](#boolean)
|
22
|
+
- [Date](#date)
|
23
|
+
- [Date Range](#date-range)
|
20
24
|
- [Date Time ISO8601](#date-time-iso8601)
|
25
|
+
- [Date Time ISO8601 Range](#date-time-iso8601-range)
|
21
26
|
- [Date Time Posix](#date-time-posix)
|
22
27
|
- [Default](#default)
|
23
28
|
- [Description](#description)
|
@@ -58,6 +63,10 @@ available:
|
|
58
63
|
- `header(name, type)` - Adds a type to the request header.
|
59
64
|
- `param(name, type, options = {})` - Adds a type to the request param.
|
60
65
|
It works for params in the request body, query string and path params.
|
66
|
+
- The following conveniences are available via options:
|
67
|
+
- `optional: true` - Makes the param nilable.
|
68
|
+
- `default: value` - Sets a default value to the param, which makes it optional.
|
69
|
+
- `description: "text"` - Adds a documentation to the param. Markdown supported.
|
61
70
|
- `response(status, type)` - Adds a response type. You can add multiple
|
62
71
|
responses with different formats.
|
63
72
|
- `add_example(params:, headers:, response:)` - Adds an example to the
|
@@ -245,7 +254,8 @@ Call `Explicit::Documentation.new` to group, organize and publish the
|
|
245
254
|
documentation for your API. The following methods are available:
|
246
255
|
|
247
256
|
- `page_title(text)` - Sets the web page title.
|
248
|
-
- `company_logo_url(url)` - Shows the company logo
|
257
|
+
- `company_logo_url(url)` - Shows the company logo in the navigation menu.
|
258
|
+
- `favicon_url(url)` - Adds a favicon to the web page.
|
249
259
|
- `version(semver)` - Sets the version of the API. Default: "1.0"
|
250
260
|
- `section(name, &block)` - Adds a section to the navigation menu.
|
251
261
|
- `add(request)` - Adds a request to the section
|
@@ -258,6 +268,7 @@ module MyApp::API::V1
|
|
258
268
|
Documentation = Explicit::Documentation.new do
|
259
269
|
page_title "Acme API Docs"
|
260
270
|
company_logo_url "https://my-app.com/logo.png"
|
271
|
+
favicon_url "https://my-app.com/favicon.ico"
|
261
272
|
version "1.0.5"
|
262
273
|
|
263
274
|
section "Introduction" do
|
@@ -401,9 +412,9 @@ value is invalid then the array is invalid.
|
|
401
412
|
### BigDecimal
|
402
413
|
|
403
414
|
```ruby
|
404
|
-
:
|
405
|
-
[:
|
406
|
-
[:
|
415
|
+
:big_decimal
|
416
|
+
[:big_decimal, min: 0] # inclusive
|
417
|
+
[:big_decimal, max: 100] # inclusive
|
407
418
|
```
|
408
419
|
|
409
420
|
Value must be an integer or a string like `"0.2"` to avoid rounding errors.
|
@@ -418,19 +429,60 @@ Value must be an integer or a string like `"0.2"` to avoid rounding errors.
|
|
418
429
|
The following values are true: `true`, `"true"`, `"on"`, `"1"` and `1`, and the
|
419
430
|
following values are false: `false`, `"false"`, `"off"`, `"0"` and `0`.
|
420
431
|
|
432
|
+
### Date
|
433
|
+
|
434
|
+
```ruby
|
435
|
+
:date
|
436
|
+
[:date, min: -> { 2.months.ago }]
|
437
|
+
[:date, max: -> { 1.month.from_now }]
|
438
|
+
```
|
439
|
+
|
440
|
+
A date in the format of `"YYYY-MM-DD"`.
|
441
|
+
|
442
|
+
### Date Range
|
443
|
+
|
444
|
+
```ruby
|
445
|
+
:date_range
|
446
|
+
[:date_range, min_range: 2.days]
|
447
|
+
[:date_range, max_range: 30.days]
|
448
|
+
[:date_range, min_date: -> { 2.months.ago }]
|
449
|
+
[:date_range, max_date: -> { 1.day.from_now }]
|
450
|
+
```
|
451
|
+
|
452
|
+
A range between two dates in the format of `"YYYY-MM-DD..YYYY-MM-DD"`.
|
453
|
+
The range is inclusive.
|
454
|
+
|
421
455
|
### Date Time ISO8601
|
422
456
|
|
423
457
|
```ruby
|
424
458
|
:date_time_iso8601
|
459
|
+
[:date_time_iso8601, min: -> { 2.months.ago }]
|
460
|
+
[:date_time_iso8601, max: -> { Time.current.end_of_day }]
|
425
461
|
```
|
426
462
|
|
427
463
|
String encoded date time following the ISO 8601 spec. For example:
|
428
464
|
`"2024-12-10T14:21:00Z"`
|
429
465
|
|
466
|
+
### Date Time ISO8601 Range
|
467
|
+
|
468
|
+
```ruby
|
469
|
+
:date_time_iso8601_range
|
470
|
+
[:date_time_iso8601_range, min_range: 30.minutes]
|
471
|
+
[:date_time_iso8601_range, max_range: 2.months]
|
472
|
+
[:date_time_iso8601_range, min_date_time: -> { 2.months.ago }]
|
473
|
+
[:date_time_iso8601_range, max_date_time: -> { 1.hour.from_now }]
|
474
|
+
```
|
475
|
+
|
476
|
+
A range between two date times in the format of
|
477
|
+
`"start_date_time..end_date_time"`. For example:
|
478
|
+
`"2024-12-10T14:00:00Z..2024-12-11T15:00:00Z"`. The range is inclusive.
|
479
|
+
|
430
480
|
### Date Time Posix
|
431
481
|
|
432
482
|
```ruby
|
433
|
-
:
|
483
|
+
:date_time_unix_epoch
|
484
|
+
[:date_time_unix_epoch, min: -> { 2.months.ago }]
|
485
|
+
[:date_time_unix_epoch, max: -> { Time.current.end_of_day }]
|
434
486
|
```
|
435
487
|
|
436
488
|
The number of elapsed seconds since January 1, 1970 in timezone UTC. For
|
@@ -467,15 +519,15 @@ Markdown supported.
|
|
467
519
|
### Hash
|
468
520
|
|
469
521
|
```ruby
|
470
|
-
[:hash,
|
522
|
+
[:hash, key_type, value_type, options = {}]
|
471
523
|
[:hash, :string, :string]
|
472
524
|
[:hash, :string, :integer]
|
473
525
|
[:hash, :string, :integer, empty: false]
|
474
526
|
[:hash, :string, [:array, :date_time_iso8601]]
|
475
527
|
```
|
476
528
|
|
477
|
-
Hashes are key value pairs where all keys must match
|
478
|
-
match
|
529
|
+
Hashes are key value pairs where all keys must match key_type and all values must
|
530
|
+
match value_type. If you are expecting a hash with a specific set of keys use a
|
479
531
|
[record](#record) instead.
|
480
532
|
|
481
533
|
### Enum
|
@@ -558,7 +610,7 @@ address_type = {
|
|
558
610
|
|
559
611
|
payment_type = {
|
560
612
|
currency: [:nilable, :string], # use :nilable for optional attribute
|
561
|
-
amount: :
|
613
|
+
amount: :big_decimal
|
562
614
|
}
|
563
615
|
```
|
564
616
|
|
@@ -574,8 +626,8 @@ records with array of records, etc.
|
|
574
626
|
[:string, empty: false]
|
575
627
|
[:string, downcase: true] # "FOO" gets transformed to "foo"
|
576
628
|
[:string, format: URI::MailTo::EMAIL_REGEXP]
|
577
|
-
[:string,
|
578
|
-
[:string,
|
629
|
+
[:string, min_length: 8] # inclusive
|
630
|
+
[:string, max_length: 20] # inclusive
|
579
631
|
```
|
580
632
|
|
581
633
|
# Configuration
|
@@ -13,7 +13,7 @@
|
|
13
13
|
<% end %>
|
14
14
|
|
15
15
|
<% if type.has_details? || type.description.present? %>
|
16
|
-
<div class="
|
16
|
+
<div class="ml-auto text-neutral-300">
|
17
17
|
<span x-show="expanded">▲</span>
|
18
18
|
<span x-show="!expanded">▼</span>
|
19
19
|
</div>
|
@@ -29,7 +29,7 @@
|
|
29
29
|
<% end %>
|
30
30
|
|
31
31
|
<% if type.has_details? %>
|
32
|
-
<div class="
|
32
|
+
<div class="mt-2">
|
33
33
|
<%= type_render type %>
|
34
34
|
</div>
|
35
35
|
<% end %>
|
@@ -1,49 +1,16 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
2
|
<html>
|
3
3
|
<head>
|
4
|
-
<title><%=
|
4
|
+
<title><%= page_title || "API Documentation" %></title>
|
5
|
+
|
6
|
+
<% if favicon_url %>
|
7
|
+
<link rel="icon" href="<%= favicon_url %>" />
|
8
|
+
<% end %>
|
5
9
|
|
6
10
|
<style>
|
7
11
|
html, body {
|
8
|
-
font-family: sans-serif;
|
9
12
|
font-size: 14px;
|
10
|
-
|
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%;
|
13
|
+
font-family: system-ui, sans-serif;
|
47
14
|
}
|
48
15
|
|
49
16
|
.markdown code {
|
@@ -62,48 +29,6 @@
|
|
62
29
|
.markdown li {
|
63
30
|
margin-left: 2rem;
|
64
31
|
}
|
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
32
|
</style>
|
108
33
|
|
109
34
|
<script src="https://cdn.tailwindcss.com"></script>
|
@@ -113,7 +38,7 @@
|
|
113
38
|
|
114
39
|
<body>
|
115
40
|
<div class="flex" x-data="{ activeLink: null }">
|
116
|
-
<section class="bg-neutral-100 border-r w-[280px] shrink-0 h-screen overflow-y-auto">
|
41
|
+
<section class="hidden lg:block bg-neutral-100 border-r w-[280px] shrink-0 h-screen overflow-y-auto">
|
117
42
|
<% if company_logo_url.present? %>
|
118
43
|
<div class="flex items-center justify-center mt-4 mb-8">
|
119
44
|
<img src="<%= company_logo_url %>" class="max-w-full h-auto" />
|
@@ -26,8 +26,8 @@
|
|
26
26
|
</div>
|
27
27
|
<% end %>
|
28
28
|
|
29
|
-
<div class="flex mt-4 gap-8">
|
30
|
-
<div class="
|
29
|
+
<div class="block lg:flex mt-4 gap-8">
|
30
|
+
<div class="w-full lg:w-1/2 space-y-4">
|
31
31
|
<% if (description = page.request.get_description) %>
|
32
32
|
<div class="markdown">
|
33
33
|
<%= Explicit::Documentation::Markdown.to_html(description) %>
|
@@ -57,10 +57,7 @@
|
|
57
57
|
|
58
58
|
<% statuses = page.request.responses.keys.sort %>
|
59
59
|
|
60
|
-
<div
|
61
|
-
class="page__response"
|
62
|
-
x-data="{ active: <%= statuses.first %> }"
|
63
|
-
>
|
60
|
+
<div class="w-full lg:w-1/2 mt-4 lt:mt-0" x-data="{ active: <%= statuses.first %> }">
|
64
61
|
<div class="flex">
|
65
62
|
<% statuses.each do |status| %>
|
66
63
|
<button
|
@@ -0,0 +1 @@
|
|
1
|
+
A date in the format of <code>"YYYY-MM-DD"</code>.
|
@@ -0,0 +1,6 @@
|
|
1
|
+
A date followed by two dots then another date, no spaces: <code>"YYYY-MM-DD..YYYY-MM-DD"</code>.
|
2
|
+
|
3
|
+
<%= type_constraints do %>
|
4
|
+
<%= type_constraint "Min range:", type.min_range.inspect if type.min_range %>
|
5
|
+
<%= type_constraint "Max range:", type.max_range.inspect if type.max_range %>
|
6
|
+
<% end %>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
A range between two date times in the format of
|
2
|
+
<code>"start_date_time..end_date_time"</code> where both date times are valid
|
3
|
+
according to ISO8601. For example: <code>"2024-12-10T14:00:00Z..2024-12-11T15:00:00Z"</code>.
|
4
|
+
|
5
|
+
<%= type_constraints do %>
|
6
|
+
<%= type_constraint "Min range:", type.min_range.inspect if type.min_range %>
|
7
|
+
<%= type_constraint "Max range:", type.max_range.inspect if type.max_range %>
|
8
|
+
<% end %>
|
@@ -1,4 +1,4 @@
|
|
1
1
|
<div class="border divide-y">
|
2
|
-
<%= type_attribute_render name: "Keys", type: type.
|
3
|
-
<%= type_attribute_render name: "Values", type: type.
|
2
|
+
<%= type_attribute_render name: "Keys", type: type.key_type %>
|
3
|
+
<%= type_attribute_render name: "Values", type: type.value_type %>
|
4
4
|
</div>
|
@@ -3,12 +3,12 @@
|
|
3
3
|
<%= type_constraint "not", "empty" %>
|
4
4
|
<% end %>
|
5
5
|
|
6
|
-
<% if type.
|
7
|
-
<%= type_constraint "
|
6
|
+
<% if type.min_length %>
|
7
|
+
<%= type_constraint "min_length", type.min_length %>
|
8
8
|
<% end %>
|
9
9
|
|
10
|
-
<% if type.
|
11
|
-
<%= type_constraint "
|
10
|
+
<% if type.max_length %>
|
11
|
+
<%= type_constraint "max_length", type.max_length %>
|
12
12
|
<% end %>
|
13
13
|
|
14
14
|
<% if type.format %>
|
data/config/locales/en.yml
CHANGED
@@ -2,11 +2,31 @@ en:
|
|
2
2
|
explicit:
|
3
3
|
errors:
|
4
4
|
agreement: "must be accepted"
|
5
|
-
array: "
|
6
|
-
|
5
|
+
array: "must be an array"
|
6
|
+
array_item: "invalid item at index(%{index}): %{error}"
|
7
|
+
big_decimal: "must be a string-encoded decimal number"
|
7
8
|
boolean: "must be a boolean"
|
8
|
-
|
9
|
-
|
9
|
+
date_range_format: 'must be a string in the format of "YYYY-MM-DD..YYYY-MM-DD"'
|
10
|
+
date_range_inverted: "starting date must be the same day or a day before the ending date"
|
11
|
+
date_range_min_date: "starting date must not be a day before %{min_date}"
|
12
|
+
date_range_max_date: "ending date must not be a day after %{max_date}"
|
13
|
+
date_range_min_range: "must not be less than %{min_range}"
|
14
|
+
date_range_max_range: "must not be more than %{max_range}"
|
15
|
+
date_time_iso8601_range_format: 'must be a string in the format of: "YYYY-MM-DDTHH:MM:SS..YYYY-MM-DDTHH:MM:SS"'
|
16
|
+
date_time_iso8601_range_inverted: "starting datetime must be a moment before ending datetime"
|
17
|
+
date_time_iso8601_range_min_date_time: "starting datetime must not be a moment before %{min_date_time}"
|
18
|
+
date_time_iso8601_range_max_date_time: "ending datetime must not be a moment after %{max_date_time}"
|
19
|
+
date_time_iso8601_range_min_range: "must not be less than %{min_range}"
|
20
|
+
date_time_iso8601_range_max_range: "must not be more than %{max_range}"
|
21
|
+
date_time_iso8601: "must be a valid datetime according to ISO8601"
|
22
|
+
date_time_iso8601_min: "must not be a moment before %{min}"
|
23
|
+
date_time_iso8601_max: "must not be a moment after %{max}"
|
24
|
+
date_time_unix_epoch: "must be a valid posix timestamp"
|
25
|
+
date_time_unix_epoch_min: "must not be a moment before %{min}"
|
26
|
+
date_time_unix_epoch_max: "must not be a moment after %{max}"
|
27
|
+
date_format: 'must be a date in the format "YYYY-MM-DD"'
|
28
|
+
date_min: "must not be a date before %{min}"
|
29
|
+
date_max: "must not be a date after %{max}"
|
10
30
|
hash: "must be an object"
|
11
31
|
hash_key: "invalid key (%{key}): %{error}"
|
12
32
|
hash_value: "invalid value at key (%{key}): %{error}"
|
@@ -20,17 +40,24 @@ en:
|
|
20
40
|
not_negative: "must not be negative"
|
21
41
|
not_positive: "must not be positive"
|
22
42
|
literal: "must be %{value}"
|
43
|
+
one_of_separator: "OR"
|
23
44
|
string: "must be a string"
|
24
45
|
empty: "must not be empty"
|
25
|
-
|
26
|
-
|
46
|
+
min_length: "length must be at least %{min_length}"
|
47
|
+
max_length: "length must be at most %{max_length}"
|
27
48
|
format: "must have format %{regex}"
|
28
49
|
swagger:
|
29
50
|
agreement: "* Must be accepted (true)"
|
30
51
|
big_decimal_min: "* Minimum: %{min}"
|
31
52
|
big_decimal_max: "* Maximum: %{max}"
|
53
|
+
date_range: '* The value must be a range between two dates in the format of: "YYYY-MM-DD..YYYY-MM-DD"'
|
54
|
+
date_range_min_range: "* The range must not be less than %{min_range}"
|
55
|
+
date_range_max_range: "* The range must not be more than %{max_range}"
|
56
|
+
date_time_iso8601_range: '* The value must be a range between two date times in the format of: "YYYY-MM-DDTHH:MM:SS..YYYY-MM-DDTHH:MM:SS"'
|
57
|
+
date_time_iso8601_range_min_range: "* The range must not be less than %{min_range}"
|
58
|
+
date_time_iso8601_range_max_range: "* The range must not be more than %{max_range}"
|
32
59
|
date_time_iso8601: "* Must be valid according to ISO 8601"
|
33
|
-
|
60
|
+
date_time_unix_epoch: "* POSIX time or Unix epoch is the amount of seconds since 1970-01-01"
|
34
61
|
file_max_size: "* Max size: %{max_size}"
|
35
62
|
file_content_types: "* Content types: %{content_types}"
|
36
63
|
hash_not_empty: "* Must have at least one value"
|
@@ -19,6 +19,9 @@ module Explicit::Documentation
|
|
19
19
|
def company_logo_url(url) = (@company_logo_url = url)
|
20
20
|
def get_company_logo_url = @company_logo_url
|
21
21
|
|
22
|
+
def favicon_url(url) = (@favicon_url = url)
|
23
|
+
def get_favicon_url = @favicon_url
|
24
|
+
|
22
25
|
def version(version) = (@version = version)
|
23
26
|
def get_version = @version
|
24
27
|
|
@@ -34,6 +34,10 @@ module Explicit::Documentation::Output
|
|
34
34
|
[200, {"Content-Type" => "application/json"}, [@swagger_document.to_json]]
|
35
35
|
end
|
36
36
|
|
37
|
+
def inspect
|
38
|
+
"#{self.class.name}#call"
|
39
|
+
end
|
40
|
+
|
37
41
|
private
|
38
42
|
def get_base_url
|
39
43
|
base_urls = builder.requests.map(&:get_base_url).uniq
|
@@ -14,6 +14,10 @@ module Explicit::Documentation::Output
|
|
14
14
|
[200, {}, [@html]]
|
15
15
|
end
|
16
16
|
|
17
|
+
def inspect
|
18
|
+
"#{self.class.name}#call"
|
19
|
+
end
|
20
|
+
|
17
21
|
private
|
18
22
|
def render_documentation_page
|
19
23
|
Explicit::ApplicationController.render(
|
@@ -22,6 +26,7 @@ module Explicit::Documentation::Output
|
|
22
26
|
url_helpers: @builder.rails_engine.routes.url_helpers,
|
23
27
|
page_title: builder.get_page_title,
|
24
28
|
company_logo_url: builder.get_company_logo_url,
|
29
|
+
favicon_url: builder.get_favicon_url,
|
25
30
|
version: builder.get_version,
|
26
31
|
sections: builder.sections,
|
27
32
|
}
|
@@ -9,7 +9,7 @@ Got:
|
|
9
9
|
|
10
10
|
HTTP #{response.status} #{JSON.pretty_generate(response.data)}
|
11
11
|
|
12
|
-
This response doesn't match
|
12
|
+
This response doesn't match expected responses:
|
13
13
|
|
14
14
|
#{error.presence || " ==> no response types found for status #{response.status}"}
|
15
15
|
|
data/lib/explicit/request.rb
CHANGED
@@ -10,7 +10,9 @@ class Explicit::Request
|
|
10
10
|
@responses = Hash.new { |hash, key| hash[key] = [] }
|
11
11
|
@examples = Hash.new { |hash, key| hash[key] = [] }
|
12
12
|
|
13
|
-
|
13
|
+
instance_eval(&block)
|
14
|
+
|
15
|
+
if Explicit.configuration.rescue_from_invalid_params? && @params.any?
|
14
16
|
@responses[422] << {
|
15
17
|
error: "invalid_params",
|
16
18
|
params: [
|
@@ -20,28 +22,28 @@ class Explicit::Request
|
|
20
22
|
]
|
21
23
|
}
|
22
24
|
end
|
23
|
-
|
24
|
-
instance_eval(&block)
|
25
25
|
end
|
26
26
|
|
27
27
|
def new(&block)
|
28
|
-
|
28
|
+
this = self
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
self.class.new do
|
31
|
+
instance_variable_set(:@base_url, this.get_base_url)
|
32
|
+
instance_variable_set(:@base_path, this.get_base_path)
|
33
|
+
instance_variable_set(:@routes, this.routes.dup)
|
34
|
+
instance_variable_set(:@headers, this.headers.dup)
|
35
|
+
instance_variable_set(:@params, this.params.dup)
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
this.responses.each do |status, types|
|
38
|
+
@responses[status] = types.dup
|
39
|
+
end
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
this.examples.each do |status, examples|
|
42
|
+
@examples[status] = examples.dup
|
43
|
+
end
|
43
44
|
|
44
|
-
|
45
|
+
instance_eval(&block)
|
46
|
+
end
|
45
47
|
end
|
46
48
|
|
47
49
|
def get(path) = @routes << Route.new(method: :get, path:)
|
data/lib/explicit/test_helper.rb
CHANGED
@@ -54,6 +54,19 @@ module Explicit::TestHelper
|
|
54
54
|
|
55
55
|
process(method, path, params:, headers:)
|
56
56
|
|
57
|
+
if @response.headers["content-type"]&.include?("text/html")
|
58
|
+
html = @response.parsed_body
|
59
|
+
|
60
|
+
html.search("style").each { _1.remove }
|
61
|
+
html.search("script").each { _1.remove }
|
62
|
+
|
63
|
+
raise <<~TXT
|
64
|
+
Unexpected HTML response:
|
65
|
+
|
66
|
+
#{html.text}
|
67
|
+
TXT
|
68
|
+
end
|
69
|
+
|
57
70
|
response = Explicit::Request::Response.new(
|
58
71
|
status: @response.status,
|
59
72
|
data: @response.parsed_body.deep_symbolize_keys
|
@@ -8,7 +8,7 @@ class Explicit::Type::Agreement < Explicit::Type
|
|
8
8
|
if VALUES.include?(value)
|
9
9
|
OK
|
10
10
|
else
|
11
|
-
|
11
|
+
error_i18n("agreement")
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -28,12 +28,12 @@ class Explicit::Type::Agreement < Explicit::Type
|
|
28
28
|
|
29
29
|
concerning :Swagger do
|
30
30
|
def swagger_schema
|
31
|
-
{
|
31
|
+
merge_base_swagger_schema({
|
32
32
|
type: "boolean",
|
33
|
-
|
33
|
+
description_topics: [
|
34
34
|
swagger_i18n("agreement")
|
35
|
-
]
|
36
|
-
}
|
35
|
+
]
|
36
|
+
})
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
data/lib/explicit/type/array.rb
CHANGED
@@ -1,28 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Explicit::Type::Array < Explicit::Type
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :item_type, :empty
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@
|
6
|
+
def initialize(item_type:, empty: true)
|
7
|
+
@item_type = Explicit::Type.build(item_type)
|
8
8
|
@empty = empty
|
9
9
|
end
|
10
10
|
|
11
11
|
def validate(values)
|
12
|
-
return
|
12
|
+
return error_i18n("array") if !values.is_a?(::Array)
|
13
13
|
|
14
14
|
if values.empty? && !empty
|
15
|
-
return
|
15
|
+
return error_i18n("empty")
|
16
16
|
end
|
17
17
|
|
18
18
|
validated = []
|
19
19
|
|
20
20
|
values.each.with_index do |value, index|
|
21
|
-
case
|
21
|
+
case item_type.validate(value)
|
22
22
|
in [:ok, value]
|
23
23
|
validated << value
|
24
24
|
in [:error, error]
|
25
|
-
return
|
25
|
+
return error_i18n("array_item", index:, error:)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -31,7 +31,7 @@ class Explicit::Type::Array < Explicit::Type
|
|
31
31
|
|
32
32
|
concerning :Webpage do
|
33
33
|
def summary
|
34
|
-
"array of #{
|
34
|
+
"array of #{item_type.summary}"
|
35
35
|
end
|
36
36
|
|
37
37
|
def partial
|
@@ -45,12 +45,11 @@ class Explicit::Type::Array < Explicit::Type
|
|
45
45
|
|
46
46
|
concerning :Swagger do
|
47
47
|
def swagger_schema
|
48
|
-
{
|
48
|
+
merge_base_swagger_schema({
|
49
49
|
type: "array",
|
50
|
-
items:
|
51
|
-
minItems: empty ? 0 : 1
|
52
|
-
|
53
|
-
}.compact_blank
|
50
|
+
items: item_type.swagger_schema,
|
51
|
+
minItems: empty ? 0 : 1
|
52
|
+
})
|
54
53
|
end
|
55
54
|
end
|
56
55
|
end
|