grape-idempotency 0.1.1 → 0.1.3

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
  SHA256:
3
- metadata.gz: 3141f3c611358f6110a6176beea8857d316b4f2ba859b630385f3287ba68811f
4
- data.tar.gz: c367aa96495a47ac03243a31c79b0de163ccd2411cdcc7a673663b310083bfe5
3
+ metadata.gz: 7f9a4d3d3a6e885bfa07afd8f0d51dfbfe930853d3919934016e9173f9d418a0
4
+ data.tar.gz: 81e8817b09d72d35bb71746a3451e66aed12af38888b169abc0297bb2c3f7481
5
5
  SHA512:
6
- metadata.gz: c42121192159af6be351e82e2da316b549d1a7be17203ec146af5809dcc10df4ca058728b78de916913aaa02d8689317a69f3c365fa67ddd83cc06ebab5c7f29
7
- data.tar.gz: 305b32f988ecac6a209019fffd42a95b34be5f938d9eff4c749e74acc48c95ad51c8c1439b26d80fd0938d7b6854f82fe53e29d43f283b40f29d83d8c41e51ff
6
+ metadata.gz: 20a9654e3b1086a7dfa77379129f7bc46d18df4eadfa85ee13e7027dd91ffc6f68b83843f0d41212695287b472ebcb6e7c9b68ac322bb46f8ddadbdb9294a6ad
7
+ data.tar.gz: 60fc291bbc9558e19b2816614f0584812e08b6846b17b68af7395d15665439cad756034ff4f64d59592f56626d4fa0d5381f8dbcfc91b91294e5d223c8d4a8da
data/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ All changes to `grape-idempotency` will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.1.3] - 2023-01-07
8
+
9
+ ### Fix
10
+
11
+ - Second calls were returning `null` when the first response was generated inside a `rescue_from`.
12
+ - Conflict response had invalid format.
13
+
14
+ ## [0.1.2] - 2023-01-06
15
+
16
+ ### Fix
17
+
18
+ - Return correct original response when the endpoint returns a hash in the body
19
+
7
20
  ## [0.1.1] - 2023-01-06
8
21
 
9
22
  ### Fix
@@ -0,0 +1,36 @@
1
+ require 'grape/middleware/base'
2
+
3
+ module Grape
4
+ module Middleware
5
+ class Error < Base
6
+ def run_rescue_handler(handler, error)
7
+ if handler.instance_of?(Symbol)
8
+ raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
9
+
10
+ handler = public_method(handler)
11
+ end
12
+
13
+ response = handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
14
+
15
+ if response.is_a?(Rack::Response)
16
+ update_idempotency_error_with(error, response)
17
+ response
18
+ else
19
+ run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def update_idempotency_error_with(error, response)
26
+ begin
27
+ body = JSON.parse(response.body.join)
28
+ rescue JSON::ParserError
29
+ body = response.body.join
30
+ end
31
+
32
+ Grape::Idempotency.update_error_with_rescue_from_result(error, response.status, body)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Grape
4
4
  module Idempotency
5
- VERSION = '0.1.1'
5
+ VERSION = '0.1.3'
6
6
  end
7
7
  end
@@ -1,6 +1,7 @@
1
1
  require 'grape'
2
2
  require 'securerandom'
3
3
  require 'grape/idempotency/version'
4
+ require 'grape/idempotency/middleware/error'
4
5
 
5
6
  module Grape
6
7
  module Idempotency
@@ -29,7 +30,7 @@ module Grape
29
30
  cached_request = get_from_cache(idempotency_key)
30
31
  if cached_request && (cached_request["params"] != grape.request.params || cached_request["path"] != grape.request.path)
31
32
  grape.status 409
32
- return configuration.conflict_error_response.to_json
33
+ return configuration.conflict_error_response
33
34
  elsif cached_request
34
35
  grape.status cached_request["status"]
35
36
  grape.header(ORIGINAL_REQUEST_HEADER, cached_request["original_request"])
@@ -41,21 +42,44 @@ module Grape
41
42
  block.call
42
43
  end
43
44
 
44
- if response.is_a?(Hash)
45
- response = response[:message].to_json
46
- end
45
+ response = response[:message] if is_an_error?(response)
47
46
 
48
47
  original_request_id = get_request_id(grape.request.headers)
49
48
  grape.header(ORIGINAL_REQUEST_HEADER, original_request_id)
50
- response
49
+ grape.body response
50
+ rescue => e
51
+ if !cached_request && !response
52
+ validate_config!
53
+ original_request_id = get_request_id(grape.request.headers)
54
+ stored_key = store_error_request(idempotency_key, grape.request.path, grape.request.params, grape.status, original_request_id, e)
55
+ grape.header(ORIGINAL_REQUEST_HEADER, original_request_id)
56
+ grape.header(configuration.idempotency_key_header, stored_key)
57
+ end
58
+ raise
51
59
  ensure
52
- validate_config!
53
- unless cached_request
54
- stored_key = store_in_cache(idempotency_key, grape.request.path, grape.request.params, grape.status, original_request_id, response)
60
+ if !cached_request && response
61
+ validate_config!
62
+ stored_key = store_request(idempotency_key, grape.request.path, grape.request.params, grape.status, original_request_id, response)
55
63
  grape.header(configuration.idempotency_key_header, stored_key)
56
64
  end
57
65
  end
58
66
 
67
+ def update_error_with_rescue_from_result(error, status, response)
68
+ validate_config!
69
+
70
+ stored_error = get_error_request_for(error)
71
+ return unless stored_error
72
+
73
+ request_with_unmanaged_error = stored_error[:request]
74
+ idempotency_key = stored_error[:idempotency_key]
75
+ path = request_with_unmanaged_error["path"]
76
+ params = request_with_unmanaged_error["params"]
77
+ original_request_id = request_with_unmanaged_error["original_request"]
78
+
79
+ store_request(idempotency_key, path, params, status, original_request_id, response)
80
+ storage.del(stored_error[:error_key])
81
+ end
82
+
59
83
  private
60
84
 
61
85
  def validate_config!
@@ -89,7 +113,7 @@ module Grape
89
113
  JSON.parse(value)
90
114
  end
91
115
 
92
- def store_in_cache(idempotency_key, path, params, status, request_id, response)
116
+ def store_request(idempotency_key, path, params, status, request_id, response)
93
117
  body = {
94
118
  path: path,
95
119
  params: params,
@@ -101,14 +125,70 @@ module Grape
101
125
  result = storage.set(key(idempotency_key), body, ex: configuration.expires_in, nx: true)
102
126
 
103
127
  if !result
104
- return store_in_cache(random_idempotency_key, path, params, status, request_id, response)
128
+ return store_request(random_idempotency_key, path, params, status, request_id, response)
129
+ else
130
+ return idempotency_key
131
+ end
132
+ end
133
+
134
+ def store_error_request(idempotency_key, path, params, status, request_id, error)
135
+ body = {
136
+ path: path,
137
+ params: params,
138
+ status: status,
139
+ original_request: request_id,
140
+ error: {
141
+ class_name: error.class.to_s,
142
+ message: error.message
143
+ }
144
+ }.to_json
145
+
146
+ result = storage.set(error_key(idempotency_key), body, ex: 30, nx: true)
147
+
148
+ if !result
149
+ return store_error_request(random_idempotency_key, path, params, status, request_id, error)
105
150
  else
106
151
  return idempotency_key
107
152
  end
108
153
  end
109
154
 
155
+ def get_error_request_for(error)
156
+ error_keys = storage.keys("#{error_key_prefix}*")
157
+ return if error_keys.empty?
158
+
159
+ error_keys.map do |key|
160
+ request_with_error = JSON.parse(storage.get(key))
161
+ error_class_name = request_with_error["error"]["class_name"]
162
+ error_message = request_with_error["error"]["message"]
163
+
164
+ if error_class_name == error.class.to_s && error_message == error.message
165
+ {
166
+ error_key: key,
167
+ request: request_with_error,
168
+ idempotency_key: key.gsub(error_key_prefix, '')
169
+ }
170
+ end
171
+ end.first
172
+ end
173
+
174
+ def is_an_error?(response)
175
+ response.is_a?(Hash) && response.has_key?(:message) && response.has_key?(:headers) && response.has_key?(:status)
176
+ end
177
+
110
178
  def key(idempotency_key)
111
- "grape:idempotency:#{idempotency_key}"
179
+ "#{gem_prefix}#{idempotency_key}"
180
+ end
181
+
182
+ def error_key(idempotency_key)
183
+ "#{error_key_prefix}#{idempotency_key}"
184
+ end
185
+
186
+ def error_key_prefix
187
+ "#{gem_prefix}error:"
188
+ end
189
+
190
+ def gem_prefix
191
+ "grape:idempotency:"
112
192
  end
113
193
 
114
194
  def random_idempotency_key
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grape-idempotency
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Carlos García
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-06 00:00:00.000000000 Z
11
+ date: 2023-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: grape
@@ -98,6 +98,7 @@ files:
98
98
  - lib/grape-idempotency.rb
99
99
  - lib/grape/idempotency.rb
100
100
  - lib/grape/idempotency/helpers.rb
101
+ - lib/grape/idempotency/middleware/error.rb
101
102
  - lib/grape/idempotency/version.rb
102
103
  homepage: https://github.com/jcagarcia/grape-idempotency
103
104
  licenses: