hanami-controller 2.0.0.beta1 → 2.0.0.beta4
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 +19 -0
- data/README.md +1 -1
- data/lib/hanami/action/config.rb +262 -0
- data/lib/hanami/action/constants.rb +6 -12
- data/lib/hanami/action/csrf_protection.rb +1 -0
- data/lib/hanami/action/error.rb +41 -0
- data/lib/hanami/action/mime.rb +65 -31
- data/lib/hanami/action/params.rb +5 -12
- data/lib/hanami/action/request.rb +21 -2
- data/lib/hanami/action/response.rb +20 -22
- data/lib/hanami/action/session.rb +4 -0
- data/lib/hanami/action.rb +106 -103
- data/lib/hanami/controller/version.rb +1 -1
- metadata +5 -4
- data/lib/hanami/action/configuration.rb +0 -436
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: a363029b7df3ddc560e2975862f72548788a2a2910c2d32519f073556b4bfbb2
         | 
| 4 | 
            +
              data.tar.gz: e7d67505e3b0161a656d24e1ff634bd02c11d4fad1e217a1ee482271be6dbbd9
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 0a9aeb4ea44db1fe6f137dee8f3e5e05bda318a8c53f1f57c30204a0077b5dcfd82aefab569fd7169c2c728de2331fcaa52f1fcdec2e3adb6d4679c38791edf6
         | 
| 7 | 
            +
              data.tar.gz: 726de07b87cd8055da9973ea0365c00bde1f8808a1593ef3830051e196c722f001f6dc13615182f067542c605c9437259d1d36028031867edf3abd8d9612e817
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -2,6 +2,25 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            Complete, fast and testable actions for Rack
         | 
| 4 4 |  | 
| 5 | 
            +
            ## v2.0.0.beta4 - 2022-10-24
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ### Added
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            - [Tim Riley] Add `Response#flash`, and delgate to request object for both `Response#session` and `Response#flash`, ensuring the same objects are used when accessed via either request or response (#399)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ### Fixed
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            - [Benjamin Klotz] When a params validation schema is provided (in a `params do` block), only return the validated params from `request.params` (#375)
         | 
| 14 | 
            +
            - [Sean Collins] Handle dry-schema's messages hash now being frozen by default (#391)
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            ### Changed
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            - [Tim Riley] When `Action.accept` is declared (or `Action::Config.accepted_formats` configured), return a 406 error if an `Accept` request header is present but is not acceptable. In the absence of an `Accept` header, return a 415 error if a `Content-Type` header is present but not acceptable. If neither header is provided, accept the request. (#396)
         | 
| 19 | 
            +
            - [Tim Riley] Add `Action.handle_exception` class method as a shortcut for `Hanami::Action::Config#handle_exception` (#394)
         | 
| 20 | 
            +
            - [Tim Riley] Significantly reduce memory usage by leveraging recent dry-configurable changes, and relocating `accepted_formats`, `before_callbacks`, `after_callbacks` inheritable attributes to `config` (#392)
         | 
| 21 | 
            +
            - [Tim Riley] Make params validation schemas (defined in `params do` block) inheritable to subclasses (#394)
         | 
| 22 | 
            +
            - [Benhamin Klotz, Tim Riley] Raise `Hanami::Action::MissingSessionError` with a friendly message if `Request#session`, `Request#flash`, `Response#session` or `Response#flash` are called for an action that does not already include `Hanami::Action:Session` mixin (#379 via #395)
         | 
| 23 | 
            +
             | 
| 5 24 | 
             
            ## v2.0.0.beta1 - 2022-07-20
         | 
| 6 25 |  | 
| 7 26 | 
             
            ### Fixed
         | 
    
        data/README.md
    CHANGED
    
    | @@ -771,7 +771,7 @@ end | |
| 771 771 | 
             
            # When called with "\*/\*"            => 200
         | 
| 772 772 | 
             
            # When called with "text/html"        => 200
         | 
| 773 773 | 
             
            # When called with "application/json" => 200
         | 
| 774 | 
            -
            # When called with "application/xml"  =>  | 
| 774 | 
            +
            # When called with "application/xml"  => 415
         | 
| 775 775 | 
             
            ```
         | 
| 776 776 |  | 
| 777 777 | 
             
            You can check if the requested MIME type is accepted by the client.
         | 
| @@ -0,0 +1,262 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "dry/configurable"
         | 
| 4 | 
            +
            require_relative "mime"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Hanami
         | 
| 7 | 
            +
              class Action
         | 
| 8 | 
            +
                class Config < Dry::Configurable::Config
         | 
| 9 | 
            +
                  # Default MIME type to format mapping
         | 
| 10 | 
            +
                  #
         | 
| 11 | 
            +
                  # @since 0.2.0
         | 
| 12 | 
            +
                  # @api private
         | 
| 13 | 
            +
                  DEFAULT_FORMATS = {
         | 
| 14 | 
            +
                    "application/octet-stream" => :all,
         | 
| 15 | 
            +
                    "*/*" => :all,
         | 
| 16 | 
            +
                    "text/html" => :html
         | 
| 17 | 
            +
                  }.freeze
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  # Default public directory
         | 
| 20 | 
            +
                  #
         | 
| 21 | 
            +
                  # This serves as the root directory for file downloads
         | 
| 22 | 
            +
                  #
         | 
| 23 | 
            +
                  # @since 1.0.0
         | 
| 24 | 
            +
                  #
         | 
| 25 | 
            +
                  # @api private
         | 
| 26 | 
            +
                  DEFAULT_PUBLIC_DIRECTORY = "public"
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # @!attribute [rw] handled_exceptions
         | 
| 29 | 
            +
                  #
         | 
| 30 | 
            +
                  #   Specifies how to handle exceptions with an HTTP status.
         | 
| 31 | 
            +
                  #
         | 
| 32 | 
            +
                  #   Raised exceptions will return the corresponding HTTP status.
         | 
| 33 | 
            +
                  #
         | 
| 34 | 
            +
                  #   @return [Hash{Exception=>Integer}] exception classes as keys and HTTP statuses as values
         | 
| 35 | 
            +
                  #
         | 
| 36 | 
            +
                  #   @example
         | 
| 37 | 
            +
                  #     config.handled_exceptions = {ArgumentError => 400}
         | 
| 38 | 
            +
                  #
         | 
| 39 | 
            +
                  #   @since 0.2.0
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  # Specifies how to handle exceptions with an HTTP status
         | 
| 42 | 
            +
                  #
         | 
| 43 | 
            +
                  # Raised exceptions will return the corresponding HTTP status
         | 
| 44 | 
            +
                  #
         | 
| 45 | 
            +
                  # The specified exceptions will be merged with any previously configured
         | 
| 46 | 
            +
                  # exceptions
         | 
| 47 | 
            +
                  #
         | 
| 48 | 
            +
                  # @param exceptions [Hash{Exception=>Integer}] exception classes as keys
         | 
| 49 | 
            +
                  #   and HTTP statuses as values
         | 
| 50 | 
            +
                  #
         | 
| 51 | 
            +
                  # @return [void]
         | 
| 52 | 
            +
                  #
         | 
| 53 | 
            +
                  # @example
         | 
| 54 | 
            +
                  #   config.handle_exceptions(ArgumentError => 400}
         | 
| 55 | 
            +
                  #
         | 
| 56 | 
            +
                  # @see handled_exceptions
         | 
| 57 | 
            +
                  #
         | 
| 58 | 
            +
                  # @since 0.2.0
         | 
| 59 | 
            +
                  def handle_exception(exceptions)
         | 
| 60 | 
            +
                    self.handled_exceptions = handled_exceptions
         | 
| 61 | 
            +
                      .merge(exceptions)
         | 
| 62 | 
            +
                      .sort { |(ex1, _), (ex2, _)| ex1.ancestors.include?(ex2) ? -1 : 1 }
         | 
| 63 | 
            +
                      .to_h
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  # @!attribute [rw] formats
         | 
| 67 | 
            +
                  #
         | 
| 68 | 
            +
                  #   Specifies the MIME type to format mapping
         | 
| 69 | 
            +
                  #
         | 
| 70 | 
            +
                  #   @return [Hash{String=>Symbol}] MIME type strings as keys and format symbols as values
         | 
| 71 | 
            +
                  #
         | 
| 72 | 
            +
                  #   @see format
         | 
| 73 | 
            +
                  #   @see Hanami::Action::Mime
         | 
| 74 | 
            +
                  #
         | 
| 75 | 
            +
                  #   @example
         | 
| 76 | 
            +
                  #     config.formats = {"text/html" => :html}
         | 
| 77 | 
            +
                  #
         | 
| 78 | 
            +
                  #   @since 0.2.0
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  # Registers a MIME type to format mapping
         | 
| 81 | 
            +
                  #
         | 
| 82 | 
            +
                  # @param hash [Hash{Symbol=>String}] format symbols as keys and the MIME
         | 
| 83 | 
            +
                  #   type strings must as values
         | 
| 84 | 
            +
                  #
         | 
| 85 | 
            +
                  # @return [void]
         | 
| 86 | 
            +
                  #
         | 
| 87 | 
            +
                  # @see formats
         | 
| 88 | 
            +
                  # @see Hanami::Action::Mime
         | 
| 89 | 
            +
                  #
         | 
| 90 | 
            +
                  # @example
         | 
| 91 | 
            +
                  #   config.format html: "text/html"
         | 
| 92 | 
            +
                  #
         | 
| 93 | 
            +
                  # @since 0.2.0
         | 
| 94 | 
            +
                  def format(hash)
         | 
| 95 | 
            +
                    symbol, mime_type = *Utils::Kernel.Array(hash)
         | 
| 96 | 
            +
                    formats[Utils::Kernel.String(mime_type)] = Utils::Kernel.Symbol(symbol)
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  # Returns the configured format for the given MIME type
         | 
| 100 | 
            +
                  #
         | 
| 101 | 
            +
                  # @param mime_type [#to_s,#to_str] A mime type
         | 
| 102 | 
            +
                  #
         | 
| 103 | 
            +
                  # @return [Symbol,nil] the corresponding format, nil if not found
         | 
| 104 | 
            +
                  #
         | 
| 105 | 
            +
                  # @see format
         | 
| 106 | 
            +
                  #
         | 
| 107 | 
            +
                  # @since 0.2.0
         | 
| 108 | 
            +
                  # @api private
         | 
| 109 | 
            +
                  def format_for(mime_type)
         | 
| 110 | 
            +
                    formats[mime_type]
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  # Returns the configured format's MIME types
         | 
| 114 | 
            +
                  #
         | 
| 115 | 
            +
                  # @return [Array<String>] the format's MIME types
         | 
| 116 | 
            +
                  #
         | 
| 117 | 
            +
                  # @see formats=
         | 
| 118 | 
            +
                  # @see format
         | 
| 119 | 
            +
                  #
         | 
| 120 | 
            +
                  # @since 0.8.0
         | 
| 121 | 
            +
                  #
         | 
| 122 | 
            +
                  # @api private
         | 
| 123 | 
            +
                  def mime_types
         | 
| 124 | 
            +
                    # FIXME: this isn't efficient. speed it up!
         | 
| 125 | 
            +
                    ((formats.keys - DEFAULT_FORMATS.keys) +
         | 
| 126 | 
            +
                      Hanami::Action::Mime::TYPES.values).freeze
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                  # Returns a MIME type for the given format
         | 
| 130 | 
            +
                  #
         | 
| 131 | 
            +
                  # @param format [#to_sym] a format
         | 
| 132 | 
            +
                  #
         | 
| 133 | 
            +
                  # @return [String,nil] the corresponding MIME type, if present
         | 
| 134 | 
            +
                  #
         | 
| 135 | 
            +
                  # @since 0.2.0
         | 
| 136 | 
            +
                  # @api private
         | 
| 137 | 
            +
                  def mime_type_for(format)
         | 
| 138 | 
            +
                    formats.key(format)
         | 
| 139 | 
            +
                  end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                  # @since 2.0.0
         | 
| 142 | 
            +
                  # @api private
         | 
| 143 | 
            +
                  def accepted_mime_types
         | 
| 144 | 
            +
                    accepted_formats.any? ? Mime.restrict_mime_types(self) : mime_types
         | 
| 145 | 
            +
                  end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                  # @!attribute [rw] default_request_format
         | 
| 148 | 
            +
                  #
         | 
| 149 | 
            +
                  #   Sets a format as default fallback for all the requests without a strict
         | 
| 150 | 
            +
                  #   requirement for the MIME type.
         | 
| 151 | 
            +
                  #
         | 
| 152 | 
            +
                  #   The given format must be coercible to a symbol, and be a valid MIME
         | 
| 153 | 
            +
                  #   type alias. If it isn't, at runtime the framework will raise an
         | 
| 154 | 
            +
                  #   `Hanami::Controller::UnknownFormatError`.
         | 
| 155 | 
            +
                  #
         | 
| 156 | 
            +
                  #   By default, this value is nil.
         | 
| 157 | 
            +
                  #
         | 
| 158 | 
            +
                  #   @return [Symbol]
         | 
| 159 | 
            +
                  #
         | 
| 160 | 
            +
                  #   @see Hanami::Action::Mime
         | 
| 161 | 
            +
                  #
         | 
| 162 | 
            +
                  #   @since 0.5.0
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  # @!attribute [rw] default_response_format
         | 
| 165 | 
            +
                  #
         | 
| 166 | 
            +
                  #   Sets a format to be used for all responses regardless of the request
         | 
| 167 | 
            +
                  #   type.
         | 
| 168 | 
            +
                  #
         | 
| 169 | 
            +
                  #   The given format must be coercible to a symbol, and be a valid MIME
         | 
| 170 | 
            +
                  #   type alias. If it isn't, at the runtime the framework will raise an
         | 
| 171 | 
            +
                  #   `Hanami::Controller::UnknownFormatError`.
         | 
| 172 | 
            +
                  #
         | 
| 173 | 
            +
                  #   By default, this value is nil.
         | 
| 174 | 
            +
                  #
         | 
| 175 | 
            +
                  #   @return [Symbol]
         | 
| 176 | 
            +
                  #
         | 
| 177 | 
            +
                  #   @see Hanami::Action::Mime
         | 
| 178 | 
            +
                  #
         | 
| 179 | 
            +
                  #   @since 0.5.0
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                  # @!attribute [rw] default_charset
         | 
| 182 | 
            +
                  #
         | 
| 183 | 
            +
                  #   Sets a charset (character set) as default fallback for all the requests
         | 
| 184 | 
            +
                  #   without a strict requirement for the charset.
         | 
| 185 | 
            +
                  #
         | 
| 186 | 
            +
                  #   By default, this value is nil.
         | 
| 187 | 
            +
                  #
         | 
| 188 | 
            +
                  #   @return [String]
         | 
| 189 | 
            +
                  #
         | 
| 190 | 
            +
                  #   @see Hanami::Action::Mime
         | 
| 191 | 
            +
                  #
         | 
| 192 | 
            +
                  #   @since 0.3.0
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                  # @!attribute [rw] default_headers
         | 
| 195 | 
            +
                  #
         | 
| 196 | 
            +
                  #   Sets default headers for all responses.
         | 
| 197 | 
            +
                  #
         | 
| 198 | 
            +
                  #   By default, this is an empty hash.
         | 
| 199 | 
            +
                  #
         | 
| 200 | 
            +
                  #   @return [Hash{String=>String}] the headers
         | 
| 201 | 
            +
                  #
         | 
| 202 | 
            +
                  #   @example
         | 
| 203 | 
            +
                  #     config.default_headers = {"X-Frame-Options" => "DENY"}
         | 
| 204 | 
            +
                  #
         | 
| 205 | 
            +
                  #   @see default_headers
         | 
| 206 | 
            +
                  #
         | 
| 207 | 
            +
                  #   @since 0.4.0
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                  # @!attribute [rw] cookies
         | 
| 210 | 
            +
                  #
         | 
| 211 | 
            +
                  #   Sets default cookie options for all responses.
         | 
| 212 | 
            +
                  #
         | 
| 213 | 
            +
                  #   By default this, is an empty hash.
         | 
| 214 | 
            +
                  #
         | 
| 215 | 
            +
                  #   @return [Hash{Symbol=>String}] the cookie options
         | 
| 216 | 
            +
                  #
         | 
| 217 | 
            +
                  #   @example
         | 
| 218 | 
            +
                  #     config.cookies = {
         | 
| 219 | 
            +
                  #       domain: "hanamirb.org",
         | 
| 220 | 
            +
                  #       path: "/controller",
         | 
| 221 | 
            +
                  #       secure: true,
         | 
| 222 | 
            +
                  #       httponly: true
         | 
| 223 | 
            +
                  #     }
         | 
| 224 | 
            +
                  #
         | 
| 225 | 
            +
                  #   @since 0.4.0
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                  # @!attribute [rw] root_directory
         | 
| 228 | 
            +
                  #
         | 
| 229 | 
            +
                  #   Sets the the for the public directory, which is used for file downloads.
         | 
| 230 | 
            +
                  #   This must be an existent directory.
         | 
| 231 | 
            +
                  #
         | 
| 232 | 
            +
                  #   Defaults to the current working directory.
         | 
| 233 | 
            +
                  #
         | 
| 234 | 
            +
                  #   @return [String] the directory path
         | 
| 235 | 
            +
                  #
         | 
| 236 | 
            +
                  #   @api private
         | 
| 237 | 
            +
                  #
         | 
| 238 | 
            +
                  #   @since 1.0.0
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                  # @!attribute [rw] public_directory
         | 
| 241 | 
            +
                  #
         | 
| 242 | 
            +
                  # Sets the path to public directory. This directory is used for file downloads.
         | 
| 243 | 
            +
                  #
         | 
| 244 | 
            +
                  # This given directory will be appended onto the root directory.
         | 
| 245 | 
            +
                  #
         | 
| 246 | 
            +
                  # By default, the public directory is `"public"`.
         | 
| 247 | 
            +
                  # @return [String] the public directory path
         | 
| 248 | 
            +
                  #
         | 
| 249 | 
            +
                  # @example
         | 
| 250 | 
            +
                  #   config.public_directory = "public"
         | 
| 251 | 
            +
                  #   config.public_directory # => "/path/to/root/public"
         | 
| 252 | 
            +
                  #
         | 
| 253 | 
            +
                  # @see root_directory
         | 
| 254 | 
            +
                  #
         | 
| 255 | 
            +
                  # @since 2.0.0
         | 
| 256 | 
            +
                  def public_directory
         | 
| 257 | 
            +
                    # This must be a string, for Rack compatibility
         | 
| 258 | 
            +
                    root_directory.join(super).to_s
         | 
| 259 | 
            +
                  end
         | 
| 260 | 
            +
                end
         | 
| 261 | 
            +
              end
         | 
| 262 | 
            +
            end
         | 
| @@ -114,13 +114,13 @@ module Hanami | |
| 114 114 | 
             
                #
         | 
| 115 115 | 
             
                # @since 0.1.0
         | 
| 116 116 | 
             
                # @api private
         | 
| 117 | 
            -
                HTTP_ACCEPT | 
| 117 | 
            +
                HTTP_ACCEPT = "HTTP_ACCEPT"
         | 
| 118 118 |  | 
| 119 119 | 
             
                # The header key to set the mime type of the response
         | 
| 120 120 | 
             
                #
         | 
| 121 121 | 
             
                # @since 0.1.0
         | 
| 122 122 | 
             
                # @api private
         | 
| 123 | 
            -
                CONTENT_TYPE | 
| 123 | 
            +
                CONTENT_TYPE = ::Rack::CONTENT_TYPE
         | 
| 124 124 |  | 
| 125 125 | 
             
                # The default mime type for an incoming HTTP request
         | 
| 126 126 | 
             
                #
         | 
| @@ -152,7 +152,7 @@ module Hanami | |
| 152 152 | 
             
                #
         | 
| 153 153 | 
             
                # @since 2.0.0
         | 
| 154 154 | 
             
                # @api private
         | 
| 155 | 
            -
                ETAG | 
| 155 | 
            +
                ETAG = ::Rack::ETAG
         | 
| 156 156 |  | 
| 157 157 | 
             
                # @since 2.0.0
         | 
| 158 158 | 
             
                # @api private
         | 
| @@ -221,7 +221,7 @@ module Hanami | |
| 221 221 | 
             
                #
         | 
| 222 222 | 
             
                # @since 2.0.0
         | 
| 223 223 | 
             
                # @api private
         | 
| 224 | 
            -
                COOKIE_HASH_KEY | 
| 224 | 
            +
                COOKIE_HASH_KEY = ::Rack::RACK_REQUEST_COOKIE_HASH
         | 
| 225 225 |  | 
| 226 226 | 
             
                # The key used by Rack to set the cookies as a String in the env
         | 
| 227 227 | 
             
                #
         | 
| @@ -233,7 +233,7 @@ module Hanami | |
| 233 233 | 
             
                #
         | 
| 234 234 | 
             
                # @since 2.0.0
         | 
| 235 235 | 
             
                # @api private
         | 
| 236 | 
            -
                RACK_INPUT | 
| 236 | 
            +
                RACK_INPUT = ::Rack::RACK_INPUT
         | 
| 237 237 |  | 
| 238 238 | 
             
                # The key that returns router params from the Rack env
         | 
| 239 239 | 
             
                # This is a builtin integration for Hanami::Router
         | 
| @@ -250,12 +250,6 @@ module Hanami | |
| 250 250 |  | 
| 251 251 | 
             
                # @since 2.0.0
         | 
| 252 252 | 
             
                # @api private
         | 
| 253 | 
            -
                DEFAULT_CHARSET | 
| 254 | 
            -
             | 
| 255 | 
            -
                # The key that returns content mime type from the Rack env
         | 
| 256 | 
            -
                #
         | 
| 257 | 
            -
                # @since 2.0.0
         | 
| 258 | 
            -
                # @api private
         | 
| 259 | 
            -
                HTTP_CONTENT_TYPE    = "CONTENT_TYPE"
         | 
| 253 | 
            +
                DEFAULT_CHARSET = "utf-8"
         | 
| 260 254 | 
             
              end
         | 
| 261 255 | 
             
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Hanami
         | 
| 4 | 
            +
              class Action
         | 
| 5 | 
            +
                # @since 2.0.0
         | 
| 6 | 
            +
                class Error < ::StandardError
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                # Missing session error
         | 
| 10 | 
            +
                #
         | 
| 11 | 
            +
                # This error is raised when `session` or `flash` is accessed/set on request/response objects
         | 
| 12 | 
            +
                # in actions which do not include `Hanami::Action::Session`.
         | 
| 13 | 
            +
                #
         | 
| 14 | 
            +
                # @since 2.0.0
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # @see Hanami::Action::Session
         | 
| 17 | 
            +
                # @see Hanami::Action::Request#session
         | 
| 18 | 
            +
                # @see Hanami::Action::Response#session
         | 
| 19 | 
            +
                # @see Hanami::Action::Response#flash
         | 
| 20 | 
            +
                class MissingSessionError < Error
         | 
| 21 | 
            +
                  def initialize(session_method)
         | 
| 22 | 
            +
                    super(<<~TEXT)
         | 
| 23 | 
            +
                      Sessions are not enabled. To use `#{session_method}`:
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      Configure sessions in your Hanami app, e.g.
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                        module MyApp
         | 
| 28 | 
            +
                          class App < Hanami::App
         | 
| 29 | 
            +
                            # See Rack::Session::Cookie for options
         | 
| 30 | 
            +
                            config.sessions = :cookie, {**cookie_session_options}
         | 
| 31 | 
            +
                          end
         | 
| 32 | 
            +
                        end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      Or include session support directly in your action class:
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                        include Hanami::Action::Session
         | 
| 37 | 
            +
                    TEXT
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
    
        data/lib/hanami/action/mime.rb
    CHANGED
    
    | @@ -84,13 +84,13 @@ module Hanami | |
| 84 84 | 
             
                  #
         | 
| 85 85 | 
             
                  # @since 2.0.0
         | 
| 86 86 | 
             
                  # @api private
         | 
| 87 | 
            -
                  def self.content_type( | 
| 87 | 
            +
                  def self.content_type(config, request, accepted_mime_types)
         | 
| 88 88 | 
             
                    if request.accept_header?
         | 
| 89 89 | 
             
                      type = best_q_match(request.accept, accepted_mime_types)
         | 
| 90 90 | 
             
                      return type if type
         | 
| 91 91 | 
             
                    end
         | 
| 92 92 |  | 
| 93 | 
            -
                    default_response_type( | 
| 93 | 
            +
                    default_response_type(config) || default_content_type(config) || Action::DEFAULT_CONTENT_TYPE
         | 
| 94 94 | 
             
                  end
         | 
| 95 95 |  | 
| 96 96 | 
             
                  # @since 2.0.0
         | 
| @@ -101,22 +101,22 @@ module Hanami | |
| 101 101 |  | 
| 102 102 | 
             
                  # @since 2.0.0
         | 
| 103 103 | 
             
                  # @api private
         | 
| 104 | 
            -
                  def self.default_response_type( | 
| 105 | 
            -
                    format_to_mime_type( | 
| 104 | 
            +
                  def self.default_response_type(config)
         | 
| 105 | 
            +
                    format_to_mime_type(config.default_response_format, config)
         | 
| 106 106 | 
             
                  end
         | 
| 107 107 |  | 
| 108 108 | 
             
                  # @since 2.0.0
         | 
| 109 109 | 
             
                  # @api private
         | 
| 110 | 
            -
                  def self.default_content_type( | 
| 111 | 
            -
                    format_to_mime_type( | 
| 110 | 
            +
                  def self.default_content_type(config)
         | 
| 111 | 
            +
                    format_to_mime_type(config.default_request_format, config)
         | 
| 112 112 | 
             
                  end
         | 
| 113 113 |  | 
| 114 114 | 
             
                  # @since 2.0.0
         | 
| 115 115 | 
             
                  # @api private
         | 
| 116 | 
            -
                  def self.format_to_mime_type(format,  | 
| 116 | 
            +
                  def self.format_to_mime_type(format, config)
         | 
| 117 117 | 
             
                    return if format.nil?
         | 
| 118 118 |  | 
| 119 | 
            -
                     | 
| 119 | 
            +
                    config.mime_type_for(format) ||
         | 
| 120 120 | 
             
                      TYPES.fetch(format) { raise Hanami::Controller::UnknownFormatError.new(format) }
         | 
| 121 121 | 
             
                  end
         | 
| 122 122 |  | 
| @@ -125,17 +125,17 @@ module Hanami | |
| 125 125 | 
             
                  #
         | 
| 126 126 | 
             
                  # @see Hanami::Action::Mime#finish
         | 
| 127 127 | 
             
                  # @example
         | 
| 128 | 
            -
                  #   detect_format("text/html; charset=utf-8",  | 
| 128 | 
            +
                  #   detect_format("text/html; charset=utf-8", config)  #=> :html
         | 
| 129 129 | 
             
                  #
         | 
| 130 130 | 
             
                  # @return [Symbol, nil]
         | 
| 131 131 | 
             
                  #
         | 
| 132 132 | 
             
                  # @since 2.0.0
         | 
| 133 133 | 
             
                  # @api private
         | 
| 134 | 
            -
                  def self.detect_format(content_type,  | 
| 134 | 
            +
                  def self.detect_format(content_type, config)
         | 
| 135 135 | 
             
                    return if content_type.nil?
         | 
| 136 136 |  | 
| 137 137 | 
             
                    ct = content_type.split(";").first
         | 
| 138 | 
            -
                     | 
| 138 | 
            +
                    config.format_for(ct) || format_for(ct)
         | 
| 139 139 | 
             
                  end
         | 
| 140 140 |  | 
| 141 141 | 
             
                  # @since 2.0.0
         | 
| @@ -146,7 +146,7 @@ module Hanami | |
| 146 146 |  | 
| 147 147 | 
             
                  # Transforms symbols to MIME Types
         | 
| 148 148 | 
             
                  # @example
         | 
| 149 | 
            -
                  #   restrict_mime_types( | 
| 149 | 
            +
                  #   restrict_mime_types(config, [:json])  #=> ["application/json"]
         | 
| 150 150 | 
             
                  #
         | 
| 151 151 | 
             
                  # @return [Array<String>, nil]
         | 
| 152 152 | 
             
                  #
         | 
| @@ -154,35 +154,71 @@ module Hanami | |
| 154 154 | 
             
                  #
         | 
| 155 155 | 
             
                  # @since 2.0.0
         | 
| 156 156 | 
             
                  # @api private
         | 
| 157 | 
            -
                  def self.restrict_mime_types( | 
| 158 | 
            -
                    return if accepted_formats.empty?
         | 
| 157 | 
            +
                  def self.restrict_mime_types(config)
         | 
| 158 | 
            +
                    return if config.accepted_formats.empty?
         | 
| 159 159 |  | 
| 160 | 
            -
                    mime_types = accepted_formats.map do |format|
         | 
| 161 | 
            -
                      format_to_mime_type(format,  | 
| 160 | 
            +
                    mime_types = config.accepted_formats.map do |format|
         | 
| 161 | 
            +
                      format_to_mime_type(format, config)
         | 
| 162 162 | 
             
                    end
         | 
| 163 163 |  | 
| 164 | 
            -
                    accepted_mime_types = mime_types &  | 
| 164 | 
            +
                    accepted_mime_types = mime_types & config.mime_types
         | 
| 165 165 |  | 
| 166 166 | 
             
                    return if accepted_mime_types.empty?
         | 
| 167 167 |  | 
| 168 168 | 
             
                    accepted_mime_types
         | 
| 169 169 | 
             
                  end
         | 
| 170 170 |  | 
| 171 | 
            -
                  #  | 
| 172 | 
            -
                  #  | 
| 171 | 
            +
                  # Yields if an action is configured with `accepted_formats`, the request has an `Accept`
         | 
| 172 | 
            +
                  # header, and none of the Accept types matches the accepted formats. The given block is
         | 
| 173 | 
            +
                  # expected to halt the request handling.
         | 
| 173 174 | 
             
                  #
         | 
| 174 | 
            -
                  # If  | 
| 175 | 
            +
                  # If any of these conditions are not met, then the request is acceptable and the method
         | 
| 176 | 
            +
                  # returns without yielding.
         | 
| 175 177 | 
             
                  #
         | 
| 176 | 
            -
                  # @ | 
| 178 | 
            +
                  # @see Action#enforce_accepted_mime_types
         | 
| 179 | 
            +
                  # @see Action.accept
         | 
| 180 | 
            +
                  # @see Config#accepted_formats
         | 
| 177 181 | 
             
                  #
         | 
| 178 182 | 
             
                  # @since 2.0.0
         | 
| 179 183 | 
             
                  # @api private
         | 
| 180 | 
            -
                  def self. | 
| 181 | 
            -
                     | 
| 182 | 
            -
                                default_content_type(configuration) ||
         | 
| 183 | 
            -
                                Action::DEFAULT_CONTENT_TYPE
         | 
| 184 | 
            +
                  def self.enforce_accept(request, config)
         | 
| 185 | 
            +
                    return unless request.accept_header?
         | 
| 184 186 |  | 
| 185 | 
            -
                     | 
| 187 | 
            +
                    accept_types = ::Rack::Utils.q_values(request.accept).map(&:first)
         | 
| 188 | 
            +
                    return if accept_types.any? { |mime_type| accepted_mime_type?(mime_type, config) }
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                    yield
         | 
| 191 | 
            +
                  end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                  # Yields if an action is configured with `accepted_formats`, the request has a `Content-Type`
         | 
| 194 | 
            +
                  # header (or a `default_requst_format` is configured), and the content type does not match the
         | 
| 195 | 
            +
                  # accepted formats. The given block is expected to halt the request handling.
         | 
| 196 | 
            +
                  #
         | 
| 197 | 
            +
                  # If any of these conditions are not met, then the request is acceptable and the method
         | 
| 198 | 
            +
                  # returns without yielding.
         | 
| 199 | 
            +
                  #
         | 
| 200 | 
            +
                  # @see Action#enforce_accepted_mime_types
         | 
| 201 | 
            +
                  # @see Action.accept
         | 
| 202 | 
            +
                  # @see Config#accepted_formats
         | 
| 203 | 
            +
                  #
         | 
| 204 | 
            +
                  # @since 2.0.0
         | 
| 205 | 
            +
                  # @api private
         | 
| 206 | 
            +
                  def self.enforce_content_type(request, config)
         | 
| 207 | 
            +
                    content_type = request.content_type || default_content_type(config)
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                    return if content_type.nil?
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                    return if accepted_mime_type?(content_type, config)
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                    yield
         | 
| 214 | 
            +
                  end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                  # @since 2.0.0
         | 
| 217 | 
            +
                  # @api private
         | 
| 218 | 
            +
                  def self.accepted_mime_type?(mime_type, config)
         | 
| 219 | 
            +
                    config.accepted_mime_types.any? { |accepted_mime_type|
         | 
| 220 | 
            +
                      ::Rack::Mime.match?(accepted_mime_type, mime_type)
         | 
| 221 | 
            +
                    }
         | 
| 186 222 | 
             
                  end
         | 
| 187 223 |  | 
| 188 224 | 
             
                  # Use for setting the content_type and charset if the response
         | 
| @@ -193,14 +229,12 @@ module Hanami | |
| 193 229 | 
             
                  #
         | 
| 194 230 | 
             
                  # @since 2.0.0
         | 
| 195 231 | 
             
                  # @api private
         | 
| 196 | 
            -
                  def self.calculate_content_type_with_charset( | 
| 197 | 
            -
                    charset | 
| 198 | 
            -
                    content_type = self.content_type( | 
| 232 | 
            +
                  def self.calculate_content_type_with_charset(config, request, accepted_mime_types)
         | 
| 233 | 
            +
                    charset = self.charset(config.default_charset)
         | 
| 234 | 
            +
                    content_type = self.content_type(config, request, accepted_mime_types)
         | 
| 199 235 | 
             
                    content_type_with_charset(content_type, charset)
         | 
| 200 236 | 
             
                  end
         | 
| 201 237 |  | 
| 202 | 
            -
                  # private
         | 
| 203 | 
            -
             | 
| 204 238 | 
             
                  # Patched version of <tt>Rack::Utils.best_q_match</tt>.
         | 
| 205 239 | 
             
                  #
         | 
| 206 240 | 
             
                  # @since 2.0.0
         | 
    
        data/lib/hanami/action/params.rb
    CHANGED
    
    | @@ -25,7 +25,7 @@ module Hanami | |
| 25 25 | 
             
                    # @since 1.1.0
         | 
| 26 26 | 
             
                    # @api private
         | 
| 27 27 | 
             
                    def initialize(errors = {})
         | 
| 28 | 
            -
                      super(errors)
         | 
| 28 | 
            +
                      super(errors.dup)
         | 
| 29 29 | 
             
                    end
         | 
| 30 30 |  | 
| 31 31 | 
             
                    # Add an error to the param validations
         | 
| @@ -160,9 +160,9 @@ module Hanami | |
| 160 160 | 
             
                  def initialize(env)
         | 
| 161 161 | 
             
                    @env = env
         | 
| 162 162 | 
             
                    super(_extract_params)
         | 
| 163 | 
            -
                     | 
| 164 | 
            -
                    @params =  | 
| 165 | 
            -
                    @errors = Errors.new( | 
| 163 | 
            +
                    validation = validate
         | 
| 164 | 
            +
                    @params = validation.to_h
         | 
| 165 | 
            +
                    @errors = Errors.new(validation.messages)
         | 
| 166 166 | 
             
                    freeze
         | 
| 167 167 | 
             
                  end
         | 
| 168 168 |  | 
| @@ -235,7 +235,7 @@ module Hanami | |
| 235 235 | 
             
                    errors.empty?
         | 
| 236 236 | 
             
                  end
         | 
| 237 237 |  | 
| 238 | 
            -
                  # Serialize params to Hash
         | 
| 238 | 
            +
                  # Serialize validated params to Hash
         | 
| 239 239 | 
             
                  #
         | 
| 240 240 | 
             
                  # @return [::Hash]
         | 
| 241 241 | 
             
                  #
         | 
| @@ -244,13 +244,6 @@ module Hanami | |
| 244 244 | 
             
                    @params
         | 
| 245 245 | 
             
                  end
         | 
| 246 246 | 
             
                  alias_method :to_hash, :to_h
         | 
| 247 | 
            -
             | 
| 248 | 
            -
                  private
         | 
| 249 | 
            -
             | 
| 250 | 
            -
                  # @api private
         | 
| 251 | 
            -
                  def _params
         | 
| 252 | 
            -
                    _router_params.merge(@result.output)
         | 
| 253 | 
            -
                  end
         | 
| 254 247 | 
             
                end
         | 
| 255 248 | 
             
              end
         | 
| 256 249 | 
             
            end
         | 
| @@ -1,8 +1,9 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require " | 
| 3 | 
            +
            require "hanami/action/flash"
         | 
| 4 4 | 
             
            require "rack/mime"
         | 
| 5 5 | 
             
            require "rack/request"
         | 
| 6 | 
            +
            require "rack/utils"
         | 
| 6 7 | 
             
            require "securerandom"
         | 
| 7 8 |  | 
| 8 9 | 
             
            module Hanami
         | 
| @@ -16,9 +17,11 @@ module Hanami | |
| 16 17 | 
             
                class Request < ::Rack::Request
         | 
| 17 18 | 
             
                  attr_reader :params
         | 
| 18 19 |  | 
| 19 | 
            -
                  def initialize(env | 
| 20 | 
            +
                  def initialize(env:, params:, sessions_enabled: false)
         | 
| 20 21 | 
             
                    super(env)
         | 
| 22 | 
            +
             | 
| 21 23 | 
             
                    @params = params
         | 
| 24 | 
            +
                    @sessions_enabled = sessions_enabled
         | 
| 22 25 | 
             
                  end
         | 
| 23 26 |  | 
| 24 27 | 
             
                  def id
         | 
| @@ -26,6 +29,22 @@ module Hanami | |
| 26 29 | 
             
                    @id ||= @env[Action::REQUEST_ID] = SecureRandom.hex(Action::DEFAULT_ID_LENGTH)
         | 
| 27 30 | 
             
                  end
         | 
| 28 31 |  | 
| 32 | 
            +
                  def session
         | 
| 33 | 
            +
                    unless @sessions_enabled
         | 
| 34 | 
            +
                      raise Hanami::Action::MissingSessionError.new("Hanami::Action::Request#session")
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    super
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def flash
         | 
| 41 | 
            +
                    unless @sessions_enabled
         | 
| 42 | 
            +
                      raise Hanami::Action::MissingSessionError.new("Hanami::Action::Request#flash")
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    @flash ||= Flash.new(session[Flash::KEY])
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 29 48 | 
             
                  def accept?(mime_type)
         | 
| 30 49 | 
             
                    !!::Rack::Utils.q_values(accept).find do |mime, _|
         | 
| 31 50 | 
             
                      ::Rack::Mime.match?(mime_type, mime)
         |