grape-idempotency 0.1.2 → 0.1.3
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 +7 -1
- data/lib/grape/idempotency/middleware/error.rb +36 -0
- data/lib/grape/idempotency/version.rb +1 -1
- data/lib/grape/idempotency.rb +86 -8
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f9a4d3d3a6e885bfa07afd8f0d51dfbfe930853d3919934016e9173f9d418a0
|
4
|
+
data.tar.gz: 81e8817b09d72d35bb71746a3451e66aed12af38888b169abc0297bb2c3f7481
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/grape/idempotency.rb
CHANGED
@@ -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
|
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]
|
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
|
-
|
51
|
-
|
52
|
-
stored_key =
|
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
|
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
|
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
|
-
"
|
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.
|
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-
|
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:
|