grape 1.3.1 → 1.3.3
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 +34 -0
- data/LICENSE +1 -1
- data/README.md +19 -6
- data/UPGRADING.md +120 -16
- data/lib/grape/api/instance.rb +12 -7
- data/lib/grape/dsl/inside_route.rb +37 -14
- data/lib/grape/http/headers.rb +1 -0
- data/lib/grape/middleware/versioner/header.rb +1 -1
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/path.rb +2 -2
- data/lib/grape/router/attribute_translator.rb +23 -2
- data/lib/grape/router/route.rb +3 -22
- data/lib/grape/router.rb +6 -14
- data/lib/grape/util/base_inheritable.rb +9 -6
- data/lib/grape/util/reverse_stackable_values.rb +3 -1
- data/lib/grape/util/stackable_values.rb +3 -1
- data/lib/grape/validations/types/array_coercer.rb +14 -5
- data/lib/grape/validations/types/build_coercer.rb +5 -8
- data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
- data/lib/grape/validations/types/file.rb +15 -13
- data/lib/grape/validations/types/json.rb +40 -36
- data/lib/grape/validations/types/primitive_coercer.rb +11 -4
- data/lib/grape/validations/types/set_coercer.rb +6 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
- data/lib/grape/validations/types.rb +6 -5
- data/lib/grape/validations/validators/coerce.rb +3 -10
- data/lib/grape/validations/validators/default.rb +0 -1
- data/lib/grape/validations/validators/regexp.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/instance_spec.rb +50 -0
- data/spec/grape/endpoint_spec.rb +18 -5
- data/spec/grape/path_spec.rb +4 -4
- data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
- data/spec/grape/validations/types/primitive_coercer_spec.rb +5 -1
- data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
- data/spec/grape/validations/types_spec.rb +1 -1
- data/spec/grape/validations/validators/coerce_spec.rb +207 -29
- data/spec/grape/validations/validators/default_spec.rb +121 -0
- data/spec/grape/validations/validators/values_spec.rb +1 -1
- data/spec/grape/validations_spec.rb +5 -5
- metadata +9 -5
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d63fb79e412ead32064ad4994e171e65962131a04afb20d12957942c744f4df8
         | 
| 4 | 
            +
              data.tar.gz: 9a9f4fb654e346eabb8a0b902d5e49ae54dc567797789c98c76f2a2fe2e2daa9
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: b75afa355e6a2b8200d72a2c4407bc78646648832a94d576bff06bdebde90d54b1897df09a560665bd9f58e735f9832a110b63bbcaabe14a4e2ee3c97e743b57
         | 
| 7 | 
            +
              data.tar.gz: 18a4ea057230ae0e6cfe16f92e5043339b66026a94d29bd8c897d2482577cb279ec4e202f41a643605a3ea09c748fa159fddc36977a3c4828b5b64daef080272
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,37 @@ | |
| 1 | 
            +
            ### 1.3.3 (2020/05/23)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            #### Features
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            * [#2048](https://github.com/ruby-grape/grape/issues/2034): Grape Enterprise support is now available [via TideLift](https://tidelift.com/subscription/request-a-demo?utm_source=rubygems-grape&utm_medium=referral&utm_campaign=enterprise) - [@dblock](https://github.com/dblock).
         | 
| 6 | 
            +
            * [#2039](https://github.com/ruby-grape/grape/pull/2039): Travis - update rails versions - [@ericproulx](https://github.com/ericproulx).
         | 
| 7 | 
            +
            * [#2038](https://github.com/ruby-grape/grape/pull/2038): Travis - update ruby versions - [@ericproulx](https://github.com/ericproulx).
         | 
| 8 | 
            +
            * [#2050](https://github.com/ruby-grape/grape/pull/2050): Refactor route public_send to AttributeTranslator - [@ericproulx](https://github.com/ericproulx).
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            #### Fixes
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            * [#2049](https://github.com/ruby-grape/grape/pull/2049): Coerce an empty string to nil in case of the bool type - [@dnesteryuk](https://github.com/dnesteryuk).
         | 
| 13 | 
            +
            * [#2043](https://github.com/ruby-grape/grape/pull/2043): Modify declared for nested array and hash - [@kadotami](https://github.com/kadotami).
         | 
| 14 | 
            +
            * [#2040](https://github.com/ruby-grape/grape/pull/2040): Fix a regression with Array of type nil - [@ericproulx](https://github.com/ericproulx).
         | 
| 15 | 
            +
            * [#2054](https://github.com/ruby-grape/grape/pull/2054): Coercing of nested arrays - [@dnesteryuk](https://github.com/dnesteryuk).
         | 
| 16 | 
            +
            * [#2050](https://github.com/ruby-grape/grape/pull/2053): Fix broken multiple mounts - [@Jack12816](https://github.com/Jack12816).
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            ### 1.3.2 (2020/04/12)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            #### Features
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            * [#2020](https://github.com/ruby-grape/grape/pull/2020): Reduce array allocation - [@ericproulx](https://github.com/ericproulx).
         | 
| 23 | 
            +
            * [#2015](https://github.com/ruby-grape/grape/pull/2014): Reduce MatchData allocation - [@ericproulx](https://github.com/ericproulx).
         | 
| 24 | 
            +
            * [#2014](https://github.com/ruby-grape/grape/pull/2014): Reduce total allocated arrays - [@ericproulx](https://github.com/ericproulx).
         | 
| 25 | 
            +
            * [#2011](https://github.com/ruby-grape/grape/pull/2011): Reduce total retained regexes - [@ericproulx](https://github.com/ericproulx).
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            #### Fixes
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            * [#2033](https://github.com/ruby-grape/grape/pull/2033): Ensure `Float` params are correctly coerced to `BigDecimal` - [@tlconnor](https://github.com/tlconnor).
         | 
| 30 | 
            +
            * [#2031](https://github.com/ruby-grape/grape/pull/2031): Fix a regression with an array of a custom type - [@dnesteryuk](https://github.com/dnesteryuk).
         | 
| 31 | 
            +
            * [#2026](https://github.com/ruby-grape/grape/pull/2026): Fix a regression in `coerce_with` when coercion returns `nil` - [@misdoro](https://github.com/misdoro).
         | 
| 32 | 
            +
            * [#2025](https://github.com/ruby-grape/grape/pull/2025): Fix Decimal type category - [@kdoya](https://github.com/kdoya).
         | 
| 33 | 
            +
            * [#2019](https://github.com/ruby-grape/grape/pull/2019): Avoid coercing parameter with multiple types to an empty Array - [@stanhu](https://github.com/stanhu).
         | 
| 34 | 
            +
             | 
| 1 35 | 
             
            ### 1.3.1 (2020/03/11)
         | 
| 2 36 |  | 
| 3 37 | 
             
            #### Features
         | 
    
        data/LICENSE
    CHANGED
    
    | @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            Copyright (c) 2010- | 
| 1 | 
            +
            Copyright (c) 2010-2020 Michael Bleigh, Intridea Inc. and Contributors.
         | 
| 2 2 |  | 
| 3 3 | 
             
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 4 4 | 
             
            a copy of this software and associated documentation files (the
         | 
    
        data/README.md
    CHANGED
    
    | @@ -12,6 +12,7 @@ | |
| 12 12 | 
             
            - [What is Grape?](#what-is-grape)
         | 
| 13 13 | 
             
            - [Stable Release](#stable-release)
         | 
| 14 14 | 
             
            - [Project Resources](#project-resources)
         | 
| 15 | 
            +
            - [Grape for Enterprise](#grape-for-enterprise)
         | 
| 15 16 | 
             
            - [Installation](#installation)
         | 
| 16 17 | 
             
            - [Basic Usage](#basic-usage)
         | 
| 17 18 | 
             
            - [Mounting](#mounting)
         | 
| @@ -141,6 +142,7 @@ | |
| 141 142 | 
             
                - [format_response.grape](#format_responsegrape)
         | 
| 142 143 | 
             
              - [Monitoring Products](#monitoring-products)
         | 
| 143 144 | 
             
            - [Contributing to Grape](#contributing-to-grape)
         | 
| 145 | 
            +
            - [Security](#security)
         | 
| 144 146 | 
             
            - [License](#license)
         | 
| 145 147 | 
             
            - [Copyright](#copyright)
         | 
| 146 148 |  | 
| @@ -154,7 +156,7 @@ content negotiation, versioning and much more. | |
| 154 156 |  | 
| 155 157 | 
             
            ## Stable Release
         | 
| 156 158 |  | 
| 157 | 
            -
            You're reading the documentation for the stable release of Grape, **1.3. | 
| 159 | 
            +
            You're reading the documentation for the stable release of Grape, **1.3.3**.
         | 
| 158 160 |  | 
| 159 161 | 
             
            ## Project Resources
         | 
| 160 162 |  | 
| @@ -163,6 +165,14 @@ You're reading the documentation for the stable release of Grape, **1.3.1**. | |
| 163 165 | 
             
            * Need help? Try [Grape Google Group](http://groups.google.com/group/ruby-grape) or [Gitter](https://gitter.im/ruby-grape/grape)
         | 
| 164 166 | 
             
            * [Follow us on Twitter](https://twitter.com/grapeframework)
         | 
| 165 167 |  | 
| 168 | 
            +
            ## Grape for Enterprise
         | 
| 169 | 
            +
             | 
| 170 | 
            +
            Available as part of the Tidelift Subscription.
         | 
| 171 | 
            +
             | 
| 172 | 
            +
            The maintainers of Grape are working with Tidelift to deliver commercial support and maintenance. Save time, reduce risk, and improve code health, while paying the maintainers of Grape. Click [here](https://tidelift.com/subscription/request-a-demo?utm_source=rubygems-grape&utm_medium=referral&utm_campaign=enterprise) for more details.
         | 
| 173 | 
            +
             | 
| 174 | 
            +
            In 2020, we plan to use the money towards gathering Grape contributors for dinner in New York City.
         | 
| 175 | 
            +
             | 
| 166 176 | 
             
            ## Installation
         | 
| 167 177 |  | 
| 168 178 | 
             
            Ruby 2.4 or newer is required.
         | 
| @@ -3187,14 +3197,13 @@ applies to the current namespace and any children, but not parents. | |
| 3187 3197 | 
             
            ```ruby
         | 
| 3188 3198 | 
             
            http_basic do |username, password|
         | 
| 3189 3199 | 
             
              # verify user's password here
         | 
| 3190 | 
            -
               | 
| 3200 | 
            +
              # IMPORTANT: make sure you use a comparison method which isn't prone to a timing attack
         | 
| 3191 3201 | 
             
            end
         | 
| 3192 3202 | 
             
            ```
         | 
| 3193 3203 |  | 
| 3194 3204 | 
             
            ```ruby
         | 
| 3195 3205 | 
             
            http_digest({ realm: 'Test Api', opaque: 'app secret' }) do |username|
         | 
| 3196 3206 | 
             
              # lookup the user's password here
         | 
| 3197 | 
            -
              { 'user1' => 'password1' }[username]
         | 
| 3198 3207 | 
             
            end
         | 
| 3199 3208 | 
             
            ```
         | 
| 3200 3209 |  | 
| @@ -3851,7 +3860,7 @@ Grape integrates with following third-party tools: | |
| 3851 3860 | 
             
            * **Librato Metrics** - [grape-librato](https://github.com/seanmoon/grape-librato) gem
         | 
| 3852 3861 | 
             
            * **[Skylight](https://www.skylight.io/)** - [skylight](https://github.com/skylightio/skylight-ruby) gem, [documentation](https://docs.skylight.io/grape/)
         | 
| 3853 3862 | 
             
            * **[AppSignal](https://www.appsignal.com)** - [appsignal-ruby](https://github.com/appsignal/appsignal-ruby) gem, [documentation](http://docs.appsignal.com/getting-started/supported-frameworks.html#grape)
         | 
| 3854 | 
            -
            * **[ElasticAPM](https://www.elastic.co/products/apm) - [elastic-apm](https://github.com/elastic/apm-agent-ruby) gem, [documentation](https://www.elastic.co/guide/en/apm/agent/ruby/3.x/getting-started-rack.html#getting-started-grape)
         | 
| 3863 | 
            +
            * **[ElasticAPM](https://www.elastic.co/products/apm)** - [elastic-apm](https://github.com/elastic/apm-agent-ruby) gem, [documentation](https://www.elastic.co/guide/en/apm/agent/ruby/3.x/getting-started-rack.html#getting-started-grape)
         | 
| 3855 3864 |  | 
| 3856 3865 | 
             
            ## Contributing to Grape
         | 
| 3857 3866 |  | 
| @@ -3860,10 +3869,14 @@ features and discuss issues. | |
| 3860 3869 |  | 
| 3861 3870 | 
             
            See [CONTRIBUTING](CONTRIBUTING.md).
         | 
| 3862 3871 |  | 
| 3872 | 
            +
            ## Security
         | 
| 3873 | 
            +
             | 
| 3874 | 
            +
            See [SECURITY](SECURITY.md) for details.
         | 
| 3875 | 
            +
             | 
| 3863 3876 | 
             
            ## License
         | 
| 3864 3877 |  | 
| 3865 | 
            -
            MIT License. See LICENSE for details.
         | 
| 3878 | 
            +
            MIT License. See [LICENSE](LICENSE) for details.
         | 
| 3866 3879 |  | 
| 3867 3880 | 
             
            ## Copyright
         | 
| 3868 3881 |  | 
| 3869 | 
            -
            Copyright (c) 2010- | 
| 3882 | 
            +
            Copyright (c) 2010-2020 Michael Bleigh, Intridea Inc. and Contributors.
         | 
    
        data/UPGRADING.md
    CHANGED
    
    | @@ -1,6 +1,65 @@ | |
| 1 1 | 
             
            Upgrading Grape
         | 
| 2 2 | 
             
            ===============
         | 
| 3 3 |  | 
| 4 | 
            +
            ### Upgrading to >= 1.4.0
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            #### Nil values for structures
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            Nil values always been a special case when dealing with types especially with the following structures:
         | 
| 9 | 
            +
             - Array
         | 
| 10 | 
            +
             - Hash
         | 
| 11 | 
            +
             - Set
         | 
| 12 | 
            +
             
         | 
| 13 | 
            +
            The behaviour for these structures has change through out the latest releases. For instance:
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            ```ruby
         | 
| 16 | 
            +
            class Api < Grape::API
         | 
| 17 | 
            +
              params do
         | 
| 18 | 
            +
                require :my_param, type: Array[Integer]
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              get 'example' do
         | 
| 22 | 
            +
                 params[:my_param]
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
              get '/example', params: { my_param: nil }
         | 
| 25 | 
            +
              # 1.3.1 = []
         | 
| 26 | 
            +
              # 1.3.2 = nil
         | 
| 27 | 
            +
            end
         | 
| 28 | 
            +
            ```
         | 
| 29 | 
            +
            For now on, `nil` values stay `nil` values for all types, including arrays, sets and hashes.
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            If you want to have the same behavior as 1.3.1, apply a `default` validator
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            ```ruby
         | 
| 34 | 
            +
            class Api < Grape::API
         | 
| 35 | 
            +
              params do
         | 
| 36 | 
            +
                require :my_param, type: Array[Integer], default: []
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              get 'example' do
         | 
| 40 | 
            +
                 params[:my_param]
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
              get '/example', params: { my_param: nil } # => []
         | 
| 43 | 
            +
            end
         | 
| 44 | 
            +
            ```
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            #### Default validator
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            Default validator is now applied for `nil` values.
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            ```ruby
         | 
| 51 | 
            +
            class Api < Grape::API
         | 
| 52 | 
            +
              params do
         | 
| 53 | 
            +
                requires :my_param, type: Integer, default: 0
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              get 'example' do
         | 
| 57 | 
            +
                 params[:my_param]
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
              get '/example', params: { my_param: nil } #=> before: nil, after: 0
         | 
| 60 | 
            +
            end
         | 
| 61 | 
            +
            ```
         | 
| 62 | 
            +
             | 
| 4 63 | 
             
            ### Upgrading to >= 1.3.0
         | 
| 5 64 |  | 
| 6 65 | 
             
            #### Ruby
         | 
| @@ -9,38 +68,83 @@ After adding dry-types, Ruby 2.4 or newer is required. | |
| 9 68 |  | 
| 10 69 | 
             
            #### Coercion
         | 
| 11 70 |  | 
| 12 | 
            -
            [Virtus](https://github.com/solnic/virtus) has been replaced by | 
| 71 | 
            +
            [Virtus](https://github.com/solnic/virtus) has been replaced by
         | 
| 72 | 
            +
            [dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter
         | 
| 73 | 
            +
            coercion. If your project depends on Virtus outside of Grape, explicitly
         | 
| 74 | 
            +
            add it to your `Gemfile`.
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            Here's an example of how to migrate a custom type from Virtus to dry-types:
         | 
| 13 77 |  | 
| 14 78 | 
             
            ```ruby
         | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 79 | 
            +
            # Legacy Grape parser
         | 
| 80 | 
            +
            class SecureUriType < Virtus::Attribute
         | 
| 81 | 
            +
              def coerce(input)
         | 
| 82 | 
            +
                URI.parse value
         | 
| 83 | 
            +
              end
         | 
| 17 84 |  | 
| 18 | 
            -
               | 
| 19 | 
            -
             | 
| 85 | 
            +
              def value_coerced?(input)
         | 
| 86 | 
            +
                value.is_a? String
         | 
| 87 | 
            +
              end
         | 
| 20 88 | 
             
            end
         | 
| 21 89 |  | 
| 22 | 
            -
            # somewhere in your API
         | 
| 23 90 | 
             
            params do
         | 
| 24 | 
            -
              requires : | 
| 91 | 
            +
              requires :secure_uri, type: SecureUri
         | 
| 25 92 | 
             
            end
         | 
| 26 93 | 
             
            ```
         | 
| 27 94 |  | 
| 28 | 
            -
             | 
| 95 | 
            +
            To use dry-types, we need to:
         | 
| 29 96 |  | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 97 | 
            +
            1. Remove the inheritance of `Virtus::Attribute`
         | 
| 98 | 
            +
            1. Rename `coerce` to `self.parse`
         | 
| 99 | 
            +
            1. Rename `value_coerced?` to `self.parsed?`
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            The custom type must have a class-level `parse` method to the model. A
         | 
| 102 | 
            +
            class-level `parsed?` is needed if the parsed type differs from the
         | 
| 103 | 
            +
            defined type. In the example below, since `SecureUri` is not the same
         | 
| 104 | 
            +
            as `URI::HTTPS`, `self.parsed?` is needed:
         | 
| 33 105 |  | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 106 | 
            +
            ```ruby
         | 
| 107 | 
            +
            # New dry-types parser
         | 
| 108 | 
            +
            class SecureUri
         | 
| 109 | 
            +
              def self.parse(value)
         | 
| 110 | 
            +
                URI.parse value
         | 
| 111 | 
            +
              end
         | 
| 36 112 |  | 
| 37 | 
            -
              def self. | 
| 38 | 
            -
                 | 
| 113 | 
            +
              def self.parsed?(value)
         | 
| 114 | 
            +
                value.is_a? URI::HTTPS
         | 
| 39 115 | 
             
              end
         | 
| 40 116 | 
             
            end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            params do
         | 
| 119 | 
            +
              requires :secure_uri, type: SecureUri
         | 
| 120 | 
            +
            end
         | 
| 41 121 | 
             
            ```
         | 
| 42 122 |  | 
| 43 | 
            -
             | 
| 123 | 
            +
            #### Ensure that Array types have explicit coercions
         | 
| 124 | 
            +
             | 
| 125 | 
            +
            Unlike Virtus, dry-types does not perform any implict coercions. If you
         | 
| 126 | 
            +
            have any uses of `Array[String]`, `Array[Integer]`, etc. be sure they
         | 
| 127 | 
            +
            use a `coerce_with` block. For example:
         | 
| 128 | 
            +
             | 
| 129 | 
            +
            ```ruby
         | 
| 130 | 
            +
            requires :values, type: Array[String]
         | 
| 131 | 
            +
            ```
         | 
| 132 | 
            +
             | 
| 133 | 
            +
            It's quite common to pass a comma-separated list, such as `tag1,tag2` as
         | 
| 134 | 
            +
            `values`. Previously Virtus would implicitly coerce this to
         | 
| 135 | 
            +
            `Array(values)` so that `["tag1,tag2"]` would pass the type checks, but
         | 
| 136 | 
            +
            with `dry-types` the values are no longer coerced for you. To fix this,
         | 
| 137 | 
            +
            you might do:
         | 
| 138 | 
            +
             | 
| 139 | 
            +
            ```ruby
         | 
| 140 | 
            +
            requires :values, type: Array[String], coerce_with: ->(val) { val.split(',').map(&:strip) }
         | 
| 141 | 
            +
            ```
         | 
| 142 | 
            +
             | 
| 143 | 
            +
            Likewise, for `Array[Integer]`, you might do:
         | 
| 144 | 
            +
             | 
| 145 | 
            +
            ```ruby
         | 
| 146 | 
            +
            requires :values, type: Array[Integer], coerce_with: ->(val) { val.split(',').map(&:strip).map(&:to_i) }
         | 
| 147 | 
            +
            ```
         | 
| 44 148 |  | 
| 45 149 | 
             
            For more information see [#1920](https://github.com/ruby-grape/grape/pull/1920).
         | 
| 46 150 |  | 
    
        data/lib/grape/api/instance.rb
    CHANGED
    
    | @@ -205,11 +205,12 @@ module Grape | |
| 205 205 | 
             
                        route_settings[:requirements] = route.requirements
         | 
| 206 206 | 
             
                        route_settings[:path] = route.origin
         | 
| 207 207 | 
             
                        route_settings[:methods] ||= []
         | 
| 208 | 
            -
                        route_settings[:methods] | 
| 208 | 
            +
                        if route.request_method == '*' || route_settings[:methods].include?('*')
         | 
| 209 | 
            +
                          route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS
         | 
| 210 | 
            +
                        else
         | 
| 211 | 
            +
                          route_settings[:methods] << route.request_method
         | 
| 212 | 
            +
                        end
         | 
| 209 213 | 
             
                        route_settings[:endpoint] = route.app
         | 
| 210 | 
            -
             | 
| 211 | 
            -
                        # using the :any shorthand produces [nil] for route methods, substitute all manually
         | 
| 212 | 
            -
                        route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS if route_settings[:methods].include?('*')
         | 
| 213 214 | 
             
                      end
         | 
| 214 215 | 
             
                    end
         | 
| 215 216 |  | 
| @@ -243,9 +244,13 @@ module Grape | |
| 243 244 | 
             
                  # Generate a route that returns an HTTP 405 response for a user defined
         | 
| 244 245 | 
             
                  # path on methods not specified
         | 
| 245 246 | 
             
                  def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
         | 
| 246 | 
            -
                     | 
| 247 | 
            -
             | 
| 248 | 
            -
             | 
| 247 | 
            +
                    supported_methods =
         | 
| 248 | 
            +
                      if self.class.namespace_inheritable(:do_not_route_options)
         | 
| 249 | 
            +
                        Grape::Http::Headers::SUPPORTED_METHODS
         | 
| 250 | 
            +
                      else
         | 
| 251 | 
            +
                        Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS
         | 
| 252 | 
            +
                      end
         | 
| 253 | 
            +
                    not_allowed_methods = supported_methods - allowed_methods
         | 
| 249 254 | 
             
                    return if not_allowed_methods.empty?
         | 
| 250 255 |  | 
| 251 256 | 
             
                    @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
         | 
| @@ -28,36 +28,38 @@ module Grape | |
| 28 28 | 
             
                  # Methods which should not be available in filters until the before filter
         | 
| 29 29 | 
             
                  # has completed
         | 
| 30 30 | 
             
                  module PostBeforeFilter
         | 
| 31 | 
            -
                    def declared(passed_params, options = {}, declared_params = nil)
         | 
| 31 | 
            +
                    def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
         | 
| 32 32 | 
             
                      options = options.reverse_merge(include_missing: true, include_parent_namespaces: true)
         | 
| 33 33 | 
             
                      declared_params ||= optioned_declared_params(**options)
         | 
| 34 34 |  | 
| 35 35 | 
             
                      if passed_params.is_a?(Array)
         | 
| 36 | 
            -
                        declared_array(passed_params, options, declared_params)
         | 
| 36 | 
            +
                        declared_array(passed_params, options, declared_params, params_nested_path)
         | 
| 37 37 | 
             
                      else
         | 
| 38 | 
            -
                        declared_hash(passed_params, options, declared_params)
         | 
| 38 | 
            +
                        declared_hash(passed_params, options, declared_params, params_nested_path)
         | 
| 39 39 | 
             
                      end
         | 
| 40 40 | 
             
                    end
         | 
| 41 41 |  | 
| 42 42 | 
             
                    private
         | 
| 43 43 |  | 
| 44 | 
            -
                    def declared_array(passed_params, options, declared_params)
         | 
| 44 | 
            +
                    def declared_array(passed_params, options, declared_params, params_nested_path)
         | 
| 45 45 | 
             
                      passed_params.map do |passed_param|
         | 
| 46 | 
            -
                        declared(passed_param || {}, options, declared_params)
         | 
| 46 | 
            +
                        declared(passed_param || {}, options, declared_params, params_nested_path)
         | 
| 47 47 | 
             
                      end
         | 
| 48 48 | 
             
                    end
         | 
| 49 49 |  | 
| 50 | 
            -
                    def declared_hash(passed_params, options, declared_params)
         | 
| 50 | 
            +
                    def declared_hash(passed_params, options, declared_params, params_nested_path)
         | 
| 51 51 | 
             
                      declared_params.each_with_object(passed_params.class.new) do |declared_param, memo|
         | 
| 52 52 | 
             
                        if declared_param.is_a?(Hash)
         | 
| 53 53 | 
             
                          declared_param.each_pair do |declared_parent_param, declared_children_params|
         | 
| 54 | 
            +
                            params_nested_path_dup = params_nested_path.dup
         | 
| 55 | 
            +
                            params_nested_path_dup << declared_parent_param.to_s
         | 
| 54 56 | 
             
                            next unless options[:include_missing] || passed_params.key?(declared_parent_param)
         | 
| 55 57 |  | 
| 56 58 | 
             
                            passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
         | 
| 57 59 | 
             
                            memo_key = optioned_param_key(declared_parent_param, options)
         | 
| 58 60 |  | 
| 59 | 
            -
                            memo[memo_key] = handle_passed_param( | 
| 60 | 
            -
                              declared(passed_children_params, options, declared_children_params)
         | 
| 61 | 
            +
                            memo[memo_key] = handle_passed_param(passed_children_params, params_nested_path_dup) do
         | 
| 62 | 
            +
                              declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
         | 
| 61 63 | 
             
                            end
         | 
| 62 64 | 
             
                          end
         | 
| 63 65 | 
             
                        else
         | 
| @@ -77,19 +79,34 @@ module Grape | |
| 77 79 | 
             
                      end
         | 
| 78 80 | 
             
                    end
         | 
| 79 81 |  | 
| 80 | 
            -
                    def handle_passed_param( | 
| 81 | 
            -
                       | 
| 82 | 
            +
                    def handle_passed_param(passed_children_params, params_nested_path, &_block)
         | 
| 83 | 
            +
                      if should_be_empty_hash?(passed_children_params, params_nested_path)
         | 
| 84 | 
            +
                        {}
         | 
| 85 | 
            +
                      elsif should_be_empty_array?(passed_children_params, params_nested_path)
         | 
| 86 | 
            +
                        []
         | 
| 87 | 
            +
                      else
         | 
| 88 | 
            +
                        yield
         | 
| 89 | 
            +
                      end
         | 
| 82 90 | 
             
                    end
         | 
| 83 91 |  | 
| 84 | 
            -
                    def should_be_empty_array?( | 
| 85 | 
            -
                       | 
| 92 | 
            +
                    def should_be_empty_array?(passed_children_params, params_nested_path)
         | 
| 93 | 
            +
                      passed_children_params.empty? && declared_param_is_array?(params_nested_path)
         | 
| 86 94 | 
             
                    end
         | 
| 87 95 |  | 
| 88 | 
            -
                    def declared_param_is_array?( | 
| 89 | 
            -
                      key =  | 
| 96 | 
            +
                    def declared_param_is_array?(params_nested_path)
         | 
| 97 | 
            +
                      key = route_options_params_key(params_nested_path)
         | 
| 90 98 | 
             
                      route_options_params[key] && route_options_params[key][:type] == 'Array'
         | 
| 91 99 | 
             
                    end
         | 
| 92 100 |  | 
| 101 | 
            +
                    def should_be_empty_hash?(passed_children_params, params_nested_path)
         | 
| 102 | 
            +
                      passed_children_params.empty? && declared_param_is_hash?(params_nested_path)
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    def declared_param_is_hash?(params_nested_path)
         | 
| 106 | 
            +
                      key = route_options_params_key(params_nested_path)
         | 
| 107 | 
            +
                      route_options_params[key] && route_options_params[key][:type] == 'Hash'
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
             | 
| 93 110 | 
             
                    def route_options_params
         | 
| 94 111 | 
             
                      options[:route_options][:params] || {}
         | 
| 95 112 | 
             
                    end
         | 
| @@ -98,6 +115,12 @@ module Grape | |
| 98 115 | 
             
                      options[:stringify] ? declared_param.to_s : declared_param.to_sym
         | 
| 99 116 | 
             
                    end
         | 
| 100 117 |  | 
| 118 | 
            +
                    def route_options_params_key(params_nested_path)
         | 
| 119 | 
            +
                      key = params_nested_path[0]
         | 
| 120 | 
            +
                      key += '[' + params_nested_path[1..-1].join('][') + ']' if params_nested_path.size > 1
         | 
| 121 | 
            +
                      key
         | 
| 122 | 
            +
                    end
         | 
| 123 | 
            +
             | 
| 101 124 | 
             
                    def optioned_declared_params(**options)
         | 
| 102 125 | 
             
                      declared_params = if options[:include_parent_namespaces]
         | 
| 103 126 | 
             
                                          # Declared params including parent namespaces
         | 
    
        data/lib/grape/http/headers.rb
    CHANGED
    
    | @@ -21,6 +21,7 @@ module Grape | |
| 21 21 | 
             
                  OPTIONS = 'OPTIONS'
         | 
| 22 22 |  | 
| 23 23 | 
             
                  SUPPORTED_METHODS = [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS].freeze
         | 
| 24 | 
            +
                  SUPPORTED_METHODS_WITHOUT_OPTIONS = Grape::Util::LazyObject.new { [GET, POST, PUT, PATCH, DELETE, HEAD].freeze }
         | 
| 24 25 |  | 
| 25 26 | 
             
                  HTTP_ACCEPT_VERSION    = 'HTTP_ACCEPT_VERSION'
         | 
| 26 27 | 
             
                  X_CASCADE              = 'X-Cascade'
         | 
| @@ -3,11 +3,12 @@ | |
| 3 3 | 
             
            module Rack
         | 
| 4 4 | 
             
              module Accept
         | 
| 5 5 | 
             
                module Header
         | 
| 6 | 
            +
                  ALLOWED_CHARACTERS = %r{^([a-z*]+)\/([a-z0-9*\&\^\-_#\$!.+]+)(?:;([a-z0-9=;]+))?$}.freeze
         | 
| 6 7 | 
             
                  class << self
         | 
| 7 8 | 
             
                    # Corrected version of https://github.com/mjackson/rack-accept/blob/master/lib/rack/accept/header.rb#L40-L44
         | 
| 8 9 | 
             
                    def parse_media_type(media_type)
         | 
| 9 10 | 
             
                      # see http://tools.ietf.org/html/rfc6838#section-4.2 for allowed characters in media type names
         | 
| 10 | 
            -
                      m = media_type | 
| 11 | 
            +
                      m = media_type&.match(ALLOWED_CHARACTERS)
         | 
| 11 12 | 
             
                      m ? [m[1], m[2], m[3] || ''] : []
         | 
| 12 13 | 
             
                    end
         | 
| 13 14 | 
             
                  end
         | 
    
        data/lib/grape/path.rb
    CHANGED
    
    | @@ -42,11 +42,11 @@ module Grape | |
| 42 42 | 
             
                end
         | 
| 43 43 |  | 
| 44 44 | 
             
                def namespace?
         | 
| 45 | 
            -
                  namespace | 
| 45 | 
            +
                  namespace&.match?(/^\S/) && namespace != '/'
         | 
| 46 46 | 
             
                end
         | 
| 47 47 |  | 
| 48 48 | 
             
                def path?
         | 
| 49 | 
            -
                  raw_path | 
| 49 | 
            +
                  raw_path&.match?(/^\S/) && raw_path != '/'
         | 
| 50 50 | 
             
                end
         | 
| 51 51 |  | 
| 52 52 | 
             
                def suffix
         | 
| @@ -6,10 +6,31 @@ module Grape | |
| 6 6 | 
             
                class AttributeTranslator
         | 
| 7 7 | 
             
                  attr_reader :attributes, :request_method, :requirements
         | 
| 8 8 |  | 
| 9 | 
            +
                  ROUTE_ATTRIBUTES = %i[
         | 
| 10 | 
            +
                    prefix
         | 
| 11 | 
            +
                    version
         | 
| 12 | 
            +
                    settings
         | 
| 13 | 
            +
                    format
         | 
| 14 | 
            +
                    description
         | 
| 15 | 
            +
                    http_codes
         | 
| 16 | 
            +
                    headers
         | 
| 17 | 
            +
                    entity
         | 
| 18 | 
            +
                    details
         | 
| 19 | 
            +
                    requirements
         | 
| 20 | 
            +
                    request_method
         | 
| 21 | 
            +
                    namespace
         | 
| 22 | 
            +
                  ].freeze
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  ROUTER_ATTRIBUTES = %i[pattern index].freeze
         | 
| 25 | 
            +
             | 
| 9 26 | 
             
                  def initialize(attributes = {})
         | 
| 10 27 | 
             
                    @attributes = attributes
         | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  (ROUTER_ATTRIBUTES + ROUTE_ATTRIBUTES).each do |attr|
         | 
| 31 | 
            +
                    define_method attr do
         | 
| 32 | 
            +
                      attributes[attr]
         | 
| 33 | 
            +
                    end
         | 
| 13 34 | 
             
                  end
         | 
| 14 35 |  | 
| 15 36 | 
             
                  def to_h
         | 
    
        data/lib/grape/router/route.rb
    CHANGED
    
    | @@ -12,12 +12,13 @@ module Grape | |
| 12 12 | 
             
                  SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/.freeze
         | 
| 13 13 | 
             
                  FIXED_NAMED_CAPTURES = %w[format version].freeze
         | 
| 14 14 |  | 
| 15 | 
            -
                  attr_accessor :pattern, :translator, :app, :index, : | 
| 15 | 
            +
                  attr_accessor :pattern, :translator, :app, :index, :options
         | 
| 16 16 |  | 
| 17 17 | 
             
                  alias attributes translator
         | 
| 18 18 |  | 
| 19 19 | 
             
                  extend Forwardable
         | 
| 20 20 | 
             
                  def_delegators :pattern, :path, :origin
         | 
| 21 | 
            +
                  delegate Grape::Router::AttributeTranslator::ROUTE_ATTRIBUTES => :attributes
         | 
| 21 22 |  | 
| 22 23 | 
             
                  def method_missing(method_id, *arguments)
         | 
| 23 24 | 
             
                    match = ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s)
         | 
| @@ -31,26 +32,7 @@ module Grape | |
| 31 32 | 
             
                  end
         | 
| 32 33 |  | 
| 33 34 | 
             
                  def respond_to_missing?(method_id, _)
         | 
| 34 | 
            -
                    ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s)
         | 
| 35 | 
            -
                  end
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                  %i[
         | 
| 38 | 
            -
                    prefix
         | 
| 39 | 
            -
                    version
         | 
| 40 | 
            -
                    settings
         | 
| 41 | 
            -
                    format
         | 
| 42 | 
            -
                    description
         | 
| 43 | 
            -
                    http_codes
         | 
| 44 | 
            -
                    headers
         | 
| 45 | 
            -
                    entity
         | 
| 46 | 
            -
                    details
         | 
| 47 | 
            -
                    requirements
         | 
| 48 | 
            -
                    request_method
         | 
| 49 | 
            -
                    namespace
         | 
| 50 | 
            -
                  ].each do |method_name|
         | 
| 51 | 
            -
                    define_method method_name do
         | 
| 52 | 
            -
                      attributes.public_send method_name
         | 
| 53 | 
            -
                    end
         | 
| 35 | 
            +
                    ROUTE_ATTRIBUTE_REGEXP.match?(method_id.to_s)
         | 
| 54 36 | 
             
                  end
         | 
| 55 37 |  | 
| 56 38 | 
             
                  def route_method
         | 
| @@ -67,7 +49,6 @@ module Grape | |
| 67 49 | 
             
                    method_s = method.to_s
         | 
| 68 50 | 
             
                    method_upcase = Grape::Http::Headers.find_supported_method(method_s) || method_s.upcase
         | 
| 69 51 |  | 
| 70 | 
            -
                    @suffix     = options[:suffix]
         | 
| 71 52 | 
             
                    @options    = options.merge(method: method_upcase)
         | 
| 72 53 | 
             
                    @pattern    = Pattern.new(pattern, **options)
         | 
| 73 54 | 
             
                    @translator = AttributeTranslator.new(**options, request_method: method_upcase)
         | 
    
        data/lib/grape/router.rb
    CHANGED
    
    | @@ -7,16 +7,6 @@ module Grape | |
| 7 7 | 
             
              class Router
         | 
| 8 8 | 
             
                attr_reader :map, :compiled
         | 
| 9 9 |  | 
| 10 | 
            -
                class Any < AttributeTranslator
         | 
| 11 | 
            -
                  attr_reader :pattern, :regexp, :index
         | 
| 12 | 
            -
                  def initialize(pattern, regexp, index, **attributes)
         | 
| 13 | 
            -
                    @pattern = pattern
         | 
| 14 | 
            -
                    @regexp = regexp
         | 
| 15 | 
            -
                    @index = index
         | 
| 16 | 
            -
                    super(attributes)
         | 
| 17 | 
            -
                  end
         | 
| 18 | 
            -
                end
         | 
| 19 | 
            -
             | 
| 20 10 | 
             
                class NormalizePathCache < Grape::Util::Cache
         | 
| 21 11 | 
             
                  def initialize
         | 
| 22 12 | 
             
                    @cache = Hash.new do |h, path|
         | 
| @@ -39,18 +29,20 @@ module Grape | |
| 39 29 |  | 
| 40 30 | 
             
                def initialize
         | 
| 41 31 | 
             
                  @neutral_map = []
         | 
| 32 | 
            +
                  @neutral_regexes = []
         | 
| 42 33 | 
             
                  @map = Hash.new { |hash, key| hash[key] = [] }
         | 
| 43 34 | 
             
                  @optimized_map = Hash.new { |hash, key| hash[key] = // }
         | 
| 44 35 | 
             
                end
         | 
| 45 36 |  | 
| 46 37 | 
             
                def compile!
         | 
| 47 38 | 
             
                  return if compiled
         | 
| 48 | 
            -
                  @union = Regexp.union(@ | 
| 39 | 
            +
                  @union = Regexp.union(@neutral_regexes)
         | 
| 40 | 
            +
                  @neutral_regexes = nil
         | 
| 49 41 | 
             
                  self.class.supported_methods.each do |method|
         | 
| 50 42 | 
             
                    routes = map[method]
         | 
| 51 43 | 
             
                    @optimized_map[method] = routes.map.with_index do |route, index|
         | 
| 52 44 | 
             
                      route.index = index
         | 
| 53 | 
            -
                       | 
| 45 | 
            +
                      Regexp.new("(?<_#{index}>#{route.pattern.to_regexp})")
         | 
| 54 46 | 
             
                    end
         | 
| 55 47 | 
             
                    @optimized_map[method] = Regexp.union(@optimized_map[method])
         | 
| 56 48 | 
             
                  end
         | 
| @@ -62,8 +54,8 @@ module Grape | |
| 62 54 | 
             
                end
         | 
| 63 55 |  | 
| 64 56 | 
             
                def associate_routes(pattern, **options)
         | 
| 65 | 
            -
                   | 
| 66 | 
            -
                  @neutral_map <<  | 
| 57 | 
            +
                  @neutral_regexes << Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}")
         | 
| 58 | 
            +
                  @neutral_map << Grape::Router::AttributeTranslator.new(options.merge(pattern: pattern, index: @neutral_map.length))
         | 
| 67 59 | 
             
                end
         | 
| 68 60 |  | 
| 69 61 | 
             
                def call(env)
         | 
| @@ -5,8 +5,7 @@ module Grape | |
| 5 5 | 
             
                # Base for classes which need to operate with own values kept
         | 
| 6 6 | 
             
                # in the hash and inherited values kept in a Hash-like object.
         | 
| 7 7 | 
             
                class BaseInheritable
         | 
| 8 | 
            -
                  attr_accessor :inherited_values
         | 
| 9 | 
            -
                  attr_accessor :new_values
         | 
| 8 | 
            +
                  attr_accessor :inherited_values, :new_values
         | 
| 10 9 |  | 
| 11 10 | 
             
                  # @param inherited_values [Object] An object implementing an interface
         | 
| 12 11 | 
             
                  #   of the Hash class.
         | 
| @@ -26,10 +25,14 @@ module Grape | |
| 26 25 | 
             
                  end
         | 
| 27 26 |  | 
| 28 27 | 
             
                  def keys
         | 
| 29 | 
            -
                     | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 28 | 
            +
                    if new_values.any?
         | 
| 29 | 
            +
                      combined = inherited_values.keys
         | 
| 30 | 
            +
                      combined.concat(new_values.keys)
         | 
| 31 | 
            +
                      combined.uniq!
         | 
| 32 | 
            +
                      combined
         | 
| 33 | 
            +
                    else
         | 
| 34 | 
            +
                      inherited_values.keys
         | 
| 35 | 
            +
                    end
         | 
| 33 36 | 
             
                  end
         | 
| 34 37 |  | 
| 35 38 | 
             
                  def key?(name)
         | 
| @@ -8,8 +8,10 @@ module Grape | |
| 8 8 | 
             
                  protected
         | 
| 9 9 |  | 
| 10 10 | 
             
                  def concat_values(inherited_value, new_value)
         | 
| 11 | 
            +
                    return inherited_value unless new_value
         | 
| 12 | 
            +
             | 
| 11 13 | 
             
                    [].tap do |value|
         | 
| 12 | 
            -
                      value.concat(new_value) | 
| 14 | 
            +
                      value.concat(new_value)
         | 
| 13 15 | 
             
                      value.concat(inherited_value)
         | 
| 14 16 | 
             
                    end
         | 
| 15 17 | 
             
                  end
         |