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 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: