grape-idempotency 0.1.0 β 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +3 -3
- data/grape-idempotency.gemspec +4 -4
- data/lib/grape/idempotency/version.rb +1 -1
- data/lib/grape/idempotency.rb +32 -8
- metadata +14 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 615bae812ada064af7880ab0553e2608a7f3386ce8c54f437d6b5048e565f256
|
4
|
+
data.tar.gz: 587cc1a7c3a679e347a26a6971c68ece90c836abdade401aa412abedcc5a0f0d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44b64241b9f09c5a252a91eba6747da4f77cc590163410ed0396f49ac64016f86e8225c2730ed2cf9545764f3fc3098585e49493fe712e241dda2b7ee674140e
|
7
|
+
data.tar.gz: 3bd18355c4d7053e1d77b283e45c41f670c8a06048f8a37e3f3b1344a51dec704b659e1170e4e2f3fc85c4afd5431993e7272109fe9ac1acca2d112850528ce4
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,25 @@ 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.2] - 2023-01-06
|
8
|
+
|
9
|
+
### Fix
|
10
|
+
|
11
|
+
- Return correct original response when the endpoint returns a hash in the body
|
12
|
+
|
13
|
+
|
14
|
+
## [0.1.1] - 2023-01-06
|
15
|
+
|
16
|
+
### Fix
|
17
|
+
|
18
|
+
- Return `409 - Conflict` response if idempotency key is provided for same query and body parameters BUT different endpoints.
|
19
|
+
- Use `nx: true` when storing the original request in the Redis storage for only setting the key if it does not already exist.
|
20
|
+
|
21
|
+
### Changed
|
22
|
+
|
23
|
+
- Include `idempotency-key` in the response headers
|
24
|
+
- In the case of a concurrency error when storing the request into the redis storage (because now `nx: true`), a new idempotency key will be generated, so the consumer can check the new one seeing the headers.
|
25
|
+
|
7
26
|
## [0.1.0] - 2023-01-03
|
8
27
|
|
9
28
|
- Initial version
|
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
#
|
1
|
+
# Grape::Idempotency ππ
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/grape-idempotency.svg)](https://badge.fury.io/rb/grape-idempotency)
|
4
|
+
[![Build Status](https://github.com/jcagarcia/grape-idempotency/actions/workflows/ruby.yml/badge.svg?branch=main)](https://github.com/jcagarcia/grape-idempotency/actions)
|
4
5
|
|
5
6
|
Gem for supporting idempotency in your [Grape](https://github.com/ruby-grape/grape) APIs.
|
6
7
|
|
7
|
-
|
8
8
|
Topics covered in this README:
|
9
9
|
|
10
10
|
- [Installation](#installation-)
|
@@ -75,7 +75,7 @@ Keys are automatically removed from the system if they are at least 24 hours old
|
|
75
75
|
|
76
76
|
Results are only saved if an API endpoint begins its execution. If incoming parameters fail validation or if the request conflicts with another one executing concurrently, no idempotent result is stored because no API endpoint has initiated execution. In such cases, retrying these requests is safe.
|
77
77
|
|
78
|
-
Additionally, this gem automatically appends the `Original-Request` header to your API's response, enabling you to trace back to the initial request that generated that specific response.
|
78
|
+
Additionally, this gem automatically appends the `Original-Request` header and the `Idempotency-Key` header to your API's response, enabling you to trace back to the initial request that generated that specific response.
|
79
79
|
|
80
80
|
## Configuration πͺ
|
81
81
|
|
data/grape-idempotency.gemspec
CHANGED
@@ -22,10 +22,10 @@ Gem::Specification.new do |spec|
|
|
22
22
|
|
23
23
|
spec.required_ruby_version = '>= 2.6'
|
24
24
|
|
25
|
-
spec.add_runtime_dependency 'grape', '
|
25
|
+
spec.add_runtime_dependency 'grape', '~> 1'
|
26
26
|
|
27
|
-
spec.add_development_dependency 'bundler'
|
28
|
-
spec.add_development_dependency 'rspec'
|
27
|
+
spec.add_development_dependency 'bundler'
|
28
|
+
spec.add_development_dependency 'rspec'
|
29
29
|
spec.add_development_dependency 'rack-test'
|
30
|
-
spec.add_development_dependency 'mock_redis'
|
30
|
+
spec.add_development_dependency 'mock_redis'
|
31
31
|
end
|
data/lib/grape/idempotency.rb
CHANGED
@@ -27,12 +27,13 @@ module Grape
|
|
27
27
|
return block.call unless idempotency_key
|
28
28
|
|
29
29
|
cached_request = get_from_cache(idempotency_key)
|
30
|
-
if cached_request && cached_request["params"] != grape.request.params
|
30
|
+
if cached_request && (cached_request["params"] != grape.request.params || cached_request["path"] != grape.request.path)
|
31
31
|
grape.status 409
|
32
32
|
return configuration.conflict_error_response.to_json
|
33
33
|
elsif cached_request
|
34
34
|
grape.status cached_request["status"]
|
35
35
|
grape.header(ORIGINAL_REQUEST_HEADER, cached_request["original_request"])
|
36
|
+
grape.header(configuration.idempotency_key_header, idempotency_key)
|
36
37
|
return cached_request["response"]
|
37
38
|
end
|
38
39
|
|
@@ -40,16 +41,17 @@ module Grape
|
|
40
41
|
block.call
|
41
42
|
end
|
42
43
|
|
43
|
-
|
44
|
-
response = response[:message].to_json
|
45
|
-
end
|
44
|
+
response = response[:message].to_json if is_an_error?(response)
|
46
45
|
|
47
46
|
original_request_id = get_request_id(grape.request.headers)
|
48
47
|
grape.header(ORIGINAL_REQUEST_HEADER, original_request_id)
|
49
|
-
response
|
48
|
+
grape.body response
|
50
49
|
ensure
|
51
50
|
validate_config!
|
52
|
-
|
51
|
+
unless cached_request
|
52
|
+
stored_key = store_in_cache(idempotency_key, grape.request.path, grape.request.params, grape.status, original_request_id, response)
|
53
|
+
grape.header(configuration.idempotency_key_header, stored_key)
|
54
|
+
end
|
53
55
|
end
|
54
56
|
|
55
57
|
private
|
@@ -85,20 +87,42 @@ module Grape
|
|
85
87
|
JSON.parse(value)
|
86
88
|
end
|
87
89
|
|
88
|
-
def store_in_cache(idempotency_key, params, status, request_id, response)
|
90
|
+
def store_in_cache(idempotency_key, path, params, status, request_id, response)
|
89
91
|
body = {
|
92
|
+
path: path,
|
90
93
|
params: params,
|
91
94
|
status: status,
|
92
95
|
original_request: request_id,
|
93
96
|
response: response
|
94
97
|
}.to_json
|
95
|
-
|
98
|
+
|
99
|
+
result = storage.set(key(idempotency_key), body, ex: configuration.expires_in, nx: true)
|
100
|
+
|
101
|
+
if !result
|
102
|
+
return store_in_cache(random_idempotency_key, path, params, status, request_id, response)
|
103
|
+
else
|
104
|
+
return idempotency_key
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def is_an_error?(response)
|
109
|
+
response.is_a?(Hash) && response.has_key?(:message) && response.has_key?(:headers) && response.has_key?(:status)
|
96
110
|
end
|
97
111
|
|
98
112
|
def key(idempotency_key)
|
99
113
|
"grape:idempotency:#{idempotency_key}"
|
100
114
|
end
|
101
115
|
|
116
|
+
def random_idempotency_key
|
117
|
+
tentative_key = SecureRandom.uuid
|
118
|
+
already_existing_key = storage.get(key(tentative_key))
|
119
|
+
if already_existing_key
|
120
|
+
return random_idempotency_key
|
121
|
+
else
|
122
|
+
return tentative_key
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
102
126
|
def storage
|
103
127
|
configuration.storage
|
104
128
|
end
|
metadata
CHANGED
@@ -1,27 +1,27 @@
|
|
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.2
|
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-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: grape
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -30,28 +30,28 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rack-test
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,16 +70,16 @@ dependencies:
|
|
70
70
|
name: mock_redis
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0
|
75
|
+
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0
|
82
|
+
version: '0'
|
83
83
|
description: Add idempotency support to your Grape APIs for safely retrying requests
|
84
84
|
without accidentally performing the same operation twice. When creating or updating
|
85
85
|
an object, use an idempotency key. Then, if a connection error occurs, you can safely
|