elm-matrix-sdk-alpha/src/Internal/Api/Chain.elm

130 lines
3.9 KiB
Elm
Raw Normal View History

2023-03-10 20:34:25 +00:00
module Internal.Api.Chain exposing (..)
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:50:23 +00:00
The model is like a snake: \_\_\_\_\_
/ o \\
/-|------------ | ------- | ------------- | -------- | |//
< | accessToken | baseUrl | transactionId | API call | |------< Final API call
-|------------ | ------- | ------------- | -------- | |//\\
-----/
(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-10 20:34:25 +00:00
-}
import Internal.Tools.Context as Context exposing (Context)
import Internal.Tools.Exceptions as X
2023-03-10 20:34:25 +00:00
import Task exposing (Task)
2023-03-10 20:34:25 +00:00
type alias TaskChain u a b =
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-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 =
\context ->
2023-03-10 20:34:25 +00:00
f1 context
|> 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-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 =
{ 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-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-10 20:34:25 +00:00
-}
toTask : TaskChain u {} b -> Task X.Error (List u)
toTask f1 =
Context.init
|> 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-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-10 20:34:25 +00:00
else
(\_ -> tryNTimes (n - 1) f context)
|> Task.onError
|> (|>) (f context)