grape-idempotency 0.1.2 → 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: 615bae812ada064af7880ab0553e2608a7f3386ce8c54f437d6b5048e565f256
4
- data.tar.gz: 587cc1a7c3a679e347a26a6971c68ece90c836abdade401aa412abedcc5a0f0d
3
+ metadata.gz: 7f9a4d3d3a6e885bfa07afd8f0d51dfbfe930853d3919934016e9173f9d418a0
4
+ data.tar.gz: 81e8817b09d72d35bb71746a3451e66aed12af38888b169abc0297bb2c3f7481
5
5
  SHA512:
6
- metadata.gz: 44b64241b9f09c5a252a91eba6747da4f77cc590163410ed0396f49ac64016f86e8225c2730ed2cf9545764f3fc3098585e49493fe712e241dda2b7ee674140e
7
- data.tar.gz: 3bd18355c4d7053e1d77b283e45c41f670c8a06048f8a37e3f3b1344a51dec704b659e1170e4e2f3fc85c4afd5431993e7272109fe9ac1acca2d112850528ce4
6
+ metadata.gz: 20a9654e3b1086a7dfa77379129f7bc46d18df4eadfa85ee13e7027dd91ffc6f68b83843f0d41212695287b472ebcb6e7c9b68ac322bb46f8ddadbdb9294a6ad
7
+ data.tar.gz: 60fc291bbc9558e19b2816614f0584812e08b6846b17b68af7395d15665439cad756034ff4f64d59592f56626d4fa0d5381f8dbcfc91b91294e5d223c8d4a8da
data/CHANGELOG.md CHANGED
@@ -4,13 +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
+
7
14
  ## [0.1.2] - 2023-01-06
8
15
 
9
16
  ### Fix
10
17
 
11
18
  - Return correct original response when the endpoint returns a hash in the body
12
19
 
13
-
14
20
  ## [0.1.1] - 2023-01-06
15
21
 
16
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.2'
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,19 +42,44 @@ module Grape
41
42
  block.call
42
43
  end
43
44
 
44
- response = response[:message].to_json if is_an_error?(response)
45
+ response = response[:message] if is_an_error?(response)
45
46
 
46
47
  original_request_id = get_request_id(grape.request.headers)
47
48
  grape.header(ORIGINAL_REQUEST_HEADER, original_request_id)
48
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
49
59
  ensure
50
- validate_config!
51
- unless cached_request
52
- 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)
53
63
  grape.header(configuration.idempotency_key_header, stored_key)
54
64
  end
55
65
  end
56
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
+
57
83
  private
58
84
 
59
85
  def validate_config!
@@ -87,7 +113,7 @@ module Grape
87
113
  JSON.parse(value)
88
114
  end
89
115
 
90
- def store_in_cache(idempotency_key, path, params, status, request_id, response)
116
+ def store_request(idempotency_key, path, params, status, request_id, response)
91
117
  body = {
92
118
  path: path,
93
119
  params: params,
@@ -99,18 +125,70 @@ module Grape
99
125
  result = storage.set(key(idempotency_key), body, ex: configuration.expires_in, nx: true)
100
126
 
101
127
  if !result
102
- 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)
103
129
  else
104
130
  return idempotency_key
105
131
  end
106
132
  end
107
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)
150
+ else
151
+ return idempotency_key
152
+ end
153
+ end
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
+
108
174
  def is_an_error?(response)
109
175
  response.is_a?(Hash) && response.has_key?(:message) && response.has_key?(:headers) && response.has_key?(:status)
110
176
  end
111
177
 
112
178
  def key(idempotency_key)
113
- "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:"
114
192
  end
115
193
 
116
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.2
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: