grape-idempotency 0.1.1 → 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: 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: