2023-03-10 20:34:25 +00:00
|
|
|
module Internal.Api.Chain exposing (..)
|
2023-03-13 12:50:41 +00:00
|
|
|
|
2023-03-10 20:34:25 +00:00
|
|
|
{-| This module aims to simplify chaining several API tasks together.
|
|
|
|
|
|
|
|
Chaining tasks together is usually done through the `Task` submodule of `elm/core`,
|
|
|
|
but this isn't always sufficient for getting complex chained tasks.
|
|
|
|
|
|
|
|
For example, suppose you need to run 3 consecutive tasks that each need an access
|
|
|
|
token, and only the 1st and the 3rd require another token. You will need to pass
|
|
|
|
on all necessary information, and preferably in a way that the compiler can
|
|
|
|
assure that the information is present when it arrives there. Using the `Task`
|
|
|
|
submodule, this can lead to indentation hell.
|
|
|
|
|
|
|
|
This module aims to allow for simple task chaining without adding too much complexity
|
|
|
|
if you wish to pass on values.
|
|
|
|
|
2023-03-14 14:51:40 +00:00
|
|
|
The model is like a snake: _____
|
|
|
|
/ o \
|
|
|
|
/-|------------ | ------- | ------------- | -------- | |\/\/
|
|
|
|
< | accessToken | baseUrl | transactionId | API call | |------< Final API call
|
|
|
|
\-|------------ | ------- | ------------- | -------- | |/\/\
|
|
|
|
\-----/
|
2023-03-13 12:50:41 +00:00
|
|
|
|
|
|
|
(You're not allowed to judge my ASCII art skills unless you submit a PR with a
|
2023-03-10 20:34:25 +00:00
|
|
|
superior ASCII snake model.)
|
|
|
|
|
|
|
|
Every task will add another value to an extensible record, which can be used
|
2023-03-14 14:50:23 +00:00
|
|
|
by later tasks in the chain. Additionally, every subtask can leave a `VaultUpdate`
|
2023-03-10 20:34:25 +00:00
|
|
|
type as a message to the Credentials to update certain information.
|
2023-03-13 12:50:41 +00:00
|
|
|
|
2023-03-10 20:34:25 +00:00
|
|
|
-}
|
|
|
|
|
2023-03-13 12:53:26 +00:00
|
|
|
import Internal.Tools.Context as Context exposing (Context)
|
2023-03-13 12:50:41 +00:00
|
|
|
import Internal.Tools.Exceptions as X
|
2023-03-10 20:34:25 +00:00
|
|
|
import Task exposing (Task)
|
|
|
|
|
2023-03-13 12:50:41 +00:00
|
|
|
|
2023-03-10 20:34:25 +00:00
|
|
|
type alias TaskChain u a b =
|
2023-03-13 12:50:41 +00:00
|
|
|
Context a -> Task X.Error (TaskChainPiece u a b)
|
|
|
|
|
|
|
|
|
|
|
|
type alias IdemChain u a =
|
|
|
|
TaskChain u a a
|
2023-03-10 20:34:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
type TaskChainPiece u a b
|
|
|
|
= TaskChainPiece
|
|
|
|
{ contextChange : Context a -> Context b
|
|
|
|
, messages : List u
|
|
|
|
}
|
|
|
|
|
2023-03-13 12:50:41 +00:00
|
|
|
|
2023-03-10 20:34:25 +00:00
|
|
|
{-| Chain two tasks together. The second task will only run if the first one succeeds.
|
|
|
|
-}
|
|
|
|
andThen : TaskChain u b c -> TaskChain u a b -> TaskChain u a c
|
|
|
|
andThen f2 f1 =
|
2023-03-13 12:50:41 +00:00
|
|
|
\context ->
|
2023-03-10 20:34:25 +00:00
|
|
|
f1 context
|
2023-03-13 12:50:41 +00:00
|
|
|
|> Task.andThen
|
|
|
|
(\(TaskChainPiece old) ->
|
|
|
|
context
|
|
|
|
|> old.contextChange
|
|
|
|
|> f2
|
|
|
|
|> Task.map
|
|
|
|
(\(TaskChainPiece new) ->
|
|
|
|
TaskChainPiece
|
|
|
|
{ contextChange = old.contextChange >> new.contextChange
|
|
|
|
, messages = List.append old.messages new.messages
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
{-| Optionally run a task that may provide additional information.
|
2023-03-10 20:34:25 +00:00
|
|
|
|
2023-03-12 13:53:56 +00:00
|
|
|
If the provided chain fails, it will be ignored. This way, the chain can be tasked
|
|
|
|
without needlessly breaking the whole chain if anything breaks in here.
|
2023-03-10 20:34:25 +00:00
|
|
|
|
2023-03-12 13:53:56 +00:00
|
|
|
You cannot use this function to execute a task chain that adds or removes context.
|
2023-03-13 12:50:41 +00:00
|
|
|
|
2023-03-10 20:34:25 +00:00
|
|
|
-}
|
2023-03-12 13:53:56 +00:00
|
|
|
maybe : IdemChain u a -> IdemChain u a
|
2023-03-10 20:34:25 +00:00
|
|
|
maybe f =
|
2023-03-13 12:50:41 +00:00
|
|
|
{ contextChange = identity
|
|
|
|
, messages = []
|
|
|
|
}
|
|
|
|
|> TaskChainPiece
|
|
|
|
|> Task.succeed
|
|
|
|
|> always
|
|
|
|
|> Task.onError
|
|
|
|
|> (>>) f
|
|
|
|
|
2023-03-10 20:34:25 +00:00
|
|
|
|
|
|
|
{-| If the TaskChain fails, run this task otherwise.
|
|
|
|
-}
|
|
|
|
otherwise : TaskChain u a b -> TaskChain u a b -> TaskChain u a b
|
|
|
|
otherwise f2 f1 context =
|
|
|
|
Task.onError (always <| f2 context) (f1 context)
|
|
|
|
|
2023-03-13 12:50:41 +00:00
|
|
|
|
2023-03-10 20:34:25 +00:00
|
|
|
{-| Once all the pieces of the chain have been assembled, you can turn it into a task.
|
|
|
|
|
|
|
|
The compiler will fail if the chain is missing a vital piece of information.
|
2023-03-13 12:50:41 +00:00
|
|
|
|
2023-03-10 20:34:25 +00:00
|
|
|
-}
|
|
|
|
toTask : TaskChain u {} b -> Task X.Error (List u)
|
|
|
|
toTask f1 =
|
|
|
|
Context.init
|
2023-03-13 12:50:41 +00:00
|
|
|
|> f1
|
|
|
|
|> Task.map
|
|
|
|
(\(TaskChainPiece data) ->
|
|
|
|
data.messages
|
|
|
|
)
|
2023-03-10 20:34:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
{-| If the TaskChain fails, this function will get it to retry.
|
|
|
|
|
|
|
|
When set to 1 or lower, the task will only try once.
|
2023-03-13 12:50:41 +00:00
|
|
|
|
2023-03-10 20:34:25 +00:00
|
|
|
-}
|
|
|
|
tryNTimes : Int -> TaskChain u a b -> TaskChain u a b
|
|
|
|
tryNTimes n f context =
|
|
|
|
if n <= 1 then
|
|
|
|
f context
|
2023-03-13 12:50:41 +00:00
|
|
|
|
2023-03-10 20:34:25 +00:00
|
|
|
else
|
|
|
|
(\_ -> tryNTimes (n - 1) f context)
|
2023-03-13 12:50:41 +00:00
|
|
|
|> Task.onError
|
|
|
|
|> (|>) (f context)
|