rack-idempotency 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3a968ee6548a34d9c9847ddb7da52a4f52fd5af8
4
- data.tar.gz: c642eca5a95a7fc9a8a589545cea7c43ca71a0c9
3
+ metadata.gz: d0f2c986509883647740c552f61dac9f5c7bca1b
4
+ data.tar.gz: ffd45d68ce0a2cab353ebfc69e5c72471d136bd8
5
5
  SHA512:
6
- metadata.gz: ed16a28b9147de63b309108ca463b52dd5f22d82699f0336e125ca3e116aafed55a8350b0d9ca7c35a894597e9ad2dd9b46ac1ee3e4f4009ffaacfc29e1d0045
7
- data.tar.gz: 48e5cd558e95a13c46a8718a267ebf1bcad363a2433a0fad89dc0306115ef0bdd18365249e142655be37497c96023365a59d677be71ca116adbd8094987b0ab6
6
+ metadata.gz: 2ba38597029bd2dcccb581da0e140aee7306ba0acf09fe17cb14a68cf613af50114fdf97d3d953e3523f4c0475f52804a26b8d458cf79b879d7690bb64749fb1
7
+ data.tar.gz: bc4879106473268af4ca39be56bcb5df5205f472cc4f1b20962d9b1ddad2f87d462ac8e54b1c7f70ef2c8f0d439c1c2af575f59cde95156016a0c55c5f479da8
data/.travis.yml CHANGED
@@ -3,3 +3,8 @@ language: ruby
3
3
  rvm:
4
4
  - 2.4.0
5
5
  before_install: gem install bundler -v 1.14.0
6
+ addons:
7
+ code_climate:
8
+ repo_token: 20db8d8f86efbab70a903a66606cb5ab8e1aaaae2d76337c4fea3271ae2b2d37
9
+ after_success:
10
+ - bundle exec codeclimate-test-reporter
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
1
  # Rack::Idempotency
2
+ [![Gem Version](https://badge.fury.io/rb/rack-idempotency.svg)](https://badge.fury.io/rb/rack-idempotency)
3
+ [![Build Status](https://travis-ci.org/guitsaru/rack-idempotency.svg?branch=master)](https://travis-ci.org/guitsaru/rack-idempotency)
4
+ [![Code Climate](https://codeclimate.com/github/guitsaru/rack-idempotency/badges/gpa.svg)](https://codeclimate.com/github/guitsaru/rack-idempotency)
5
+ [![Test Coverage](https://codeclimate.com/github/guitsaru/rack-idempotency/badges/coverage.svg)](https://codeclimate.com/github/guitsaru/rack-idempotency/coverage)
2
6
 
3
7
  Rack middleware ensuring at most once requests for mutating endpoints.
4
8
 
@@ -51,7 +55,7 @@ Rack::Idempotency should handle the following cases:
51
55
  - [x] The initial connection to the server fails.
52
56
  - [ ] The request fails halfway through, leaving data in limbo.
53
57
  - [x] The request succeeds, but the connection to the client is lost.
54
-
58
+
55
59
  The second case is much more dependent on implementation. Rack::Idempotency assumes that the request is in a transaction and
56
60
  can be safely retried if it wasn't successful.
57
61
 
@@ -69,4 +73,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/guitsa
69
73
  ## License
70
74
 
71
75
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
72
-
@@ -2,13 +2,9 @@ module Rack
2
2
  class Idempotency
3
3
  # Basic version of the store. This class doesn't read or write.
4
4
  class NullStore
5
- def read(_id)
6
- nil
7
- end
5
+ def read(_id); end
8
6
 
9
- def write(_id, _value)
10
- nil
11
- end
7
+ def write(_id, _value); end
12
8
  end
13
9
  end
14
10
  end
@@ -0,0 +1,11 @@
1
+ require "rack/request"
2
+
3
+ module Rack
4
+ class Idempotency
5
+ class Request < Rack::Request
6
+ def idempotency_key
7
+ get_header("HTTP_IDEMPOTENCY_KEY")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ module Rack
2
+ class Idempotency
3
+ class RequestStorage
4
+ def initialize(store, request)
5
+ @store = store
6
+ @request = request
7
+ end
8
+
9
+ def read
10
+ stored = store.read(storage_key)
11
+ JSON.parse(stored) if stored
12
+ end
13
+
14
+ def write(response)
15
+ store.write(storage_key, response.to_json)
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :request
21
+ attr_reader :store
22
+
23
+ def storage_key
24
+ "rack:idempotency:" + request.idempotency_key
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ require "rack/utils"
2
+
3
+ module Rack
4
+ class Idempotency
5
+ class Response
6
+ attr_reader :status, :headers, :body
7
+
8
+ def initialize(status, headers, body)
9
+ @status = status.to_i
10
+ @headers = Rack::Utils::HeaderHash.new(headers)
11
+ @body = body
12
+ end
13
+
14
+ def success?
15
+ status.to_i >= 200 && status.to_i < 400
16
+ end
17
+
18
+ def to_a
19
+ [status, headers.to_hash, body.each]
20
+ end
21
+
22
+ def to_json
23
+ to_a.to_json
24
+ end
25
+
26
+ def idempotency_key
27
+ headers["Idempotency-Key"]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class Idempotency
3
- VERSION = "0.2.0".freeze
3
+ VERSION = "0.3.0".freeze
4
4
  end
5
5
  end
@@ -4,6 +4,9 @@ require "rack/idempotency/version"
4
4
 
5
5
  require "rack/idempotency/memory_store"
6
6
  require "rack/idempotency/null_store"
7
+ require "rack/idempotency/request"
8
+ require "rack/idempotency/request_storage"
9
+ require "rack/idempotency/response"
7
10
 
8
11
  module Rack
9
12
  # Rack middleware for ensuring mutating endpoints are called at most once.
@@ -18,41 +21,33 @@ module Rack
18
21
  end
19
22
 
20
23
  def call(env)
21
- idempotency_key = env["HTTP_IDEMPOTENCY_KEY"]
22
- storage_key = generate_storage_key(idempotency_key)
23
- stored = store.read(storage_key)
24
- return JSON.parse(stored) if stored
24
+ @env = env
25
+ @request = Request.new(env.dup.freeze)
25
26
 
26
- response = @app.call(env)
27
- store_response(storage_key, response)
28
-
29
- response
27
+ lookup || store_response
30
28
  end
31
29
 
32
30
  private
33
31
 
34
- attr_reader :app, :store
35
-
36
- def store_response(storage_key, response)
37
- if successful?(response)
38
- marshaled_response = response.to_json
39
- store.write(storage_key, marshaled_response)
40
- end
32
+ attr_reader :app
33
+ attr_reader :env
34
+ attr_reader :request
35
+ attr_reader :store
41
36
 
42
- response
37
+ def request_store
38
+ @request_store ||= RequestStorage.new(store, request)
43
39
  end
44
40
 
45
- def successful?(response)
46
- status = response.first
47
-
48
- # Treat redirects as a successful response.
49
- status.to_i >= 200 && status.to_i < 400
41
+ def lookup
42
+ request_store.read
50
43
  end
51
44
 
52
- def generate_storage_key(idempotency_key)
53
- namespace = "rack:idempotency"
45
+ def store_response
46
+ response = Response.new(*@app.call(env))
47
+
48
+ request_store.write(response) if response.success?
54
49
 
55
- [namespace, idempotency_key.to_s].join(":")
50
+ response.to_a
56
51
  end
57
52
  end
58
53
  end
@@ -25,4 +25,6 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency "bundler", "~> 1.14"
26
26
  spec.add_development_dependency "rake", "~> 10.0"
27
27
  spec.add_development_dependency "rspec", "~> 3.0"
28
+ spec.add_development_dependency "simplecov"
29
+ spec.add_development_dependency "codeclimate-test-reporter"
28
30
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-idempotency
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Pruitt
@@ -66,6 +66,34 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: codeclimate-test-reporter
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
69
97
  description:
70
98
  email:
71
99
  - guitsaru@gmail.com
@@ -86,6 +114,9 @@ files:
86
114
  - lib/rack/idempotency.rb
87
115
  - lib/rack/idempotency/memory_store.rb
88
116
  - lib/rack/idempotency/null_store.rb
117
+ - lib/rack/idempotency/request.rb
118
+ - lib/rack/idempotency/request_storage.rb
119
+ - lib/rack/idempotency/response.rb
89
120
  - lib/rack/idempotency/version.rb
90
121
  - rack-idempotency.gemspec
91
122
  homepage: https://github.com/guitsaru/rack-idempotency