Skip to content

Commit

Permalink
feat(#19): Re-raise redis exceptions by default
Browse files Browse the repository at this point in the history
  • Loading branch information
jcagarcia committed Dec 11, 2023
1 parent 85e69e4 commit e3bef43
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 49 deletions.
58 changes: 28 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,32 @@ If you want to change the error message returned in this scenario, check [How to

### Redis Storage Connectivity Issue

By default, the `grape-idempotency` gem is configured to handle potential `Redis` exceptions.
By default, `Redis` exceptions are not handled by the `grape-idempotency` gem.

Therefore, if an exception arises while attempting to read, write or delete data from the `Redis` storage, the gem will safeguard against a crash by returning empty results. Consequently, the associated `block` will execute without considering potential prior calls made with the provided idempotency key. As a result, the expected idempotent behavior will not be enforced.
Therefore, if an exception arises while attempting to read, write or delete data from the `Redis` storage, the gem will re-raise the identical exception to your application. Thus, you will be responsible for handling it within your own code, such as:

If you want to avoid this functionality, you have the option to configure the gem to refrain from handling these `Redis` exceptions. Please refer to the [manage_redis_exceptions](#manage_redis_exceptions) configuration property.
```ruby
require 'grape'
require 'grape-idempotency'

class API < Grape::API
post '/payments' do
begin
idempotent do
status 201
Payment.create!({
amount: params[:amount]
})
end
rescue Redis::BaseError => e
error!("Redis error! Idempotency is very important here and we cannot continue.", 500)
end
end
end
end
```

If you want to avoid this functionality, and you want the gem handles the potential `Redis` exceptions, you have the option to configure the gem for handling these `Redis` exceptions. Please refer to the [manage_redis_exceptions](#manage_redis_exceptions) configuration property.

## Configuration 🪚

Expand Down Expand Up @@ -206,41 +227,18 @@ I, [2023-11-23T22:41:39.148537 #1] DEBUG -- : [my-own-prefix] Returning the res
### manage_redis_exceptions
By default, the `grape-idempotency` gem is configured to handle potential `Redis` exceptions.
However, this approach carries a certain level of risk. In the case that `Redis` experiences an outage, the idempotent functionality will be lost, and this issue may go unnoticed.
By default, the `grape-idempotency` gem is configured to re-raise `Redis` exceptions.
If you prefer to take control of handling potential `Redis` exceptions, you have the option to configure the gem to abstain from managing Redis exceptions.
If you want to delegate the `Redis` exception management into the gem, you can configure it using the `manage_redis_exceptions` configuration property.
```ruby
Grape::Idempotency.configure do |c|
c.storage = @storage
c.manage_redis_exceptions = false
c.manage_redis_exceptions = true
end
```
Now, if a `Redis` exception arises while attempting to utilize the `Redis` storage, the gem will re-raise the identical exception to your application. Thus, you will be responsible for handling it within your own code, such as:
```ruby
require 'grape'
require 'grape-idempotency'
class API < Grape::API
post '/payments' do
begin
idempotent do
status 201
Payment.create!({
amount: params[:amount]
})
end
rescue Redis::BaseError => e
error!("Redis error! Idempotency is very important here and we cannot continue.", 500)
end
end
end
end
```
However, this approach carries a certain level of risk. In the case that `Redis` experiences an outage, the idempotent functionality will be lost, the endpoint will behave as no idempotent, and this issue may go unnoticed.
### conflict_error_response
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/idempotency.rb
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def initialize
@expires_in = 216_000
@idempotency_key_header = "idempotency-key"
@request_id_header = "x-request-id"
@manage_redis_exceptions = true
@manage_redis_exceptions = false
@conflict_error_response = {
"title" => "Idempotency-Key is already used",
"detail" => "This operation is idempotent and it requires correct usage of Idempotency Key. Idempotency Key MUST not be reused across different payloads of this operation."
Expand Down
36 changes: 18 additions & 18 deletions spec/idempotent_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -329,30 +329,25 @@
allow(storage).to receive(:del).and_raise(Redis::CannotConnectError)
end

it 'manages the exeception and the endpoint behaves as no idempotent' do
allow(SecureRandom).to receive(:random_number).and_return(1, 2)

it 'raises the redis exception' do
app.post('/payments') do
idempotent do
status 201
{ amount_to: SecureRandom.random_number }.to_json
end
end

header "idempotency-key", idempotency_key
post 'payments', { amount: 100_00 }.to_json
expect(last_response.body).to eq({ amount_to: 1 }.to_json)

header "idempotency-key", idempotency_key
post 'payments', { amount: 100_00 }.to_json
expect(last_response.body).to eq({ amount_to: 2 }.to_json)
expect {
header "idempotency-key", idempotency_key
post 'payments', { amount: 100_00 }.to_json
}.to raise_error(Redis::CannotConnectError)
end

context 'and the gem is configured for NOT managing Redis exceptions' do
context 'and the gem is configured for managing Redis exceptions' do
before do
Grape::Idempotency.configure do |c|
c.storage = storage
c.manage_redis_exceptions = false
c.manage_redis_exceptions = true
end
end

Expand All @@ -362,18 +357,23 @@
end
end

it 'raises the redis exception' do
it 'manages the exeception and the endpoint behaves as no idempotent' do
allow(SecureRandom).to receive(:random_number).and_return(1, 2)

app.post('/payments') do
idempotent do
status 201
{ amount_to: SecureRandom.random_number }.to_json
end
end

expect {
header "idempotency-key", idempotency_key
post 'payments', { amount: 100_00 }.to_json
}.to raise_error(Redis::CannotConnectError)

header "idempotency-key", idempotency_key
post 'payments', { amount: 100_00 }.to_json
expect(last_response.body).to eq({ amount_to: 1 }.to_json)

header "idempotency-key", idempotency_key
post 'payments', { amount: 100_00 }.to_json
expect(last_response.body).to eq({ amount_to: 2 }.to_json)
end
end
end
Expand Down

0 comments on commit e3bef43

Please sign in to comment.