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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e458d533c1b108959144d42615dcfbd9b85f741811a3150e0fd64a103469a734
4
- data.tar.gz: 55a8de15e2332ff805edd7992dab11ff4bb14576b980ee71e7f699d83fcc6f11
3
+ metadata.gz: 615bae812ada064af7880ab0553e2608a7f3386ce8c54f437d6b5048e565f256
4
+ data.tar.gz: 587cc1a7c3a679e347a26a6971c68ece90c836abdade401aa412abedcc5a0f0d
5
5
  SHA512:
6
- metadata.gz: 720d6987f1007f27656a769ef7cc945d04fbbe2355dac226a2dd38c801d935f674ab11c80409a6c850817ce8f641ecf2fc9d3ed5b7dc6fbe66ee61e664843ded
7
- data.tar.gz: a4fe4ca95116844195429b7d54908aee357c59076095749420cce607dd87da005039e1096a436d85b82e7eaa58944c158910a41ec44780238915bbd22b9ab9cd
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
- # grape-idempotency πŸ‡πŸ”
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
 
@@ -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', '>= 1'
25
+ spec.add_runtime_dependency 'grape', '~> 1'
26
26
 
27
- spec.add_development_dependency 'bundler', '>= 2.4'
28
- spec.add_development_dependency 'rspec', '~> 3.12'
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', '~> 0.38'
30
+ spec.add_development_dependency 'mock_redis'
31
31
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Grape
4
4
  module Idempotency
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.2'
6
6
  end
7
7
  end
@@ -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
- if response.is_a?(Hash)
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
- store_in_cache(idempotency_key, grape.request.params, grape.status, original_request_id, response) unless cached_request
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
- storage.set(key(idempotency_key), body, ex: configuration.expires_in)
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.0
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-03 00:00:00.000000000 Z
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: '2.4'
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: '2.4'
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: '3.12'
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: '3.12'
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.38'
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.38'
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