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>
         
     |