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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +65 -13
  3. data/app/views/explicit/documentation/_attribute.html.erb +2 -2
  4. data/app/views/explicit/documentation/_page.html.erb +7 -82
  5. data/app/views/explicit/documentation/_request.html.erb +3 -6
  6. data/app/views/explicit/documentation/type/_array.html.erb +1 -1
  7. data/app/views/explicit/documentation/type/_date.html.erb +1 -0
  8. data/app/views/explicit/documentation/type/_date_range.html.erb +6 -0
  9. data/app/views/explicit/documentation/type/_date_time_iso8601_range.html.erb +8 -0
  10. data/app/views/explicit/documentation/type/_hash.html.erb +2 -2
  11. data/app/views/explicit/documentation/type/_string.html.erb +4 -4
  12. data/config/locales/en.yml +34 -7
  13. data/lib/explicit/documentation/builder.rb +3 -0
  14. data/lib/explicit/documentation/output/swagger.rb +4 -0
  15. data/lib/explicit/documentation/output/webpage.rb +5 -0
  16. data/lib/explicit/request/invalid_response_error.rb +1 -1
  17. data/lib/explicit/request.rb +18 -16
  18. data/lib/explicit/test_helper.rb +13 -0
  19. data/lib/explicit/type/agreement.rb +5 -5
  20. data/lib/explicit/type/array.rb +12 -13
  21. data/lib/explicit/type/big_decimal.rb +14 -14
  22. data/lib/explicit/type/boolean.rb +4 -5
  23. data/lib/explicit/type/date.rb +66 -0
  24. data/lib/explicit/type/date_range.rb +95 -0
  25. data/lib/explicit/type/date_time_iso8601.rb +33 -8
  26. data/lib/explicit/type/date_time_iso8601_range.rb +108 -0
  27. data/lib/explicit/type/date_time_unix_epoch.rb +69 -0
  28. data/lib/explicit/type/enum.rb +4 -5
  29. data/lib/explicit/type/file.rb +7 -7
  30. data/lib/explicit/type/hash.rb +14 -14
  31. data/lib/explicit/type/integer.rb +9 -9
  32. data/lib/explicit/type/literal.rb +4 -5
  33. data/lib/explicit/type/modifiers/default.rb +1 -1
  34. data/lib/explicit/type/modifiers/description.rb +1 -1
  35. data/lib/explicit/type/modifiers/nilable.rb +1 -1
  36. data/lib/explicit/type/one_of.rb +59 -1
  37. data/lib/explicit/type/record.rb +5 -6
  38. data/lib/explicit/type/string.rb +19 -19
  39. data/lib/explicit/type.rb +58 -23
  40. data/lib/explicit/version.rb +1 -1
  41. data/lib/explicit.rb +4 -1
  42. metadata +10 -4
  43. data/lib/explicit/type/date_time_posix.rb +0 -44
  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: 7af55a714a82013bc41a2a3b29775b1f3ffd3886a30e39441937ce05ee8f9a38
4
- data.tar.gz: baa6e0725cd885adcb764cf3627305ff5693000506e2153bd137eeffdd5c3cc3
3
+ metadata.gz: d5c59437da284708551b719ae8b40a62e98e38886b6327807edef311068b7634
4
+ data.tar.gz: 9175b15ba7d61853e56ec823a781565b413886793f32efeb8f5ae37459511ff4
5
5
  SHA512:
6
- metadata.gz: 687b57c6d6c099c9f4e0f3010b1117751b0033846109218c894a3392d968392fbd16a3574c8b6b0663b46b4849f0e1a4cf4c479ec94f9ef48c28b4e3de513eef
7
- data.tar.gz: 681fb242515af80b13e48855ee964d0795410697be6d657a3afda94bc192a6b5fd27f20cfe862eb993f3a1ca5aa344c6c2a1023507c200ef0a0fae35ed84d9c8
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
- ![Documentation example screenshot](https://raw.githubusercontent.com/luizpvas/explicit/refs/heads/main/assets/webapp_screenshot.png)
6
+ | ![Documentation example screenshot](https://raw.githubusercontent.com/luizpvas/explicit/refs/heads/main/assets/webapp_screenshot.png) |
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](#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 above the navigation menu.
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
- :bigdecimal
405
- [:bigdecimal, min: 0] # inclusive
406
- [:bigdecimal, max: 100] # inclusive
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
- :date_time_posix
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, keytype, valuetype, options = {}]
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 keytype and all values must
478
- match valuetype. If you are expecting a hash with a specific set of keys use a
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: :bigdecimal
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, minlength: 8] # inclusive
578
- [:string, maxlength: 20] # inclusive
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="record__summary__expand">
16
+ <div class="ml-auto text-neutral-300">
17
17
  <span x-show="expanded">&#9650;</span>
18
18
  <span x-show="!expanded">&#9660;</span>
19
19
  </div>
@@ -29,7 +29,7 @@
29
29
  <% end %>
30
30
 
31
31
  <% if type.has_details? %>
32
- <div class="record__subtype">
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><%= local_assigns[:page_title] || "API Documentation" %></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
- 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%;
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="page__request space-y-4">
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
@@ -1,3 +1,3 @@
1
1
  <div class="border divide-y">
2
- <%= type_attribute_render name: "Items", type: type.itemtype %>
2
+ <%= type_attribute_render name: "Items", type: type.item_type %>
3
3
  </div>
@@ -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.keytype %>
3
- <%= type_attribute_render name: "Values", type: type.valuetype %>
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.minlength %>
7
- <%= type_constraint "minlength", type.minlength %>
6
+ <% if type.min_length %>
7
+ <%= type_constraint "min_length", type.min_length %>
8
8
  <% end %>
9
9
 
10
- <% if type.maxlength %>
11
- <%= type_constraint "maxlength", type.maxlength %>
10
+ <% if type.max_length %>
11
+ <%= type_constraint "max_length", type.max_length %>
12
12
  <% end %>
13
13
 
14
14
  <% if type.format %>
@@ -2,11 +2,31 @@ en:
2
2
  explicit:
3
3
  errors:
4
4
  agreement: "must be accepted"
5
- array: "invalid item at index(%{index}): %{error}"
6
- bigdecimal: "must be a string-encoded decimal number"
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
- date_time_iso8601: "must be a valid iso8601 date time"
9
- date_time_posix: "must be a valid posix timestamp"
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
- minlength: "length must be at least %{minlength}"
26
- maxlength: "length must be at most %{maxlength}"
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
- date_time_posix: "* POSIX time or Unix epoch is the amount of seconds since 1970-01-01"
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 any type. Here are the errors:
12
+ This response doesn't match expected responses:
13
13
 
14
14
  #{error.presence || " ==> no response types found for status #{response.status}"}
15
15
 
@@ -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
- if Explicit.configuration.rescue_from_invalid_params?
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
- subrequest = self.class.new { }
28
+ this = self
29
29
 
30
- subrequest.instance_variable_set(:@base_url, @base_url)
31
- subrequest.instance_variable_set(:@base_path, @base_path)
32
- subrequest.instance_variable_set(:@routes, @routes.dup)
33
- subrequest.instance_variable_set(:@headers, @headers.dup)
34
- subrequest.instance_variable_set(:@params, @params.dup)
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
- @responses.each do |status, types|
37
- subrequest.responses[status] = types.dup
38
- end
37
+ this.responses.each do |status, types|
38
+ @responses[status] = types.dup
39
+ end
39
40
 
40
- @examples.each do |status, examples|
41
- subrequest.examples[status] = examples.dup
42
- end
41
+ this.examples.each do |status, examples|
42
+ @examples[status] = examples.dup
43
+ end
43
44
 
44
- subrequest.tap { _1.instance_eval(&block) }
45
+ instance_eval(&block)
46
+ end
45
47
  end
46
48
 
47
49
  def get(path) = @routes << Route.new(method: :get, path:)
@@ -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
- [:error, error_i18n("agreement")]
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
- description: swagger_description([
33
+ description_topics: [
34
34
  swagger_i18n("agreement")
35
- ])
36
- }
35
+ ]
36
+ })
37
37
  end
38
38
  end
39
39
  end
@@ -1,28 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Explicit::Type::Array < Explicit::Type
4
- attr_reader :itemtype, :empty
4
+ attr_reader :item_type, :empty
5
5
 
6
- def initialize(itemtype:, empty: true)
7
- @itemtype = Explicit::Type.build(itemtype)
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 [:error, :array] if !values.is_a?(::Array)
12
+ return error_i18n("array") if !values.is_a?(::Array)
13
13
 
14
14
  if values.empty? && !empty
15
- return [:error, :empty]
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 itemtype.validate(value)
21
+ case item_type.validate(value)
22
22
  in [:ok, value]
23
23
  validated << value
24
24
  in [:error, error]
25
- return [:error, error_i18n("array", index:, error:)]
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 #{itemtype.summary}"
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: itemtype.swagger_schema,
51
- minItems: empty ? 0 : 1,
52
- description: swagger_description([])
53
- }.compact_blank
50
+ items: item_type.swagger_schema,
51
+ minItems: empty ? 0 : 1
52
+ })
54
53
  end
55
54
  end
56
55
  end