idempo 1.2.1 → 1.3.0
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/.github/workflows/ci.yml +6 -6
 - data/Appraisals +9 -0
 - data/CHANGELOG.md +14 -6
 - data/README.md +6 -3
 - data/Rakefile +5 -1
 - data/idempo.gemspec +3 -3
 - data/lib/idempo/concurrent_request_error_app.rb +1 -1
 - data/lib/idempo/malformed_key_error_app.rb +1 -1
 - data/lib/idempo/request_fingerprint.rb +16 -3
 - data/lib/idempo/version.rb +1 -1
 - data/lib/idempo.rb +22 -5
 - metadata +28 -13
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 3db7fbb3c612bae5675890f118986c3d026e49c73f3c74c24f15fea51e6a67c7
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 65a193261af6e424fe8f7e3437d28e02c20ddb4c9ae199c9556f39676474b8ac
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: bd32562481a479070ed2806ef3201d370417fd9acfbbb8499d1aefb2f1000f278ee3dff47ccb0ac640f33001be8c41cccba7c498980d07c893dee519e53f539b
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: df3b05f901ac7e49bb82bfde9a522e2a179b96953db10751b7814858597a5a0af0aa82ff0fd6824e3a11cae6bdf4ebc84004a35b09cfae7a39958114a307eef8
         
     | 
    
        data/.github/workflows/ci.yml
    CHANGED
    
    | 
         @@ -15,7 +15,7 @@ jobs: 
     | 
|
| 
       15 
15 
     | 
    
         
             
                strategy:
         
     | 
| 
       16 
16 
     | 
    
         
             
                  matrix:
         
     | 
| 
       17 
17 
     | 
    
         
             
                    ruby:
         
     | 
| 
       18 
     | 
    
         
            -
                      -  
     | 
| 
      
 18 
     | 
    
         
            +
                      - "2.7"
         
     | 
| 
       19 
19 
     | 
    
         
             
                steps:
         
     | 
| 
       20 
20 
     | 
    
         
             
                  - name: Checkout
         
     | 
| 
       21 
21 
     | 
    
         
             
                    uses: actions/checkout@v4
         
     | 
| 
         @@ -34,14 +34,13 @@ jobs: 
     | 
|
| 
       34 
34 
     | 
    
         
             
                  - name: Standard (Lint)
         
     | 
| 
       35 
35 
     | 
    
         
             
                    run: bundle exec rake standard
         
     | 
| 
       36 
36 
     | 
    
         
             
              test:
         
     | 
| 
       37 
     | 
    
         
            -
                name: Specs
         
     | 
| 
      
 37 
     | 
    
         
            +
                name: "Specs (Rack 2 and 3)"
         
     | 
| 
       38 
38 
     | 
    
         
             
                runs-on: ubuntu-22.04
         
     | 
| 
       39 
39 
     | 
    
         
             
                if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
         
     | 
| 
       40 
40 
     | 
    
         
             
                strategy:
         
     | 
| 
       41 
41 
     | 
    
         
             
                  matrix:
         
     | 
| 
       42 
42 
     | 
    
         
             
                    ruby:
         
     | 
| 
       43 
     | 
    
         
            -
                      -  
     | 
| 
       44 
     | 
    
         
            -
                      - '3.2'
         
     | 
| 
      
 43 
     | 
    
         
            +
                      - "2.7"
         
     | 
| 
       45 
44 
     | 
    
         
             
                services:
         
     | 
| 
       46 
45 
     | 
    
         
             
                  mysql:
         
     | 
| 
       47 
46 
     | 
    
         
             
                    image: mysql:5.7
         
     | 
| 
         @@ -65,13 +64,14 @@ jobs: 
     | 
|
| 
       65 
64 
     | 
    
         
             
                steps:
         
     | 
| 
       66 
65 
     | 
    
         
             
                  - name: Checkout
         
     | 
| 
       67 
66 
     | 
    
         
             
                    uses: actions/checkout@v4
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
       68 
68 
     | 
    
         
             
                  - name: Setup Ruby
         
     | 
| 
       69 
69 
     | 
    
         
             
                    uses: ruby/setup-ruby@v1
         
     | 
| 
       70 
70 
     | 
    
         
             
                    with:
         
     | 
| 
       71 
71 
     | 
    
         
             
                      ruby-version: ${{ matrix.ruby }}
         
     | 
| 
       72 
72 
     | 
    
         
             
                      bundler-cache: true
         
     | 
| 
       73 
     | 
    
         
            -
                  - name: RSpec
         
     | 
| 
       74 
     | 
    
         
            -
                    run: bundle exec rspec
         
     | 
| 
      
 73 
     | 
    
         
            +
                  - name: RSpec via Appraisal
         
     | 
| 
      
 74 
     | 
    
         
            +
                    run: "bundle exec appraisal install && bundle exec appraisal rspec"
         
     | 
| 
       75 
75 
     | 
    
         
             
                    env:
         
     | 
| 
       76 
76 
     | 
    
         
             
                      MYSQL_HOST: 127.0.0.1
         
     | 
| 
       77 
77 
     | 
    
         
             
                      MYSQL_PORT: 3306
         
     | 
    
        data/Appraisals
    ADDED
    
    
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,13 +1,21 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            ##  
     | 
| 
      
 1 
     | 
    
         
            +
            ## 1.3.0
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            - Streamline integration with both Rack 2 and 3, add tests for request fingerprinting.
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            ## 1.2.2
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            - Support `#to_ary` on Rack response bodies on newer Rails/Rack versions
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            ## 1.2.1
         
     | 
| 
       2 
10 
     | 
    
         | 
| 
       3 
11 
     | 
    
         
             
            - Use autoloading for internal modules. A user using Redis does not have to load the ActiveRecord storage backend, for example
         
     | 
| 
       4 
12 
     | 
    
         
             
            - Ensure that the original Rack response body receives a `close` when reading out for caching
         
     | 
| 
       5 
13 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
            ##  
     | 
| 
      
 14 
     | 
    
         
            +
            ## 1.2.0
         
     | 
| 
       7 
15 
     | 
    
         | 
| 
       8 
16 
     | 
    
         
             
            - Use memory locking in addition to DB locking in `ActiveRecordBackend`
         
     | 
| 
       9 
17 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
            ##  
     | 
| 
      
 18 
     | 
    
         
            +
            ## 1.1.0
         
     | 
| 
       11 
19 
     | 
    
         | 
| 
       12 
20 
     | 
    
         
             
            - Use modern ActiveRecord migration options for better Rails 7.x compatibility
         
     | 
| 
       13 
21 
     | 
    
         
             
            - Ensure Github actions CI can run and uses Postgres appropriately
         
     | 
| 
         @@ -15,16 +23,16 @@ 
     | 
|
| 
       15 
23 
     | 
    
         
             
            - Implement `#prune!` on storage backends
         
     | 
| 
       16 
24 
     | 
    
         
             
            - Reformat all code using [standard](https://github.com/standardrb/standard) instead of wetransfer_style as it is both more relaxed and more modern
         
     | 
| 
       17 
25 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
            ##  
     | 
| 
      
 26 
     | 
    
         
            +
            ## 1.0.0
         
     | 
| 
       19 
27 
     | 
    
         | 
| 
       20 
28 
     | 
    
         
             
            - Release 1.0 as the API can be considered stable and the gem has been in production for years
         
     | 
| 
       21 
29 
     | 
    
         | 
| 
       22 
     | 
    
         
            -
            ##  
     | 
| 
      
 30 
     | 
    
         
            +
            ## 0.2.0
         
     | 
| 
       23 
31 
     | 
    
         | 
| 
       24 
32 
     | 
    
         
             
            - Allow setting the global default TTL for the cached responses
         
     | 
| 
       25 
33 
     | 
    
         
             
            - Allow customisation of the request key computation (so that the client can decide whether to include/exclude `Authorization` and the like)
         
     | 
| 
       26 
34 
     | 
    
         
             
            - Extract the error response generating apps into separate modules, to make them easier to override
         
     | 
| 
       27 
35 
     | 
    
         | 
| 
       28 
     | 
    
         
            -
            ##  
     | 
| 
      
 36 
     | 
    
         
            +
            ## 0.1.0
         
     | 
| 
       29 
37 
     | 
    
         | 
| 
       30 
38 
     | 
    
         
             
            - Initial release
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -10,7 +10,8 @@ instead of calling your application. 
     | 
|
| 
       10 
10 
     | 
    
         
             
            Idempo supports a number of backends, we recommend using Redis if you have multiple application servers / dynos and MemoryBackend if you are only using one single Puma worker. To initialize with Redis as backend pass the `backend:` parameter when adding the middleware:
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
            ```ruby
         
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
      
 13 
     | 
    
         
            +
            be = Idempo::RedisBackend.new(Rails.application.config.redis_connection_pool)
         
     | 
| 
      
 14 
     | 
    
         
            +
            use Idempo, backend: be
         
     | 
| 
       14 
15 
     | 
    
         
             
            ```
         
     | 
| 
       15 
16 
     | 
    
         | 
| 
       16 
17 
     | 
    
         
             
            and to initialize with a memory store as backend:
         
     | 
| 
         @@ -65,7 +66,8 @@ end 
     | 
|
| 
       65 
66 
     | 
    
         
             
            Then configure Idempo to use the backend (in your `application.rb`):
         
     | 
| 
       66 
67 
     | 
    
         | 
| 
       67 
68 
     | 
    
         
             
            ```ruby
         
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
      
 69 
     | 
    
         
            +
            be = Idempo::ActiveRecordBackend.new
         
     | 
| 
      
 70 
     | 
    
         
            +
            config.middleware.insert Idempo, backend: be
         
     | 
| 
       69 
71 
     | 
    
         
             
            ```
         
     | 
| 
       70 
72 
     | 
    
         | 
| 
       71 
73 
     | 
    
         
             
            In your regular tasks (cron or Rake) you will want to add a call to delete old Idempo responses (there is an index on `expire_at`):
         
     | 
| 
         @@ -87,7 +89,8 @@ use Idempo, backend: Idempo::RedisBackend.new 
     | 
|
| 
       87 
89 
     | 
    
         
             
            If you have a configured Redis connection pool (and you should) - pass it to the initializer:
         
     | 
| 
       88 
90 
     | 
    
         | 
| 
       89 
91 
     | 
    
         
             
            ```ruby
         
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
      
 92 
     | 
    
         
            +
            be = Idempo::RedisBackend.new(config.redis_connection_pool)
         
     | 
| 
      
 93 
     | 
    
         
            +
            config.middleware.insert Idempo, backend: be
         
     | 
| 
       91 
94 
     | 
    
         
             
            ```
         
     | 
| 
       92 
95 
     | 
    
         | 
| 
       93 
96 
     | 
    
         
             
            All data stored in Redis will have TTLs and will expire automatically. Redis scripts ensure that updates to the stored idempotent responses and locking happen atomically.
         
     | 
    
        data/Rakefile
    CHANGED
    
    
    
        data/idempo.gemspec
    CHANGED
    
    | 
         @@ -29,16 +29,15 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       29 
29 
     | 
    
         
             
              # Specify which files should be added to the gem when it is released.
         
     | 
| 
       30 
30 
     | 
    
         
             
              # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
         
     | 
| 
       31 
31 
     | 
    
         
             
              spec.files = Dir.chdir(File.expand_path(__dir__)) do
         
     | 
| 
       32 
     | 
    
         
            -
                `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
         
     | 
| 
      
 32 
     | 
    
         
            +
                `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features|gemfiles)/}) }
         
     | 
| 
       33 
33 
     | 
    
         
             
              end
         
     | 
| 
       34 
34 
     | 
    
         
             
              spec.bindir = "exe"
         
     | 
| 
       35 
35 
     | 
    
         
             
              spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
         
     | 
| 
       36 
36 
     | 
    
         
             
              spec.require_paths = ["lib"]
         
     | 
| 
       37 
37 
     | 
    
         | 
| 
       38 
     | 
    
         
            -
              # Uncomment to register a new dependency of your gem
         
     | 
| 
       39 
     | 
    
         
            -
              spec.add_dependency "rack"
         
     | 
| 
       40 
38 
     | 
    
         
             
              spec.add_dependency "msgpack"
         
     | 
| 
       41 
39 
     | 
    
         
             
              spec.add_dependency "measurometer", "~> 1.3"
         
     | 
| 
      
 40 
     | 
    
         
            +
              spec.add_dependency "rack", "< 4"
         
     | 
| 
       42 
41 
     | 
    
         | 
| 
       43 
42 
     | 
    
         
             
              spec.add_development_dependency "rake", "~> 13.0"
         
     | 
| 
       44 
43 
     | 
    
         
             
              spec.add_development_dependency "rspec", "~> 3.0"
         
     | 
| 
         @@ -48,6 +47,7 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       48 
47 
     | 
    
         
             
              spec.add_development_dependency "mysql2"
         
     | 
| 
       49 
48 
     | 
    
         
             
              spec.add_development_dependency "pg"
         
     | 
| 
       50 
49 
     | 
    
         
             
              spec.add_development_dependency "standard"
         
     | 
| 
      
 50 
     | 
    
         
            +
              spec.add_development_dependency "appraisal"
         
     | 
| 
       51 
51 
     | 
    
         | 
| 
       52 
52 
     | 
    
         
             
              # For more information and examples about making a new gem, checkout our
         
     | 
| 
       53 
53 
     | 
    
         
             
              # guide at: https://bundler.io/guides/creating_gem.html
         
     | 
| 
         @@ -8,6 +8,6 @@ class Idempo::ConcurrentRequestErrorApp 
     | 
|
| 
       8 
8 
     | 
    
         
             
                    message: "Another request with this idempotency key is still in progress, please try again later"
         
     | 
| 
       9 
9 
     | 
    
         
             
                  }
         
     | 
| 
       10 
10 
     | 
    
         
             
                }
         
     | 
| 
       11 
     | 
    
         
            -
                [429, {" 
     | 
| 
      
 11 
     | 
    
         
            +
                [429, {"retry-after" => RETRY_AFTER_SECONDS, "content-type" => "application/json"}, [JSON.pretty_generate(res)]]
         
     | 
| 
       12 
12 
     | 
    
         
             
              end
         
     | 
| 
       13 
13 
     | 
    
         
             
            end
         
     | 
| 
         @@ -6,6 +6,6 @@ class Idempo::MalformedKeyErrorApp 
     | 
|
| 
       6 
6 
     | 
    
         
             
                    message: "The Idempotency-Key header provided was empty or malformed"
         
     | 
| 
       7 
7 
     | 
    
         
             
                  }
         
     | 
| 
       8 
8 
     | 
    
         
             
                }
         
     | 
| 
       9 
     | 
    
         
            -
                [400, {" 
     | 
| 
      
 9 
     | 
    
         
            +
                [400, {"content-type" => "application/json"}, [JSON.pretty_generate(res)]]
         
     | 
| 
       10 
10 
     | 
    
         
             
              end
         
     | 
| 
       11 
11 
     | 
    
         
             
            end
         
     | 
| 
         @@ -5,11 +5,24 @@ module Idempo::RequestFingerprint 
     | 
|
| 
       5 
5 
     | 
    
         
             
                d << rack_request.url << "\n"
         
     | 
| 
       6 
6 
     | 
    
         
             
                d << rack_request.request_method << "\n"
         
     | 
| 
       7 
7 
     | 
    
         
             
                d << rack_request.get_header("HTTP_AUTHORIZATION").to_s << "\n"
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                # Under Rack 3.0 the rack.input may or may not be rewindable (this is done to support
         
     | 
| 
      
 10 
     | 
    
         
            +
                # streaming HTTP request bodies). If we know a request body is rewindable we can read it
         
     | 
| 
      
 11 
     | 
    
         
            +
                # out in full and add it to the request fingerprint. If the request body cannot be
         
     | 
| 
      
 12 
     | 
    
         
            +
                # rewound, we can't really rely on it as it can be fairly large (and we want the
         
     | 
| 
      
 13 
     | 
    
         
            +
                # downstream app to read the request body, not us).
         
     | 
| 
      
 14 
     | 
    
         
            +
                if rack_request.env["rack.input"].respond_to?(:rewind)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  read_and_rewind(rack_request.env["rack.input"], d)
         
     | 
| 
       10 
16 
     | 
    
         
             
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
       11 
18 
     | 
    
         
             
                Base64.strict_encode64(d.digest)
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              def self.read_and_rewind(source_io, to_destination_io)
         
     | 
| 
      
 22 
     | 
    
         
            +
                while (chunk = source_io.read(1024 * 65))
         
     | 
| 
      
 23 
     | 
    
         
            +
                  to_destination_io << chunk
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
       12 
25 
     | 
    
         
             
              ensure
         
     | 
| 
       13 
     | 
    
         
            -
                 
     | 
| 
      
 26 
     | 
    
         
            +
                source_io.rewind
         
     | 
| 
       14 
27 
     | 
    
         
             
              end
         
     | 
| 
       15 
28 
     | 
    
         
             
            end
         
     | 
    
        data/lib/idempo/version.rb
    CHANGED
    
    
    
        data/lib/idempo.rb
    CHANGED
    
    | 
         @@ -7,6 +7,7 @@ require "measurometer" 
     | 
|
| 
       7 
7 
     | 
    
         
             
            require "msgpack"
         
     | 
| 
       8 
8 
     | 
    
         
             
            require "zlib"
         
     | 
| 
       9 
9 
     | 
    
         
             
            require "set"
         
     | 
| 
      
 10 
     | 
    
         
            +
            require "rack"
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
       11 
12 
     | 
    
         
             
            require "idempo/version"
         
     | 
| 
       12 
13 
     | 
    
         | 
| 
         @@ -54,9 +55,17 @@ class Idempo 
     | 
|
| 
       54 
55 
     | 
    
         
             
                    return from_persisted_response(stored_response)
         
     | 
| 
       55 
56 
     | 
    
         
             
                  end
         
     | 
| 
       56 
57 
     | 
    
         | 
| 
       57 
     | 
    
         
            -
                  status,  
     | 
| 
      
 58 
     | 
    
         
            +
                  status, raw_headers, body = @app.call(env)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  headers = downcase_keys(raw_headers)
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  expires_in_seconds = (headers.delete("x-idempo-persist-for-seconds") || @persist_for_seconds).to_i
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  # In some cases `body` could respond to to_ary. In this case, we don't need to
         
     | 
| 
      
 64 
     | 
    
         
            +
                  # call .close on the body afterwards, as it is supposed to self-close as per Rack 3.0 SPEC
         
     | 
| 
      
 65 
     | 
    
         
            +
                  #
         
     | 
| 
      
 66 
     | 
    
         
            +
                  # @see https://github.com/rack/rack/blob/main/SPEC.rdoc#the-body-
         
     | 
| 
      
 67 
     | 
    
         
            +
                  body = body.to_ary if body.respond_to?(:to_ary)
         
     | 
| 
       58 
68 
     | 
    
         | 
| 
       59 
     | 
    
         
            -
                  expires_in_seconds = (headers.delete("X-Idempo-Persist-For-Seconds") || @persist_for_seconds).to_i
         
     | 
| 
       60 
69 
     | 
    
         
             
                  if response_may_be_persisted?(status, headers, body)
         
     | 
| 
       61 
70 
     | 
    
         
             
                    # Body is replaced with a cached version since a Rack response body is not rewindable
         
     | 
| 
       62 
71 
     | 
    
         
             
                    marshaled_response, body = serialize_response(status, headers, body)
         
     | 
| 
         @@ -76,6 +85,12 @@ class Idempo 
     | 
|
| 
       76 
85 
     | 
    
         | 
| 
       77 
86 
     | 
    
         
             
              private
         
     | 
| 
       78 
87 
     | 
    
         | 
| 
      
 88 
     | 
    
         
            +
              def downcase_keys(raw_headers)
         
     | 
| 
      
 89 
     | 
    
         
            +
                raw_headers.each_with_object({}) do |(name, value), hh|
         
     | 
| 
      
 90 
     | 
    
         
            +
                  hh[name.to_s.downcase] = value
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
              end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
       79 
94 
     | 
    
         
             
              def from_persisted_response(marshaled_response)
         
     | 
| 
       80 
95 
     | 
    
         
             
                if marshaled_response[-2..] != ":1"
         
     | 
| 
       81 
96 
     | 
    
         
             
                  raise Error, "Unknown serialization of the marshaled response"
         
     | 
| 
         @@ -104,21 +119,23 @@ class Idempo 
     | 
|
| 
       104 
119 
     | 
    
         
             
                # does not
         
     | 
| 
       105 
120 
     | 
    
         
             
                [deflated_message_packed_str, body_chunks]
         
     | 
| 
       106 
121 
     | 
    
         
             
              ensure
         
     | 
| 
      
 122 
     | 
    
         
            +
                # This will not be applied to response bodies of Array type.
         
     | 
| 
       107 
123 
     | 
    
         
             
                rack_response_body.close if rack_response_body.respond_to?(:close)
         
     | 
| 
       108 
124 
     | 
    
         
             
              end
         
     | 
| 
       109 
125 
     | 
    
         | 
| 
       110 
126 
     | 
    
         
             
              def response_may_be_persisted?(status, headers, body)
         
     | 
| 
       111 
     | 
    
         
            -
                return false if headers.delete(" 
     | 
| 
      
 127 
     | 
    
         
            +
                return false if headers.delete("x-idempo-policy") == "no-store"
         
     | 
| 
       112 
128 
     | 
    
         
             
                return false unless status_may_be_persisted?(status)
         
     | 
| 
       113 
129 
     | 
    
         
             
                return false unless body_size_within_limit?(headers, body)
         
     | 
| 
       114 
130 
     | 
    
         
             
                true
         
     | 
| 
       115 
131 
     | 
    
         
             
              end
         
     | 
| 
       116 
132 
     | 
    
         | 
| 
       117 
133 
     | 
    
         
             
              def body_size_within_limit?(response_headers, body)
         
     | 
| 
       118 
     | 
    
         
            -
                 
     | 
| 
      
 134 
     | 
    
         
            +
                if response_headers["content-length"]
         
     | 
| 
      
 135 
     | 
    
         
            +
                  return response_headers["content-length"].to_i <= SAVED_RESPONSE_BODY_SIZE_LIMIT
         
     | 
| 
      
 136 
     | 
    
         
            +
                end
         
     | 
| 
       119 
137 
     | 
    
         | 
| 
       120 
138 
     | 
    
         
             
                return false unless body.is_a?(Array) # Arbitrary iterable of unknown size
         
     | 
| 
       121 
     | 
    
         
            -
             
     | 
| 
       122 
139 
     | 
    
         
             
                sum_of_string_bytesizes(body) <= SAVED_RESPONSE_BODY_SIZE_LIMIT
         
     | 
| 
       123 
140 
     | 
    
         
             
              end
         
     | 
| 
       124 
141 
     | 
    
         | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: idempo
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 1. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.3.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Julik Tarkhanov
         
     | 
| 
         @@ -9,10 +9,10 @@ authors: 
     | 
|
| 
       9 
9 
     | 
    
         
             
            autorequire:
         
     | 
| 
       10 
10 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       11 
11 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       12 
     | 
    
         
            -
            date: 2024- 
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2024-11-29 00:00:00.000000000 Z
         
     | 
| 
       13 
13 
     | 
    
         
             
            dependencies:
         
     | 
| 
       14 
14 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       15 
     | 
    
         
            -
              name:  
     | 
| 
      
 15 
     | 
    
         
            +
              name: msgpack
         
     | 
| 
       16 
16 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       17 
17 
     | 
    
         
             
                requirements:
         
     | 
| 
       18 
18 
     | 
    
         
             
                - - ">="
         
     | 
| 
         @@ -26,33 +26,33 @@ dependencies: 
     | 
|
| 
       26 
26 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       27 
27 
     | 
    
         
             
                    version: '0'
         
     | 
| 
       28 
28 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       29 
     | 
    
         
            -
              name:  
     | 
| 
      
 29 
     | 
    
         
            +
              name: measurometer
         
     | 
| 
       30 
30 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       31 
31 
     | 
    
         
             
                requirements:
         
     | 
| 
       32 
     | 
    
         
            -
                - - " 
     | 
| 
      
 32 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
       33 
33 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       34 
     | 
    
         
            -
                    version: ' 
     | 
| 
      
 34 
     | 
    
         
            +
                    version: '1.3'
         
     | 
| 
       35 
35 
     | 
    
         
             
              type: :runtime
         
     | 
| 
       36 
36 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       37 
37 
     | 
    
         
             
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       38 
38 
     | 
    
         
             
                requirements:
         
     | 
| 
       39 
     | 
    
         
            -
                - - " 
     | 
| 
      
 39 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
       40 
40 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       41 
     | 
    
         
            -
                    version: ' 
     | 
| 
      
 41 
     | 
    
         
            +
                    version: '1.3'
         
     | 
| 
       42 
42 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       43 
     | 
    
         
            -
              name:  
     | 
| 
      
 43 
     | 
    
         
            +
              name: rack
         
     | 
| 
       44 
44 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       45 
45 
     | 
    
         
             
                requirements:
         
     | 
| 
       46 
     | 
    
         
            -
                - - " 
     | 
| 
      
 46 
     | 
    
         
            +
                - - "<"
         
     | 
| 
       47 
47 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       48 
     | 
    
         
            -
                    version: ' 
     | 
| 
      
 48 
     | 
    
         
            +
                    version: '4'
         
     | 
| 
       49 
49 
     | 
    
         
             
              type: :runtime
         
     | 
| 
       50 
50 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       51 
51 
     | 
    
         
             
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       52 
52 
     | 
    
         
             
                requirements:
         
     | 
| 
       53 
     | 
    
         
            -
                - - " 
     | 
| 
      
 53 
     | 
    
         
            +
                - - "<"
         
     | 
| 
       54 
54 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       55 
     | 
    
         
            -
                    version: ' 
     | 
| 
      
 55 
     | 
    
         
            +
                    version: '4'
         
     | 
| 
       56 
56 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       57 
57 
     | 
    
         
             
              name: rake
         
     | 
| 
       58 
58 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -165,6 +165,20 @@ dependencies: 
     | 
|
| 
       165 
165 
     | 
    
         
             
                - - ">="
         
     | 
| 
       166 
166 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       167 
167 
     | 
    
         
             
                    version: '0'
         
     | 
| 
      
 168 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 169 
     | 
    
         
            +
              name: appraisal
         
     | 
| 
      
 170 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 171 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 172 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 173 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 174 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 175 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 176 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 177 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 178 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 179 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 180 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 181 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
       168 
182 
     | 
    
         
             
            description: Provides idempotency keys for Rack applications.
         
     | 
| 
       169 
183 
     | 
    
         
             
            email:
         
     | 
| 
       170 
184 
     | 
    
         
             
            - me@julik.nl
         
     | 
| 
         @@ -177,6 +191,7 @@ files: 
     | 
|
| 
       177 
191 
     | 
    
         
             
            - ".gitignore"
         
     | 
| 
       178 
192 
     | 
    
         
             
            - ".rubocop.yml"
         
     | 
| 
       179 
193 
     | 
    
         
             
            - ".standard.yml"
         
     | 
| 
      
 194 
     | 
    
         
            +
            - Appraisals
         
     | 
| 
       180 
195 
     | 
    
         
             
            - CHANGELOG.md
         
     | 
| 
       181 
196 
     | 
    
         
             
            - Gemfile
         
     | 
| 
       182 
197 
     | 
    
         
             
            - LICENSE.txt
         
     |