Skip to content

Commit 22534bc

Browse files
committed
First commit
1 parent f95c423 commit 22534bc

File tree

4 files changed

+216
-1
lines changed

4 files changed

+216
-1
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
elm-stuff
33
# elm-repl generated files
44
repl-temp-*
5+
.DS_Store

README.md

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,37 @@
11
# jsonapi-http-retry
2-
Retry failed jsonapi requests with retry policies
2+
3+
Retry failed jsonapi requests with retry policies.
4+
5+
It uses the [`Retry` package](https://package.elm-lang.org/packages/choonkeat/elm-retry/latest/) and the [`jsonapi-http` package](https://package.elm-lang.org/packages/calions-app/jsonapi-http/latest/) under the hood.
6+
7+
With this package you can add [`retry`](https://package.elm-lang.org/packages/choonkeat/elm-retry/latest/) policies to [`jsonapi-http`](https://package.elm-lang.org/packages/calions-app/jsonapi-http/latest/) requests errors.
8+
You can choose specific errors that will trigger a retry: unauthenticated error, network error, etc...
9+
10+
## Getting Started
11+
12+
Here is an example retrying requests 5 times maximum with a constant interval between retries, only for unauthenticated and unauthorized errors:
13+
14+
```elm
15+
import Http.Request
16+
import Http.Retry
17+
import Json.Encode
18+
import JsonApi.Decode
19+
import Retry
20+
21+
request : Cmd Msg
22+
request =
23+
Request.request
24+
{ url = "http://endpoint"
25+
, headers = []
26+
, body = Json.Encode.object []
27+
, documentDecoder = JsonApi.Decode.resources "resource-type" entityDecoder
28+
}
29+
|> Http.Retry.with
30+
[ Retry.maxRetries 5
31+
, Retry.exponentialBackoff { interval = 500, maxInterval = 3000 }
32+
]
33+
[ Http.Retry.onUnauthenticatedStatus
34+
, Http.Retry.onUnauthorizedStatus
35+
]
36+
|> Task.perform OnTaskCompleted
37+
```

elm.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"type": "package",
3+
"name": "calions-app/jsonapi-http-retry",
4+
"summary": "Retry failed jsonapi requests with policies",
5+
"license": "MIT",
6+
"version": "1.0.0",
7+
"exposed-modules": [
8+
"Http.Retry"
9+
],
10+
"elm-version": "0.19.0 <= v < 0.20.0",
11+
"dependencies": {
12+
"calions-app/jsonapi-http": "1.1.0 <= v < 2.0.0",
13+
"choonkeat/elm-retry": "1.0.1 <= v < 2.0.0",
14+
"elm/core": "1.0.5 <= v < 2.0.0",
15+
"elm/http": "2.0.0 <= v < 3.0.0",
16+
"krisajenkins/remotedata": "6.0.1 <= v < 7.0.0"
17+
},
18+
"test-dependencies": {}
19+
}

src/Http/Retry.elm

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
module Http.Retry exposing
2+
( with
3+
, FailureCondition, onStatus, onUnauthenticatedStatus, onUnauthorizedStatus, onNetworkError, onTimeout, onAllFailures
4+
)
5+
6+
{-| Add retries to a jsonapi http task, based on a list of retry policies, until any one of
7+
the policies fail too.
8+
9+
@docs with
10+
11+
12+
# Failure conditions
13+
14+
@docs FailureCondition, onStatus, onUnauthenticatedStatus, onUnauthorizedStatus, onNetworkError, onTimeout, onAllFailures
15+
16+
-}
17+
18+
import Http
19+
import Http.Error
20+
import RemoteData
21+
import Retry
22+
import Task exposing (Task)
23+
24+
25+
{-| FailureCondition contains a function that filter request errors
26+
-}
27+
type FailureCondition
28+
= FailureCondition (Http.Error.RequestError -> Bool)
29+
30+
31+
{-| To use with `with` function. Filters errors to be retried by retrying errors base on the 401 status code
32+
-}
33+
onUnauthenticatedStatus : FailureCondition
34+
onUnauthenticatedStatus =
35+
let
36+
conditionCheck err =
37+
case err of
38+
Http.Error.HttpError (Http.BadStatus 401) ->
39+
True
40+
41+
_ ->
42+
False
43+
in
44+
FailureCondition conditionCheck
45+
46+
47+
{-| To use with `with` function. Filters errors to be retried by retrying errors base on the 403 status code
48+
-}
49+
onUnauthorizedStatus : FailureCondition
50+
onUnauthorizedStatus =
51+
let
52+
conditionCheck err =
53+
case err of
54+
Http.Error.HttpError (Http.BadStatus 403) ->
55+
True
56+
57+
_ ->
58+
False
59+
in
60+
FailureCondition conditionCheck
61+
62+
63+
{-| To use with `with` function. Filters errors to be retried by retrying errors based on the status code
64+
-}
65+
onStatus : Int -> FailureCondition
66+
onStatus statusCode =
67+
let
68+
conditionCheck err =
69+
case err of
70+
Http.Error.HttpError (Http.BadStatus code) ->
71+
code == statusCode
72+
73+
_ ->
74+
False
75+
in
76+
FailureCondition conditionCheck
77+
78+
79+
{-| To use with `with` function. Filters errors to be retried by retrying all network errors
80+
-}
81+
onNetworkError : FailureCondition
82+
onNetworkError =
83+
let
84+
conditionCheck err =
85+
case err of
86+
Http.Error.HttpError Http.NetworkError ->
87+
True
88+
89+
_ ->
90+
False
91+
in
92+
FailureCondition conditionCheck
93+
94+
95+
{-| To use with `with` function. Filters errors to be retried by retrying all timeout errors
96+
-}
97+
onTimeout : FailureCondition
98+
onTimeout =
99+
let
100+
conditionCheck err =
101+
case err of
102+
Http.Error.HttpError Http.Timeout ->
103+
True
104+
105+
_ ->
106+
False
107+
in
108+
FailureCondition conditionCheck
109+
110+
111+
{-| To use with `with` function. Filters errors to be retried by retrying all errors
112+
-}
113+
onAllFailures : FailureCondition
114+
onAllFailures =
115+
let
116+
conditionCheck err =
117+
True
118+
in
119+
FailureCondition conditionCheck
120+
121+
122+
{-| Given a list of error handling `Policy` from [`Retry` package](https://package.elm-lang.org/packages/choonkeat/elm-retry/latest/) we can make our `originalTask`
123+
retry on failure until any one of the `Policy` fails.
124+
125+
originalTask
126+
|> Http.Retry.with
127+
[ Retry.maxDuration 7000
128+
, Retry.exponentialBackoff { interval = 500, maxInterval = 3000 }
129+
]
130+
[ Http.Retry.onUnauthenticatedStatus ]
131+
|> Task.perform DidOriginalTask
132+
133+
-}
134+
with : List (Retry.Policy Http.Error.RequestError) -> List FailureCondition -> Task Never (RemoteData.RemoteData Http.Error.RequestError data) -> Task Never (RemoteData.RemoteData Http.Error.RequestError data)
135+
with errTasks failureConditions originalTask =
136+
originalTask
137+
|> Task.mapError (always (Http.Error.CustomError "no error"))
138+
|> Task.andThen (convertToError failureConditions)
139+
|> Retry.with errTasks
140+
|> Task.onError (RemoteData.Failure >> Task.succeed)
141+
142+
143+
convertToError : List FailureCondition -> RemoteData.RemoteData Http.Error.RequestError data -> Task Http.Error.RequestError (RemoteData.RemoteData Http.Error.RequestError data)
144+
convertToError failureConditions result =
145+
case result of
146+
RemoteData.Loading ->
147+
Task.succeed result
148+
149+
RemoteData.NotAsked ->
150+
Task.succeed result
151+
152+
RemoteData.Success d ->
153+
Task.succeed result
154+
155+
RemoteData.Failure err ->
156+
if List.any (\(FailureCondition f) -> f err) failureConditions then
157+
Task.fail err
158+
159+
else
160+
Task.succeed result

0 commit comments

Comments
 (0)