openapi_first 0.6.10 → 0.7.0.alpha1
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/CHANGELOG.md +6 -0
- data/Gemfile.lock +20 -11
- data/README.md +25 -71
- data/benchmarks/Gemfile +1 -1
- data/benchmarks/Gemfile.lock +10 -5
- data/benchmarks/apps/openapi_first_resolve_only.ru +2 -2
- data/lib/openapi_first/app.rb +5 -5
- data/lib/openapi_first/coverage.rb +4 -3
- data/lib/openapi_first/definition.rb +3 -1
- data/lib/openapi_first/operation.rb +48 -0
- data/lib/openapi_first/operation_resolver.rb +10 -37
- data/lib/openapi_first/request_validation.rb +30 -21
- data/lib/openapi_first/router.rb +63 -14
- data/lib/openapi_first/utils.rb +31 -0
- data/lib/openapi_first/version.rb +1 -1
- data/lib/openapi_first.rb +2 -2
- data/openapi_first.gemspec +2 -1
- metadata +29 -15
- data/benchmarks/apps/openapi_first_request_validation_only.ru +0 -30
- data/lib/openapi_first/query_parameters.rb +0 -24
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 4885473ec6373b0fcfd9baace6175296f362099ba8ee73de0b8c1538b3420504
         | 
| 4 | 
            +
              data.tar.gz: 5a72ae579e67bcb17c33debb3adbcae1cdaae09ad98ccc0e1158a59719d00115
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: d2783c23798622b8bcc6c6f9520272d0477760d405f9879d4dc39e94b1d361b3873a87147ccef81e0ba3ec5c59e67941f645e41caf52dd221ddb915167140c6d
         | 
| 7 | 
            +
              data.tar.gz: d540387f504454feca50ea746ef87745aed68e841bc5eb734c8d2e3419676e221a8dd93404601f010e3725152cd0e0e9a2775a1646690b8f4e5b62fbd8d7ca93
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -2,6 +2,12 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            ## Unreleased
         | 
| 4 4 |  | 
| 5 | 
            +
            - Make use of hanami-router, because it's fast
         | 
| 6 | 
            +
            - Remove OpenapiFirst::Coverage
         | 
| 7 | 
            +
            - Remove option `allow_unknown_query_paramerters`
         | 
| 8 | 
            +
            - Move the namespace option to Router
         | 
| 9 | 
            +
            - Convert numeric parameters to `Integer` or `Float`
         | 
| 10 | 
            +
             | 
| 5 11 | 
             
            ## 0.6.9
         | 
| 6 12 | 
             
            - Removed radix tree, because of a bug (https://github.com/namusyaka/r2ree-ruby/issues/2)
         | 
| 7 13 |  | 
    
        data/Gemfile.lock
    CHANGED
    
    | @@ -1,10 +1,11 @@ | |
| 1 1 | 
             
            PATH
         | 
| 2 2 | 
             
              remote: .
         | 
| 3 3 | 
             
              specs:
         | 
| 4 | 
            -
                openapi_first (0. | 
| 4 | 
            +
                openapi_first (0.7.0.alpha1)
         | 
| 5 | 
            +
                  hanami-router (~> 2.0.alpha2)
         | 
| 6 | 
            +
                  hanami-utils (~> 2.0.alpha1)
         | 
| 5 7 | 
             
                  json_schemer (~> 0.2)
         | 
| 6 8 | 
             
                  multi_json (~> 1.14)
         | 
| 7 | 
            -
                  mustermann-contrib (~> 1.1.1)
         | 
| 8 9 | 
             
                  oas_parser (~> 0.24)
         | 
| 9 10 | 
             
                  rack (~> 2.2)
         | 
| 10 11 |  | 
| @@ -25,9 +26,16 @@ GEM | |
| 25 26 | 
             
                concurrent-ruby (1.1.6)
         | 
| 26 27 | 
             
                deep_merge (1.2.1)
         | 
| 27 28 | 
             
                diff-lcs (1.3)
         | 
| 28 | 
            -
                ecma-re-validator (0.2. | 
| 29 | 
            +
                ecma-re-validator (0.2.0)
         | 
| 29 30 | 
             
                  regexp_parser (~> 1.2)
         | 
| 30 31 | 
             
                hana (1.3.5)
         | 
| 32 | 
            +
                hanami-router (2.0.0.alpha2)
         | 
| 33 | 
            +
                  mustermann (~> 1.0)
         | 
| 34 | 
            +
                  mustermann-contrib (~> 1.0)
         | 
| 35 | 
            +
                  rack (~> 2.0)
         | 
| 36 | 
            +
                hanami-utils (2.0.0.alpha1)
         | 
| 37 | 
            +
                  concurrent-ruby (~> 1.0)
         | 
| 38 | 
            +
                  transproc (~> 1.0)
         | 
| 31 39 | 
             
                hansi (0.2.0)
         | 
| 32 40 | 
             
                hash-deep-merge (0.1.1)
         | 
| 33 41 | 
             
                i18n (1.8.2)
         | 
| @@ -38,7 +46,7 @@ GEM | |
| 38 46 | 
             
                  hana (~> 1.3)
         | 
| 39 47 | 
             
                  regexp_parser (~> 1.5)
         | 
| 40 48 | 
             
                  uri_template (~> 0.7)
         | 
| 41 | 
            -
                method_source (0. | 
| 49 | 
            +
                method_source (1.0.0)
         | 
| 42 50 | 
             
                mini_portile2 (2.4.0)
         | 
| 43 51 | 
             
                minitest (5.14.0)
         | 
| 44 52 | 
             
                multi_json (1.14.1)
         | 
| @@ -58,12 +66,12 @@ GEM | |
| 58 66 | 
             
                  mustermann-contrib (~> 1.1.1)
         | 
| 59 67 | 
             
                  nokogiri
         | 
| 60 68 | 
             
                parallel (1.19.1)
         | 
| 61 | 
            -
                parser (2.7.0. | 
| 69 | 
            +
                parser (2.7.0.5)
         | 
| 62 70 | 
             
                  ast (~> 2.4.0)
         | 
| 63 | 
            -
                pry (0. | 
| 64 | 
            -
                  coderay (~> 1.1 | 
| 65 | 
            -
                  method_source (~>  | 
| 66 | 
            -
                public_suffix (4.0. | 
| 71 | 
            +
                pry (0.13.0)
         | 
| 72 | 
            +
                  coderay (~> 1.1)
         | 
| 73 | 
            +
                  method_source (~> 1.0)
         | 
| 74 | 
            +
                public_suffix (4.0.3)
         | 
| 67 75 | 
             
                rack (2.2.2)
         | 
| 68 76 | 
             
                rack-test (1.1.0)
         | 
| 69 77 | 
             
                  rack (>= 1.0, < 3)
         | 
| @@ -77,7 +85,7 @@ GEM | |
| 77 85 | 
             
                  rspec-mocks (~> 3.9.0)
         | 
| 78 86 | 
             
                rspec-core (3.9.1)
         | 
| 79 87 | 
             
                  rspec-support (~> 3.9.1)
         | 
| 80 | 
            -
                rspec-expectations (3.9. | 
| 88 | 
            +
                rspec-expectations (3.9.1)
         | 
| 81 89 | 
             
                  diff-lcs (>= 1.2.0, < 2.0)
         | 
| 82 90 | 
             
                  rspec-support (~> 3.9.0)
         | 
| 83 91 | 
             
                rspec-mocks (3.9.1)
         | 
| @@ -95,7 +103,8 @@ GEM | |
| 95 103 | 
             
                ruby-progressbar (1.10.1)
         | 
| 96 104 | 
             
                ruby2_keywords (0.0.2)
         | 
| 97 105 | 
             
                thread_safe (0.3.6)
         | 
| 98 | 
            -
                 | 
| 106 | 
            +
                transproc (1.1.1)
         | 
| 107 | 
            +
                tzinfo (1.2.6)
         | 
| 99 108 | 
             
                  thread_safe (~> 0.1)
         | 
| 100 109 | 
             
                unicode-display_width (1.6.1)
         | 
| 101 110 | 
             
                uri_template (0.7.0)
         | 
    
        data/README.md
    CHANGED
    
    | @@ -36,8 +36,6 @@ Handler functions (`find_pet`) are called with two arguments: | |
| 36 36 | 
             
            - `res` - Holds a Rack::Response that you can modify if needed
         | 
| 37 37 | 
             
              If you want to access to plain Rack env you can call `params.env`.
         | 
| 38 38 |  | 
| 39 | 
            -
            You can also use the provided Rack middlewares to auto-implement only certain aspects of the request-response flow like query parameter or request body parameter validation based on your OpenAPI file. Read on to learn how.
         | 
| 40 | 
            -
             | 
| 41 39 | 
             
            ### Handling only certain paths
         | 
| 42 40 |  | 
| 43 41 | 
             
            You can filter the URIs that should be handled by pass ing `only` to `OpenapiFirst.load`:
         | 
| @@ -57,9 +55,9 @@ run OpenapiFirst.middleware('./openapi/openapi.yaml', namespace: Pets) | |
| 57 55 |  | 
| 58 56 | 
             
            When using the middleware, all requests that are not part of the API description will be passed to the next app.
         | 
| 59 57 |  | 
| 60 | 
            -
             | 
| 58 | 
            +
            ### Try it out
         | 
| 61 59 |  | 
| 62 | 
            -
            See [ | 
| 60 | 
            +
            See [examples](examples).
         | 
| 63 61 |  | 
| 64 62 |  | 
| 65 63 | 
             
            ## Installation
         | 
| @@ -72,21 +70,31 @@ gem 'openapi_first' | |
| 72 70 |  | 
| 73 71 | 
             
            OpenapiFirst uses [`multi_json`](https://rubygems.org/gems/multi_json).
         | 
| 74 72 |  | 
| 75 | 
            -
            ##  | 
| 73 | 
            +
            ## Handlers
         | 
| 76 74 |  | 
| 77 | 
            -
            OpenapiFirst  | 
| 75 | 
            +
            OpenapiFirst maps the HTTP request to a method call based on the [operationId](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operation-object) in your API description.
         | 
| 78 76 |  | 
| 79 | 
            -
             | 
| 80 | 
            -
            - Mapping request to a function call
         | 
| 77 | 
            +
            It works like this:
         | 
| 81 78 |  | 
| 82 | 
            -
             | 
| 79 | 
            +
            - "create_pet" or "createPet" or "create pet" calls `MyApi.create_pet(params, response)`
         | 
| 80 | 
            +
            - "some_things.create" calls: `MyApi::SomeThings.create(params, response)`
         | 
| 81 | 
            +
            - "pets#create" calls: `MyApi::Pets::Create.new.call(params, response)`
         | 
| 83 82 |  | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 83 | 
            +
            These handler methods are called with two arguments:
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            - `params` - Holds the parsed request body, filtered query params and path parameters
         | 
| 86 | 
            +
            - `res` - Holds a Rack::Response that you can modify if needed
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            You can call `params.env` to access the Rack env (just like in [Hanami actions](https://guides.hanamirb.org/actions/parameters/))
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            There are two ways to set the response body:
         | 
| 88 91 |  | 
| 89 | 
            -
             | 
| 92 | 
            +
            - Calling `res.write "things"` (see [Rack::Response](https://www.rubydoc.info/github/rack/rack/Rack/Response))
         | 
| 93 | 
            +
            - Returning a value from the function (see example above) (this will always converted to JSON)
         | 
| 94 | 
            +
             | 
| 95 | 
            +
            ## Request validation
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            If the request is not valid, these middlewares return a 400 status code with a body that describes the error.
         | 
| 90 98 |  | 
| 91 99 | 
             
            The error responses conform with [JSON:API](https://jsonapi.org).
         | 
| 92 100 |  | 
| @@ -107,18 +115,12 @@ content-type: "application/vnd.api+json" | |
| 107 115 | 
             
              ]
         | 
| 108 116 | 
             
            }
         | 
| 109 117 | 
             
            ```
         | 
| 110 | 
            -
            ## Request validation
         | 
| 111 | 
            -
             | 
| 112 | 
            -
            ```ruby
         | 
| 113 | 
            -
            # Add the middleware:
         | 
| 114 | 
            -
            use OpenapiFirst::RequestValidation
         | 
| 115 | 
            -
            ```
         | 
| 116 118 |  | 
| 117 | 
            -
             | 
| 119 | 
            +
            ### Parameter validation
         | 
| 118 120 |  | 
| 119 | 
            -
             | 
| 121 | 
            +
            The middleware filteres all top-level query parameters and paths parameters and tries to convert numeric values. Meaning, if you have an `:something_id` path with `type: integer`, it will try convert the value to an integer.
         | 
| 122 | 
            +
            Note that is currently does not convert date, date-time or time formats and that conversion is currently on done for path and query parameters, but not for request bodies.
         | 
| 120 123 |  | 
| 121 | 
            -
            The middleware filteres all top-level query parameters and adds these to the Rack env: `env[OpenapiFirst::QUERY_PARAMS]`.
         | 
| 122 124 | 
             
            If you want to forbid _nested_ query parameters you will need to use [`additionalProperties: false`](https://json-schema.org/understanding-json-schema/reference/object.html#properties) in your query parameter JSON schema.
         | 
| 123 125 |  | 
| 124 126 | 
             
            _OpenapiFirst always treats query parameters like [`style: deepObject`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#style-values), **but** it just works with nested objects (`filter[foo][bar]=baz`) (see [this discussion](https://github.com/OAI/OpenAPI-Specification/issues/1706))._
         | 
| @@ -132,54 +134,6 @@ This will add the parsed request body to `env[OpenapiFirst::REQUEST_BODY]`. | |
| 132 134 |  | 
| 133 135 | 
             
            tbd.
         | 
| 134 136 |  | 
| 135 | 
            -
            ## Mapping the request to a method call
         | 
| 136 | 
            -
             | 
| 137 | 
            -
            OpenapiFirst uses a `OperationResolver` middleware to map the HTTP request to a method call.
         | 
| 138 | 
            -
             | 
| 139 | 
            -
            The resolver function is found via the [`operationId`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operation-object) attribute in your API description like this:
         | 
| 140 | 
            -
             | 
| 141 | 
            -
            - `create_pet` will map to `MyApi.create_pet(params, response)`
         | 
| 142 | 
            -
            - `some_things.create` will map to `MyApi::SomeThings.create(params, response)`
         | 
| 143 | 
            -
            - `pets#create` will map to `MyApi::Pets::Create.new.call(params, response)` (like [Hanami::Router](https://github.com/hanami/router#controllers))
         | 
| 144 | 
            -
             | 
| 145 | 
            -
            These handler methods are called with two arguments:
         | 
| 146 | 
            -
             | 
| 147 | 
            -
            - `params` - Holds the parsed request body, filtered query params and path parameters
         | 
| 148 | 
            -
            - `res` - Holds a Rack::Response that you can modify if needed
         | 
| 149 | 
            -
             | 
| 150 | 
            -
            You can call `params.env` to access the Rack env (just like in [Hanami actions](https://guides.hanamirb.org/actions/parameters/))
         | 
| 151 | 
            -
             | 
| 152 | 
            -
            There are two ways to set the response body:
         | 
| 153 | 
            -
             | 
| 154 | 
            -
            - Calling `res.write "things"` (see [Rack::Response](https://www.rubydoc.info/github/rack/rack/Rack/Response))
         | 
| 155 | 
            -
            - Returning a value from the function (see example above) (this will always converted to JSON)
         | 
| 156 | 
            -
             | 
| 157 | 
            -
            ### Adding the middleware
         | 
| 158 | 
            -
             | 
| 159 | 
            -
            ```ruby
         | 
| 160 | 
            -
            # Define some methods
         | 
| 161 | 
            -
            module MyApi
         | 
| 162 | 
            -
              def self.create_pet(params, res)
         | 
| 163 | 
            -
                res.status = 201
         | 
| 164 | 
            -
                {
         | 
| 165 | 
            -
                  id: '1',
         | 
| 166 | 
            -
                  name: params['name']
         | 
| 167 | 
            -
                }
         | 
| 168 | 
            -
              end
         | 
| 169 | 
            -
            end
         | 
| 170 | 
            -
             | 
| 171 | 
            -
            # Add the middleware:
         | 
| 172 | 
            -
            use OpenapiFirst::OperationResolver, namespace: MyApi
         | 
| 173 | 
            -
            # If the operation was not found in the OAS file, the next app will be called
         | 
| 174 | 
            -
             | 
| 175 | 
            -
            # OR use it as a Rack app via `run`:
         | 
| 176 | 
            -
            run OpenapiFirst::OperationResolver, namespace: Pets
         | 
| 177 | 
            -
            # If the operation was not found, this will return 404
         | 
| 178 | 
            -
             | 
| 179 | 
            -
            # Now make a request like
         | 
| 180 | 
            -
            # POST /pets, { name: 'Oscar' }
         | 
| 181 | 
            -
            ```
         | 
| 182 | 
            -
             | 
| 183 137 | 
             
            ## Response validation
         | 
| 184 138 |  | 
| 185 139 | 
             
            Response validation is useful to make sure your app responds as described in your API description. You usually do this in your tests using [rack-test](https://github.com/rack-test/rack-test).
         | 
    
        data/benchmarks/Gemfile
    CHANGED
    
    
    
        data/benchmarks/Gemfile.lock
    CHANGED
    
    | @@ -1,10 +1,11 @@ | |
| 1 1 | 
             
            PATH
         | 
| 2 2 | 
             
              remote: ..
         | 
| 3 3 | 
             
              specs:
         | 
| 4 | 
            -
                openapi_first (0. | 
| 4 | 
            +
                openapi_first (0.7.0.alpha1)
         | 
| 5 | 
            +
                  hanami-router (~> 2.0.alpha2)
         | 
| 6 | 
            +
                  hanami-utils (~> 2.0.alpha1)
         | 
| 5 7 | 
             
                  json_schemer (~> 0.2)
         | 
| 6 8 | 
             
                  multi_json (~> 1.14)
         | 
| 7 | 
            -
                  mustermann-contrib (~> 1.1.1)
         | 
| 8 9 | 
             
                  oas_parser (~> 0.24)
         | 
| 9 10 | 
             
                  rack (~> 2.2)
         | 
| 10 11 |  | 
| @@ -44,7 +45,7 @@ GEM | |
| 44 45 | 
             
                  concurrent-ruby (~> 1.0)
         | 
| 45 46 | 
             
                  dry-core (~> 0.2)
         | 
| 46 47 | 
             
                  dry-equalizer (~> 0.2)
         | 
| 47 | 
            -
                dry-types (1. | 
| 48 | 
            +
                dry-types (1.4.0)
         | 
| 48 49 | 
             
                  concurrent-ruby (~> 1.0)
         | 
| 49 50 | 
             
                  dry-container (~> 0.3)
         | 
| 50 51 | 
             
                  dry-core (~> 0.4, >= 0.4.4)
         | 
| @@ -65,12 +66,15 @@ GEM | |
| 65 66 | 
             
                  mustermann (~> 1.0)
         | 
| 66 67 | 
             
                  mustermann-contrib (~> 1.0)
         | 
| 67 68 | 
             
                  rack (~> 2.0)
         | 
| 69 | 
            +
                hanami-utils (2.0.0.alpha1)
         | 
| 70 | 
            +
                  concurrent-ruby (~> 1.0)
         | 
| 71 | 
            +
                  transproc (~> 1.0)
         | 
| 68 72 | 
             
                hansi (0.2.0)
         | 
| 69 73 | 
             
                hash-deep-merge (0.1.1)
         | 
| 70 74 | 
             
                i18n (1.8.2)
         | 
| 71 75 | 
             
                  concurrent-ruby (~> 1.0)
         | 
| 72 76 | 
             
                json_schema (0.20.8)
         | 
| 73 | 
            -
                json_schemer (0.2. | 
| 77 | 
            +
                json_schemer (0.2.11)
         | 
| 74 78 | 
             
                  ecma-re-validator (~> 0.2)
         | 
| 75 79 | 
             
                  hana (~> 1.3)
         | 
| 76 80 | 
             
                  regexp_parser (~> 1.5)
         | 
| @@ -116,6 +120,7 @@ GEM | |
| 116 120 | 
             
                  seg
         | 
| 117 121 | 
             
                thread_safe (0.3.6)
         | 
| 118 122 | 
             
                tilt (2.0.10)
         | 
| 123 | 
            +
                transproc (1.1.1)
         | 
| 119 124 | 
             
                tzinfo (1.2.6)
         | 
| 120 125 | 
             
                  thread_safe (~> 0.1)
         | 
| 121 126 | 
             
                uri_template (0.7.0)
         | 
| @@ -129,7 +134,7 @@ DEPENDENCIES | |
| 129 134 | 
             
              benchmark-memory
         | 
| 130 135 | 
             
              committee
         | 
| 131 136 | 
             
              grape
         | 
| 132 | 
            -
              hanami-router (~> 2.0.0. | 
| 137 | 
            +
              hanami-router (~> 2.0.0.alpha2)
         | 
| 133 138 | 
             
              multi_json
         | 
| 134 139 | 
             
              openapi_first!
         | 
| 135 140 | 
             
              sinatra
         | 
| @@ -19,5 +19,5 @@ namespace = Module.new do | |
| 19 19 | 
             
            end
         | 
| 20 20 |  | 
| 21 21 | 
             
            spec = OpenapiFirst.load(File.absolute_path('./openapi.yaml', __dir__))
         | 
| 22 | 
            -
            use OpenapiFirst::Router, spec: spec
         | 
| 23 | 
            -
            run OpenapiFirst::OperationResolver.new | 
| 22 | 
            +
            use OpenapiFirst::Router, spec: spec, namespace: namespace
         | 
| 23 | 
            +
            run OpenapiFirst::OperationResolver.new
         | 
    
        data/lib/openapi_first/app.rb
    CHANGED
    
    | @@ -5,18 +5,18 @@ require 'rack' | |
| 5 5 | 
             
            module OpenapiFirst
         | 
| 6 6 | 
             
              class App
         | 
| 7 7 | 
             
                def initialize(
         | 
| 8 | 
            -
                   | 
| 8 | 
            +
                  parent_app,
         | 
| 9 9 | 
             
                  spec,
         | 
| 10 | 
            -
                  namespace | 
| 11 | 
            -
                  allow_unknown_operation: !app.nil?
         | 
| 10 | 
            +
                  namespace:
         | 
| 12 11 | 
             
                )
         | 
| 13 12 | 
             
                  @stack = Rack::Builder.app do
         | 
| 14 13 | 
             
                    freeze_app
         | 
| 15 14 | 
             
                    use OpenapiFirst::Router,
         | 
| 16 15 | 
             
                        spec: spec,
         | 
| 17 | 
            -
                         | 
| 16 | 
            +
                        namespace: namespace,
         | 
| 17 | 
            +
                        parent_app: parent_app
         | 
| 18 18 | 
             
                    use OpenapiFirst::RequestValidation
         | 
| 19 | 
            -
                    run OpenapiFirst::OperationResolver.new | 
| 19 | 
            +
                    run OpenapiFirst::OperationResolver.new
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 | 
             
                end
         | 
| 22 22 |  | 
| @@ -13,15 +13,16 @@ module OpenapiFirst | |
| 13 13 | 
             
                end
         | 
| 14 14 |  | 
| 15 15 | 
             
                def call(env)
         | 
| 16 | 
            -
                   | 
| 16 | 
            +
                  response = @app.call(env)
         | 
| 17 | 
            +
                  operation = env[OPERATION]
         | 
| 17 18 | 
             
                  @to_be_called.delete(endpoint_id(operation)) if operation
         | 
| 18 | 
            -
                   | 
| 19 | 
            +
                  response
         | 
| 19 20 | 
             
                end
         | 
| 20 21 |  | 
| 21 22 | 
             
                private
         | 
| 22 23 |  | 
| 23 24 | 
             
                def endpoint_id(operation)
         | 
| 24 | 
            -
                  "#{operation.path | 
| 25 | 
            +
                  "#{operation.path}##{operation.method}"
         | 
| 25 26 | 
             
                end
         | 
| 26 27 | 
             
              end
         | 
| 27 28 | 
             
            end
         | 
| @@ -1,5 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            require_relative 'operation'
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            module OpenapiFirst
         | 
| 4 6 | 
             
              class Definition
         | 
| 5 7 | 
             
                def initialize(parsed)
         | 
| @@ -7,7 +9,7 @@ module OpenapiFirst | |
| 7 9 | 
             
                end
         | 
| 8 10 |  | 
| 9 11 | 
             
                def operations
         | 
| 10 | 
            -
                  @spec.endpoints
         | 
| 12 | 
            +
                  @spec.endpoints.map { |e| Operation.new(e) }
         | 
| 11 13 | 
             
                end
         | 
| 12 14 |  | 
| 13 15 | 
             
                def find_operation!(request)
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'forwardable'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module OpenapiFirst
         | 
| 6 | 
            +
              class Operation
         | 
| 7 | 
            +
                extend Forwardable
         | 
| 8 | 
            +
                def_delegators :@operation,
         | 
| 9 | 
            +
                               :parameters,
         | 
| 10 | 
            +
                               :method,
         | 
| 11 | 
            +
                               :request_body,
         | 
| 12 | 
            +
                               :operation_id
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def initialize(parsed)
         | 
| 15 | 
            +
                  @operation = parsed
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def path
         | 
| 19 | 
            +
                  @operation.path.path
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def parameters_json_schema
         | 
| 23 | 
            +
                  @parameters_json_schema ||= build_parameters_json_schema
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def content_type_for(status)
         | 
| 27 | 
            +
                  content = @operation
         | 
| 28 | 
            +
                            .response_by_code(status.to_s, use_default: true)
         | 
| 29 | 
            +
                            .content
         | 
| 30 | 
            +
                  content.keys[0] if content
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                private
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def build_parameters_json_schema
         | 
| 36 | 
            +
                  return unless @operation.parameters&.any?
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  @operation.parameters.each_with_object(
         | 
| 39 | 
            +
                    'type' => 'object',
         | 
| 40 | 
            +
                    'required' => [],
         | 
| 41 | 
            +
                    'properties' => {}
         | 
| 42 | 
            +
                  ) do |parameter, schema|
         | 
| 43 | 
            +
                    schema['required'] << parameter.name if parameter.required
         | 
| 44 | 
            +
                    schema['properties'][parameter.name] = parameter.schema
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -4,55 +4,28 @@ require 'rack' | |
| 4 4 |  | 
| 5 5 | 
             
            module OpenapiFirst
         | 
| 6 6 | 
             
              class OperationResolver
         | 
| 7 | 
            -
                 | 
| 8 | 
            -
                DEFAULT_APP = ->(_env) { NOT_FOUND }
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                def initialize(app = DEFAULT_APP, options) # rubocop:disable Style/OptionalArguments
         | 
| 11 | 
            -
                  @app = app
         | 
| 12 | 
            -
                  @namespace = options.fetch(:namespace)
         | 
| 13 | 
            -
                end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                def call(env) # rubocop:disable Metrics/AbcSize
         | 
| 7 | 
            +
                def call(env)
         | 
| 16 8 | 
             
                  operation = env[OpenapiFirst::OPERATION]
         | 
| 17 | 
            -
                  return @app.call(env) unless operation
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                  operation_id = operation.operation_id
         | 
| 20 9 | 
             
                  res = Rack::Response.new
         | 
| 21 10 | 
             
                  params = build_params(env)
         | 
| 22 | 
            -
                  handler =  | 
| 11 | 
            +
                  handler = env[HANDLER]
         | 
| 23 12 | 
             
                  result = handler.call(params, res)
         | 
| 24 | 
            -
                  res.write  | 
| 25 | 
            -
                  res[Rack::CONTENT_TYPE] ||=  | 
| 13 | 
            +
                  res.write serialize(result) if result && res.body.empty?
         | 
| 14 | 
            +
                  res[Rack::CONTENT_TYPE] ||= operation.content_type_for(res.status)
         | 
| 26 15 | 
             
                  res.finish
         | 
| 27 16 | 
             
                end
         | 
| 28 17 |  | 
| 29 | 
            -
                def find_handler(operation_id)
         | 
| 30 | 
            -
                  if operation_id.include?('.')
         | 
| 31 | 
            -
                    module_name, method_name = operation_id.split('.')
         | 
| 32 | 
            -
                    return @namespace.const_get(module_name.camelize).method(method_name)
         | 
| 33 | 
            -
                  end
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                  if operation_id.include?('#')
         | 
| 36 | 
            -
                    module_name, class_name = operation_id.split('#')
         | 
| 37 | 
            -
                    return @namespace.const_get(module_name.camelize)
         | 
| 38 | 
            -
                                     .const_get(class_name.camelize).new
         | 
| 39 | 
            -
                  end
         | 
| 40 | 
            -
                  @namespace.method(operation_id)
         | 
| 41 | 
            -
                end
         | 
| 42 | 
            -
             | 
| 43 18 | 
             
                private
         | 
| 44 19 |  | 
| 45 | 
            -
                def  | 
| 46 | 
            -
                   | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
                  content.keys[0] if content
         | 
| 20 | 
            +
                def serialize(result)
         | 
| 21 | 
            +
                  return result if result.is_a?(String)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  MultiJson.dump(result)
         | 
| 50 24 | 
             
                end
         | 
| 51 25 |  | 
| 52 26 | 
             
                def build_params(env)
         | 
| 53 27 | 
             
                  sources = [
         | 
| 54 | 
            -
                    env[ | 
| 55 | 
            -
                    env[QUERY_PARAMS],
         | 
| 28 | 
            +
                    env[PARAMS],
         | 
| 56 29 | 
             
                    env[REQUEST_BODY]
         | 
| 57 30 | 
             
                  ].tap(&:compact!)
         | 
| 58 31 | 
             
                  Params.new(env).merge!(*sources)
         | 
| @@ -64,7 +37,7 @@ module OpenapiFirst | |
| 64 37 |  | 
| 65 38 | 
             
                def initialize(env)
         | 
| 66 39 | 
             
                  @env = env
         | 
| 67 | 
            -
                  super | 
| 40 | 
            +
                  super
         | 
| 68 41 | 
             
                end
         | 
| 69 42 | 
             
              end
         | 
| 70 43 | 
             
            end
         | 
| @@ -3,25 +3,21 @@ | |
| 3 3 | 
             
            require 'rack'
         | 
| 4 4 | 
             
            require 'json_schemer'
         | 
| 5 5 | 
             
            require 'multi_json'
         | 
| 6 | 
            -
            require_relative 'query_parameters'
         | 
| 7 6 | 
             
            require_relative 'validation_format'
         | 
| 8 7 |  | 
| 9 8 | 
             
            module OpenapiFirst
         | 
| 10 | 
            -
              class RequestValidation
         | 
| 11 | 
            -
                def initialize(app,  | 
| 9 | 
            +
              class RequestValidation # rubocop:disable Metrics/ClassLength
         | 
| 10 | 
            +
                def initialize(app, _options = {})
         | 
| 12 11 | 
             
                  @app = app
         | 
| 13 | 
            -
                  @allow_unknown_query_parameters = options.fetch(
         | 
| 14 | 
            -
                    :allow_unknown_query_parameters, false
         | 
| 15 | 
            -
                  )
         | 
| 16 12 | 
             
                end
         | 
| 17 13 |  | 
| 18 14 | 
             
                def call(env) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
         | 
| 19 15 | 
             
                  operation = env[OpenapiFirst::OPERATION]
         | 
| 20 16 | 
             
                  return @app.call(env) unless operation
         | 
| 21 17 |  | 
| 22 | 
            -
                  req = Rack::Request.new(env)
         | 
| 23 18 | 
             
                  catch(:halt) do
         | 
| 24 | 
            -
                    validate_query_parameters!(env, operation,  | 
| 19 | 
            +
                    validate_query_parameters!(env, operation, env[PARAMS])
         | 
| 20 | 
            +
                    req = Rack::Request.new(env)
         | 
| 25 21 | 
             
                    content_type = req.content_type
         | 
| 26 22 | 
             
                    return @app.call(env) unless operation.request_body
         | 
| 27 23 |  | 
| @@ -33,6 +29,8 @@ module OpenapiFirst | |
| 33 29 | 
             
                  end
         | 
| 34 30 | 
             
                end
         | 
| 35 31 |  | 
| 32 | 
            +
                private
         | 
| 33 | 
            +
             | 
| 36 34 | 
             
                def halt(response)
         | 
| 37 35 | 
             
                  throw :halt, response
         | 
| 38 36 | 
             
                end
         | 
| @@ -83,10 +81,10 @@ module OpenapiFirst | |
| 83 81 | 
             
                  ).finish
         | 
| 84 82 | 
             
                end
         | 
| 85 83 |  | 
| 86 | 
            -
                def request_body_schema(content_type,  | 
| 87 | 
            -
                  return unless  | 
| 84 | 
            +
                def request_body_schema(content_type, operation)
         | 
| 85 | 
            +
                  return unless operation
         | 
| 88 86 |  | 
| 89 | 
            -
                   | 
| 87 | 
            +
                  operation.request_body.content[content_type]&.fetch('schema')
         | 
| 90 88 | 
             
                end
         | 
| 91 89 |  | 
| 92 90 | 
             
                def serialize_request_body_errors(validation_errors)
         | 
| @@ -100,27 +98,25 @@ module OpenapiFirst | |
| 100 98 | 
             
                end
         | 
| 101 99 |  | 
| 102 100 | 
             
                def validate_query_parameters!(env, operation, params)
         | 
| 103 | 
            -
                  json_schema =  | 
| 104 | 
            -
                    operation: operation,
         | 
| 105 | 
            -
                    allow_unknown_parameters: @allow_unknown_query_parameters
         | 
| 106 | 
            -
                  ).to_json_schema
         | 
| 107 | 
            -
             | 
| 101 | 
            +
                  json_schema = operation.parameters_json_schema
         | 
| 108 102 | 
             
                  return unless json_schema
         | 
| 109 103 |  | 
| 104 | 
            +
                  params = filtered_params(json_schema, params)
         | 
| 110 105 | 
             
                  errors = JSONSchemer.schema(json_schema).validate(params)
         | 
| 111 106 | 
             
                  if errors.any?
         | 
| 112 107 | 
             
                    halt error_response(400, serialize_query_parameter_errors(errors))
         | 
| 113 108 | 
             
                  end
         | 
| 114 | 
            -
                  env[ | 
| 109 | 
            +
                  env[PARAMS] = params
         | 
| 115 110 | 
             
                end
         | 
| 116 111 |  | 
| 117 | 
            -
                def  | 
| 112 | 
            +
                def filtered_params(json_schema, params)
         | 
| 118 113 | 
             
                  json_schema['properties']
         | 
| 119 | 
            -
                    . | 
| 120 | 
            -
             | 
| 114 | 
            +
                    .each_with_object({}) do |key_value, result|
         | 
| 115 | 
            +
                      parameter_name, schema = key_value
         | 
| 121 116 | 
             
                      next unless params.key?(parameter_name)
         | 
| 122 117 |  | 
| 123 | 
            -
                       | 
| 118 | 
            +
                      value = params[parameter_name]
         | 
| 119 | 
            +
                      result[parameter_name] = parse_parameter(value, schema)
         | 
| 124 120 | 
             
                    end
         | 
| 125 121 | 
             
                end
         | 
| 126 122 |  | 
| @@ -131,5 +127,18 @@ module OpenapiFirst | |
| 131 127 | 
             
                    }.update(ValidationFormat.error_details(error))
         | 
| 132 128 | 
             
                  end
         | 
| 133 129 | 
             
                end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                def parse_parameter(value, schema)
         | 
| 132 | 
            +
                  return filtered_params(schema, value) if schema['properties']
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                  begin
         | 
| 135 | 
            +
                    return Integer(value, 10) if schema['type'] == 'integer'
         | 
| 136 | 
            +
                    return Float(value) if schema['type'] == 'number'
         | 
| 137 | 
            +
                  rescue ArgumentError
         | 
| 138 | 
            +
                    value
         | 
| 139 | 
            +
                  end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                  value
         | 
| 142 | 
            +
                end
         | 
| 134 143 | 
             
              end
         | 
| 135 144 | 
             
            end
         | 
    
        data/lib/openapi_first/router.rb
    CHANGED
    
    | @@ -1,9 +1,8 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'rack'
         | 
| 4 | 
            -
            require ' | 
| 5 | 
            -
             | 
| 6 | 
            -
            require 'mustermann/template'
         | 
| 4 | 
            +
            require 'hanami/router'
         | 
| 5 | 
            +
            require_relative 'utils'
         | 
| 7 6 |  | 
| 8 7 | 
             
            module OpenapiFirst
         | 
| 9 8 | 
             
              class Router
         | 
| @@ -11,25 +10,75 @@ module OpenapiFirst | |
| 11 10 |  | 
| 12 11 | 
             
                def initialize(app, options)
         | 
| 13 12 | 
             
                  @app = app
         | 
| 14 | 
            -
                  @ | 
| 15 | 
            -
                  @ | 
| 13 | 
            +
                  @namespace = options.fetch(:namespace)
         | 
| 14 | 
            +
                  @parent_app = options.fetch(:parent_app, nil)
         | 
| 15 | 
            +
                  @router = build_router(options.fetch(:spec).operations)
         | 
| 16 16 | 
             
                end
         | 
| 17 17 |  | 
| 18 18 | 
             
                def call(env)
         | 
| 19 | 
            -
                   | 
| 20 | 
            -
                   | 
| 21 | 
            -
             | 
| 22 | 
            -
                  env | 
| 23 | 
            -
                  return @app.call(env) if operation || @allow_unknown_operation
         | 
| 19 | 
            +
                  route = @router.recognize(env)
         | 
| 20 | 
            +
                  return route.endpoint.call(env) if route.routable?
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  return @parent_app.call(env) if @parent_app
         | 
| 24 23 |  | 
| 25 24 | 
             
                  NOT_FOUND
         | 
| 26 25 | 
             
                end
         | 
| 27 26 |  | 
| 28 | 
            -
                def  | 
| 29 | 
            -
                   | 
| 27 | 
            +
                def find_handler(operation_id) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
         | 
| 28 | 
            +
                  name = operation_id.match(/:*(.*)/)&.to_a&.at(1)
         | 
| 29 | 
            +
                  return if name.nil?
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  if name.include?('.')
         | 
| 32 | 
            +
                    module_name, method_name = name.split('.')
         | 
| 33 | 
            +
                    klass = find_const(@namespace, module_name)
         | 
| 34 | 
            +
                    return klass&.method(Utils.underscore(method_name))
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                  if name.include?('#')
         | 
| 37 | 
            +
                    module_name, klass_name = name.split('#')
         | 
| 38 | 
            +
                    const = find_const(@namespace, module_name)
         | 
| 39 | 
            +
                    klass = find_const(const, klass_name)
         | 
| 40 | 
            +
                    return ->(params, res) { klass.new.call(params, res) }
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                  method_name = Utils.underscore(name)
         | 
| 43 | 
            +
                  return unless @namespace.respond_to?(method_name)
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  @namespace.method(method_name)
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                private
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def find_const(parent, name)
         | 
| 51 | 
            +
                  name = Utils.classify(name)
         | 
| 52 | 
            +
                  return unless parent.const_defined?(name, false)
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  parent.const_get(name, false)
         | 
| 55 | 
            +
                end
         | 
| 30 56 |  | 
| 31 | 
            -
             | 
| 32 | 
            -
                   | 
| 57 | 
            +
                def build_router(operations) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
         | 
| 58 | 
            +
                  router = Hanami::Router.new {}
         | 
| 59 | 
            +
                  operations.each do |operation|
         | 
| 60 | 
            +
                    normalized_path = operation.path.gsub('{', ':').gsub('}', '')
         | 
| 61 | 
            +
                    if operation.operation_id.nil?
         | 
| 62 | 
            +
                      warn "operationId is missing in '#{operation.method} #{operation.path}'. I am ignoring this operation." # rubocop:disable Layout/LineLength
         | 
| 63 | 
            +
                      next
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
                    handler = find_handler(operation.operation_id)
         | 
| 66 | 
            +
                    if handler.nil?
         | 
| 67 | 
            +
                      warn "could not find handler for '#{operation.operation_id}' (#{operation.method} #{operation.path}). I am ignoring this operation." # rubocop:disable Layout/LineLength
         | 
| 68 | 
            +
                      next
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
                    router.public_send(
         | 
| 71 | 
            +
                      operation.method,
         | 
| 72 | 
            +
                      normalized_path,
         | 
| 73 | 
            +
                      to: lambda do |env|
         | 
| 74 | 
            +
                        env[OPERATION] = operation
         | 
| 75 | 
            +
                        env[PARAMS] = Utils.deep_stringify(env['router.params'])
         | 
| 76 | 
            +
                        env[HANDLER] = handler
         | 
| 77 | 
            +
                        @app.call(env)
         | 
| 78 | 
            +
                      end
         | 
| 79 | 
            +
                    )
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                  router
         | 
| 33 82 | 
             
                end
         | 
| 34 83 | 
             
              end
         | 
| 35 84 | 
             
            end
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'hanami/utils/string'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module OpenapiFirst
         | 
| 6 | 
            +
              module Utils
         | 
| 7 | 
            +
                def self.underscore(string)
         | 
| 8 | 
            +
                  Hanami::Utils::String.underscore(string)
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def self.classify(string)
         | 
| 12 | 
            +
                  Hanami::Utils::String.classify(string)
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def self.deep_stringify(params) # rubocop:disable Metrics/MethodLength
         | 
| 16 | 
            +
                  params.each_with_object({}) do |(key, value), output|
         | 
| 17 | 
            +
                    output[key.to_s] =
         | 
| 18 | 
            +
                      case value
         | 
| 19 | 
            +
                      when ::Hash
         | 
| 20 | 
            +
                        deep_stringify(value)
         | 
| 21 | 
            +
                      when Array
         | 
| 22 | 
            +
                        value.map do |item|
         | 
| 23 | 
            +
                          item.is_a?(::Hash) ? deep_stringify(item) : item
         | 
| 24 | 
            +
                        end
         | 
| 25 | 
            +
                      else
         | 
| 26 | 
            +
                        value
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
    
        data/lib/openapi_first.rb
    CHANGED
    
    | @@ -12,9 +12,9 @@ require 'openapi_first/app' | |
| 12 12 |  | 
| 13 13 | 
             
            module OpenapiFirst
         | 
| 14 14 | 
             
              OPERATION = 'openapi_first.operation'
         | 
| 15 | 
            -
               | 
| 15 | 
            +
              PARAMS = 'openapi_first.params'
         | 
| 16 16 | 
             
              REQUEST_BODY = 'openapi_first.parsed_request_body'
         | 
| 17 | 
            -
               | 
| 17 | 
            +
              HANDLER = 'openapi_first.handler'
         | 
| 18 18 |  | 
| 19 19 | 
             
              def self.load(spec_path, only: nil)
         | 
| 20 20 | 
             
                content = YAML.load_file(spec_path)
         | 
    
        data/openapi_first.gemspec
    CHANGED
    
    | @@ -32,9 +32,10 @@ Gem::Specification.new do |spec| | |
| 32 32 | 
             
              spec.bindir        = 'exe'
         | 
| 33 33 | 
             
              spec.require_paths = ['lib']
         | 
| 34 34 |  | 
| 35 | 
            +
              spec.add_dependency 'hanami-router', '~> 2.0.alpha2'
         | 
| 36 | 
            +
              spec.add_dependency 'hanami-utils', '~> 2.0.alpha1'
         | 
| 35 37 | 
             
              spec.add_dependency 'json_schemer', '~> 0.2'
         | 
| 36 38 | 
             
              spec.add_dependency 'multi_json', '~> 1.14'
         | 
| 37 | 
            -
              spec.add_dependency 'mustermann-contrib', '~> 1.1.1'
         | 
| 38 39 | 
             
              spec.add_dependency 'oas_parser', '~> 0.24'
         | 
| 39 40 | 
             
              spec.add_dependency 'rack', '~> 2.2'
         | 
| 40 41 |  | 
    
        metadata
    CHANGED
    
    | @@ -1,57 +1,71 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: openapi_first
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.7.0.alpha1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Andreas Haller
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2020- | 
| 11 | 
            +
            date: 2020-03-23 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            -
              name:  | 
| 14 | 
            +
              name: hanami-router
         | 
| 15 15 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 16 | 
             
                requirements:
         | 
| 17 17 | 
             
                - - "~>"
         | 
| 18 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            -
                    version:  | 
| 19 | 
            +
                    version: 2.0.alpha2
         | 
| 20 20 | 
             
              type: :runtime
         | 
| 21 21 | 
             
              prerelease: false
         | 
| 22 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 23 | 
             
                requirements:
         | 
| 24 24 | 
             
                - - "~>"
         | 
| 25 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            -
                    version:  | 
| 26 | 
            +
                    version: 2.0.alpha2
         | 
| 27 27 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            -
              name:  | 
| 28 | 
            +
              name: hanami-utils
         | 
| 29 29 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 30 | 
             
                requirements:
         | 
| 31 31 | 
             
                - - "~>"
         | 
| 32 32 | 
             
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            -
                    version:  | 
| 33 | 
            +
                    version: 2.0.alpha1
         | 
| 34 34 | 
             
              type: :runtime
         | 
| 35 35 | 
             
              prerelease: false
         | 
| 36 36 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 37 | 
             
                requirements:
         | 
| 38 38 | 
             
                - - "~>"
         | 
| 39 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            -
                    version:  | 
| 40 | 
            +
                    version: 2.0.alpha1
         | 
| 41 41 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            -
              name:  | 
| 42 | 
            +
              name: json_schemer
         | 
| 43 43 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 44 | 
             
                requirements:
         | 
| 45 45 | 
             
                - - "~>"
         | 
| 46 46 | 
             
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            -
                    version:  | 
| 47 | 
            +
                    version: '0.2'
         | 
| 48 48 | 
             
              type: :runtime
         | 
| 49 49 | 
             
              prerelease: false
         | 
| 50 50 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 51 | 
             
                requirements:
         | 
| 52 52 | 
             
                - - "~>"
         | 
| 53 53 | 
             
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            -
                    version:  | 
| 54 | 
            +
                    version: '0.2'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: multi_json
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - "~>"
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '1.14'
         | 
| 62 | 
            +
              type: :runtime
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - "~>"
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '1.14'
         | 
| 55 69 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 56 70 | 
             
              name: oas_parser
         | 
| 57 71 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -160,7 +174,6 @@ files: | |
| 160 174 | 
             
            - benchmarks/apps/hanami_router.ru
         | 
| 161 175 | 
             
            - benchmarks/apps/openapi.yaml
         | 
| 162 176 | 
             
            - benchmarks/apps/openapi_first.ru
         | 
| 163 | 
            -
            - benchmarks/apps/openapi_first_request_validation_only.ru
         | 
| 164 177 | 
             
            - benchmarks/apps/openapi_first_resolve_only.ru
         | 
| 165 178 | 
             
            - benchmarks/apps/sinatra.ru
         | 
| 166 179 | 
             
            - benchmarks/apps/syro.ru
         | 
| @@ -175,11 +188,12 @@ files: | |
| 175 188 | 
             
            - lib/openapi_first/app.rb
         | 
| 176 189 | 
             
            - lib/openapi_first/coverage.rb
         | 
| 177 190 | 
             
            - lib/openapi_first/definition.rb
         | 
| 191 | 
            +
            - lib/openapi_first/operation.rb
         | 
| 178 192 | 
             
            - lib/openapi_first/operation_resolver.rb
         | 
| 179 | 
            -
            - lib/openapi_first/query_parameters.rb
         | 
| 180 193 | 
             
            - lib/openapi_first/request_validation.rb
         | 
| 181 194 | 
             
            - lib/openapi_first/response_validator.rb
         | 
| 182 195 | 
             
            - lib/openapi_first/router.rb
         | 
| 196 | 
            +
            - lib/openapi_first/utils.rb
         | 
| 183 197 | 
             
            - lib/openapi_first/validation.rb
         | 
| 184 198 | 
             
            - lib/openapi_first/validation_format.rb
         | 
| 185 199 | 
             
            - lib/openapi_first/version.rb
         | 
| @@ -202,9 +216,9 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 202 216 | 
             
                  version: '0'
         | 
| 203 217 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 204 218 | 
             
              requirements:
         | 
| 205 | 
            -
              - - " | 
| 219 | 
            +
              - - ">"
         | 
| 206 220 | 
             
                - !ruby/object:Gem::Version
         | 
| 207 | 
            -
                  version:  | 
| 221 | 
            +
                  version: 1.3.1
         | 
| 208 222 | 
             
            requirements: []
         | 
| 209 223 | 
             
            rubygems_version: 3.1.2
         | 
| 210 224 | 
             
            signing_key: 
         | 
| @@ -1,30 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            require 'openapi_first'
         | 
| 4 | 
            -
            require 'syro'
         | 
| 5 | 
            -
            require 'multi_json'
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            app = Syro.new do
         | 
| 8 | 
            -
              on 'hello' do
         | 
| 9 | 
            -
                on :id do
         | 
| 10 | 
            -
                  get do
         | 
| 11 | 
            -
                    res.json MultiJson.dump(hello: 'world', id: inbox[:id])
         | 
| 12 | 
            -
                  end
         | 
| 13 | 
            -
                end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                get do
         | 
| 16 | 
            -
                  res.json [MultiJson.dump(hello: 'world')]
         | 
| 17 | 
            -
                end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                post do
         | 
| 20 | 
            -
                  res.status = 201
         | 
| 21 | 
            -
                  res.json MultiJson.dump(hello: 'world')
         | 
| 22 | 
            -
                end
         | 
| 23 | 
            -
              end
         | 
| 24 | 
            -
            end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
            spec = OpenapiFirst.load(File.absolute_path('./openapi.yaml', __dir__))
         | 
| 27 | 
            -
            use OpenapiFirst::Router, spec: spec
         | 
| 28 | 
            -
            use OpenapiFirst::RequestValidation
         | 
| 29 | 
            -
             | 
| 30 | 
            -
            run app
         | 
| @@ -1,24 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            module OpenapiFirst
         | 
| 4 | 
            -
              class QueryParameters
         | 
| 5 | 
            -
                def initialize(operation:, allow_unknown_parameters: false)
         | 
| 6 | 
            -
                  @operation = operation
         | 
| 7 | 
            -
                  @allow_unknown_parameters = allow_unknown_parameters
         | 
| 8 | 
            -
                end
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                def to_json_schema
         | 
| 11 | 
            -
                  return unless @operation&.query_parameters&.any?
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                  @operation.query_parameters.each_with_object(
         | 
| 14 | 
            -
                    'type' => 'object',
         | 
| 15 | 
            -
                    'required' => [],
         | 
| 16 | 
            -
                    'additionalProperties' => @allow_unknown_parameters,
         | 
| 17 | 
            -
                    'properties' => {}
         | 
| 18 | 
            -
                  ) do |parameter, schema|
         | 
| 19 | 
            -
                    schema['required'] << parameter.name if parameter.required
         | 
| 20 | 
            -
                    schema['properties'][parameter.name] = parameter.schema
         | 
| 21 | 
            -
                  end
         | 
| 22 | 
            -
                end
         | 
| 23 | 
            -
              end
         | 
| 24 | 
            -
            end
         |