154 lines
5.3 KiB
Elm
154 lines
5.3 KiB
Elm
module MassiveDecks.API.Request exposing (send, send', request, Request, KnownError, Error(..))
|
|
|
|
import Json.Encode exposing (encode)
|
|
import Json.Decode as Json
|
|
import Task
|
|
import Dict exposing (Dict)
|
|
|
|
import Http exposing (Value(..))
|
|
|
|
import MassiveDecks.Components.Errors as Errors
|
|
import MassiveDecks.Util as Util
|
|
|
|
|
|
{-| Send a request to the server.
|
|
-}
|
|
send : Request specificError result -> (specificError -> message) -> (Errors.Message -> message) -> (result -> message) -> Cmd message
|
|
send request knownHandler errorMessageWrapper resultToMessage =
|
|
let
|
|
errorToMessage = errorHandler knownHandler errorMessageWrapper
|
|
task = Http.send Http.defaultSettings
|
|
{ verb = request.verb
|
|
, url = request.url
|
|
, headers = if request.body == Nothing then [] else jsonContentType
|
|
, body = case request.body of
|
|
Just json -> jsonBody json
|
|
Nothing -> Http.empty
|
|
} |> Task.map (handleResponse request.errors request.resultDecoder)
|
|
errorMapped = (Task.mapError Communication task) `Task.andThen` Task.fromResult
|
|
in
|
|
Task.perform errorToMessage resultToMessage errorMapped
|
|
|
|
|
|
{-| Same as 'send', but for requests with no known errors.
|
|
-}
|
|
send' : Request Never result -> (Errors.Message -> message) -> (result -> message) -> Cmd message
|
|
send' request errorMessageWrapper resultToMessage = send request Util.impossible errorMessageWrapper resultToMessage
|
|
|
|
|
|
{-| A request to the API.
|
|
-}
|
|
type alias Request specificError result =
|
|
{ verb : String
|
|
, url : String
|
|
, body : Maybe Json.Value
|
|
, errors : KnownErrors specificError
|
|
, resultDecoder : Json.Decoder result
|
|
}
|
|
|
|
|
|
{-| A convinience method to make the errors dictionary from a list.
|
|
-}
|
|
request : String -> String -> Maybe Json.Value -> List (KnownError specificError) -> Json.Decoder result -> Request specificError result
|
|
request verb url body errors resultDecoder = Request verb url body (Dict.fromList errors) resultDecoder
|
|
|
|
|
|
{-| Specifies an error the client understands and can present to the user nicely.
|
|
-}
|
|
type alias KnownError specificError = ((Int, String), Json.Decoder specificError)
|
|
|
|
{-| The dictionary of KnownErrors.
|
|
-}
|
|
type alias KnownErrors specificError = Dict (Int, String) (Json.Decoder specificError)
|
|
|
|
|
|
{-| The top level of potential errors from an API request.
|
|
-}
|
|
type Error specificError
|
|
= Communication Http.RawError
|
|
| Malformed String
|
|
| Known specificError
|
|
| Unknown Int String
|
|
|
|
|
|
{-| Handle an error, turning it into a message.
|
|
-}
|
|
errorHandler : (specificErrors -> msg) -> (Errors.Message -> msg) -> Error specificErrors -> msg
|
|
errorHandler knownHandler errorMessageWrapper error =
|
|
case error of
|
|
Known specificError -> knownHandler specificError
|
|
_ -> genericErrorHandler error |> errorMessageWrapper
|
|
|
|
|
|
{-| Convert errors to generic error messages for display in the errors component. Generally you want to use this as a
|
|
fallback after handling all known errors.
|
|
-}
|
|
genericErrorHandler : Error a -> Errors.Message
|
|
genericErrorHandler error =
|
|
case error of
|
|
Known specificError -> Errors.New ("An error was not correctly handled: " ++ (toString specificError)) True
|
|
Communication Http.RawTimeout -> Errors.New "Timed out trying to connect to the server." False
|
|
Communication Http.RawNetworkError -> Errors.New "There was a network error trying to connect to the server." False
|
|
Malformed explanation -> Errors.New ("The response recieved from the server was incorrect: " ++ explanation) True
|
|
Unknown code response -> Errors.New ("Recieved an unexpected response (" ++ (toString code) ++ ") from the server: " ++ response) True
|
|
|
|
|
|
handleResponse : KnownErrors specificError -> Json.Decoder message -> Http.Response -> Result (Error specificError) message
|
|
handleResponse errors resultDecoder response =
|
|
if response.status >= 200 && response.status < 300 then
|
|
handleSuccess resultDecoder response
|
|
else
|
|
handleFailure errors response |> Err
|
|
|
|
|
|
handleSuccess : Json.Decoder message -> Http.Response -> Result (Error specificError) message
|
|
handleSuccess resultDecoder response =
|
|
case response.value of
|
|
Text value -> Result.formatError Malformed (Json.decodeString resultDecoder value)
|
|
Blob blob -> Err (Malformed "Recieved binary data instead of expected JSON.")
|
|
|
|
|
|
handleFailure : KnownErrors specificError -> Http.Response -> Error specificError
|
|
handleFailure errors response =
|
|
case response.value of
|
|
Text value ->
|
|
case Json.decodeString errorKeyDecoder value of
|
|
Ok errorName ->
|
|
let
|
|
decoder = Dict.get (response.status, errorName) errors
|
|
in
|
|
case decoder of
|
|
Just decoder ->
|
|
case Json.decodeString decoder value of
|
|
Ok error ->
|
|
Known error
|
|
|
|
Err error ->
|
|
Malformed error
|
|
|
|
Nothing ->
|
|
Unknown response.status value
|
|
|
|
Err error ->
|
|
Malformed error
|
|
|
|
Blob blob -> Malformed "Recieved binary data instead of expected JSON in error."
|
|
|
|
|
|
{-| Specifies JSON encoded content.
|
|
-}
|
|
jsonContentType : List (String, String)
|
|
jsonContentType = [ ("Content-Type", "application/json") ]
|
|
|
|
|
|
{-| Turns JSON into an HTTP body for a request.
|
|
-}
|
|
jsonBody : Json.Value -> Http.Body
|
|
jsonBody value = encode 0 value |> Http.string
|
|
|
|
|
|
{-| Decode the error key from
|
|
-}
|
|
errorKeyDecoder : Json.Decoder String
|
|
errorKeyDecoder = Json.at [ "error" ] Json.string
|