Created
January 28, 2020 12:50
-
-
Save Herteby/020256ce5f4dd694b77b13d753f1c1e0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Api exposing | |
( Data | |
, Error(..) | |
, ErrorResponse | |
, Origin | |
, delete | |
, deleteTask | |
, errorToNotification | |
, errorView | |
, get | |
, getTask | |
, grid | |
, handleResult | |
, patch | |
, patchTask | |
, post | |
, postTask | |
, toResult | |
, view | |
) | |
import FontAwesome.Solid as Solid | |
import Html exposing (..) | |
import Html.Attributes as Attributes | |
import Http exposing (Metadata) | |
import Json.Decode as Decode exposing (Decoder) | |
import Json.Decode.Extra as Decode | |
import RemoteData exposing (RemoteData(..)) | |
import Task exposing (Task) | |
import UI exposing (..) | |
import UI.Blocker as Blocker | |
import UI.Icon as Icon | |
import UI.Notifications as Notifications | |
import UI.Spinner as Spinner | |
import UrlBuilderPlus as UB exposing (QueryParameter) | |
class = | |
namespace "Api" | |
type alias Data a = | |
RemoteData Error a | |
type Error | |
= BadUrl String | |
| Timeout | |
| NetworkError | |
| BadStatus Metadata ErrorResponse | |
| JsonError Metadata String | |
{-| Custom error response unique to FörsäkringsGirot | |
-} | |
type alias ErrorResponse = | |
{ error : String | |
, exception : Maybe String | |
, message : String | |
, path : String | |
, status : Int | |
} | |
{-| Represents the address of a server, for example api-gateway | |
-} | |
type alias Origin = | |
String | |
{-| Display HTML which depends on Remote Data | |
Displays a loading spinner while waiting, and an error message in case of failure. | |
-} | |
view : Data (Html msg) -> Html msg | |
view stuff = | |
case stuff of | |
NotAsked -> | |
textSpan "" | |
Loading -> | |
Spinner.view | |
Failure error -> | |
errorView error | |
Success content -> | |
content | |
{-| Display a grid where rows can depend on Remote Data | |
Displays a loading spinner in the second column while waiting, and an error message in case of failure. | |
-} | |
grid : List (Data (List (List (Html msg)))) -> Html msg | |
grid = | |
List.concatMap | |
(\data -> | |
case data of | |
NotAsked -> | |
[] | |
Loading -> | |
[ [ none, Spinner.view ] ] | |
Failure err -> | |
[ [ none, errorView err ] ] | |
Success html -> | |
html | |
) | |
>> UI.grid | |
{-| Display info about an HTTP error | |
-} | |
errorView : Error -> Html msg | |
errorView error = | |
let | |
( header, details ) = | |
case error of | |
BadUrl message -> | |
( "Felaktig URL", Just message ) | |
Timeout -> | |
( "Servern svarar inte", Nothing ) | |
NetworkError -> | |
( "Nätverksfel", Nothing ) | |
BadStatus metadata errorResponse -> | |
( String.fromInt metadata.statusCode ++ " " ++ metadata.statusText | |
, Just (metadata.url ++ "\n\n" ++ errorResponse.message) | |
) | |
JsonError metadata message -> | |
( "Servern svarade med oväntad JSON" | |
, Just (metadata.url ++ "\n\n" ++ message) | |
) | |
in | |
div [ class "error" ] | |
[ heading [] [ text "Ett fel inträffade" ] | |
, b [] [ text header ] | |
, textSpan "Prova att ladda om sidan, eller kontakta kundservice om felet kvarstår." | |
, row10 | |
[ a [ Attributes.href "tel:08-522 529 00" ] [ Icon.view [] Solid.phone, text "08-522 529 00" ] | |
, a [ Attributes.href "mailto:[email protected]" ] [ Icon.view [] Solid.envelope, text "[email protected]" ] | |
] | |
, case details of | |
Just str -> | |
div [ class "errorDetails", Attributes.tabindex 0 ] | |
[ div [ class "showDetailsButton" ] [ text "Teknisk info" ] | |
, div [ class "technical" ] [ text str ] | |
] | |
Nothing -> | |
none | |
] | |
{-| Update a model based on a result. If the result is an Error, display an error notification. | |
-} | |
handleResult : | |
{ r | notifications : Notifications.Model, blocker : Blocker.Status } | |
-> Result Error data | |
-> (data -> ( { r | notifications : Notifications.Model, blocker : Blocker.Status }, Cmd msg )) | |
-> ( { r | notifications : Notifications.Model, blocker : Blocker.Status }, Cmd msg ) | |
handleResult model result onSuccess = | |
case result of | |
Ok data -> | |
onSuccess data |> Tuple.mapFirst (\m -> { m | blocker = Blocker.decrement m.blocker }) | |
Err error -> | |
( { model | |
| notifications = model.notifications |> Notifications.insert (errorToNotification error) | |
, blocker = Blocker.decrement model.blocker | |
} | |
, Cmd.none | |
) | |
{-| Convert an Error to a Notification | |
-} | |
errorToNotification : Error -> Notifications.Notification | |
errorToNotification error = | |
Notifications.Error <| | |
case error of | |
BadUrl message -> | |
[ message ] | |
Timeout -> | |
[ "Servern svarar inte" ] | |
NetworkError -> | |
[ "Nätverksfel" ] | |
BadStatus metadata errorResponse -> | |
[ metadata.statusText | |
, errorResponse.message | |
, metadata.url | |
] | |
JsonError metadata message -> | |
[ message | |
, metadata.url | |
] | |
toResult : Data a -> Result Error a | |
toResult data = | |
case data of | |
NotAsked -> | |
Err NetworkError | |
Loading -> | |
Err NetworkError | |
Failure e -> | |
Err e | |
Success a -> | |
Ok a | |
-- MAKE REQUESTS | |
get : { path : List String, query : List QueryParameter, decoder : Decoder a } -> (Data a -> msg) -> Origin -> Cmd msg | |
get args msg origin = | |
getTask args origin |> Task.attempt (RemoteData.fromResult >> msg) | |
post : { path : List String, query : List QueryParameter, body : Http.Body, decoder : Decoder a } -> (Result Error a -> msg) -> Origin -> Cmd msg | |
post args msg origin = | |
postTask args origin |> Task.attempt msg | |
patch : { path : List String, query : List QueryParameter, body : Http.Body, decoder : Decoder a } -> (Result Error a -> msg) -> Origin -> Cmd msg | |
patch args msg origin = | |
patchTask args origin |> Task.attempt msg | |
delete : { path : List String, query : List QueryParameter, body : Http.Body, decoder : Decoder a } -> (Result Error a -> msg) -> Origin -> Cmd msg | |
delete args msg origin = | |
deleteTask args origin |> Task.attempt msg | |
getTask : { path : List String, query : List QueryParameter, decoder : Decoder a } -> Origin -> Task Error a | |
getTask { path, query, decoder } = | |
task "GET" { path = path, query = query, decoder = decoder, body = Http.emptyBody } | |
postTask : { path : List String, query : List QueryParameter, body : Http.Body, decoder : Decoder a } -> Origin -> Task Error a | |
postTask = | |
task "POST" | |
patchTask : { path : List String, query : List QueryParameter, body : Http.Body, decoder : Decoder a } -> Origin -> Task Error a | |
patchTask = | |
task "PATCH" | |
deleteTask : { path : List String, query : List QueryParameter, body : Http.Body, decoder : Decoder a } -> Origin -> Task Error a | |
deleteTask = | |
task "DELETE" | |
-- INTERNAL STUFF | |
task : String -> { path : List String, query : List QueryParameter, body : Http.Body, decoder : Decoder a } -> Origin -> Task Error a | |
task method { path, query, body, decoder } origin = | |
Http.riskyTask | |
{ method = method | |
, url = UB.crossOrigin origin ("app" :: path) query | |
, body = body | |
, headers = [] | |
, resolver = Http.stringResolver (toJsonResult decoder) | |
, timeout = Nothing | |
} | |
toJsonResult : Decoder a -> Http.Response String -> Result Error a | |
toJsonResult decoder response = | |
case response of | |
Http.BadUrl_ badUrl -> | |
Err (BadUrl badUrl) | |
Http.Timeout_ -> | |
Err Timeout | |
Http.NetworkError_ -> | |
Err NetworkError | |
Http.BadStatus_ metadata body -> | |
Err | |
(case Decode.decodeString errorResponseDecoder body of | |
Ok error -> | |
BadStatus metadata error | |
Err decodingError -> | |
JsonError metadata (Decode.errorToString decodingError) | |
) | |
Http.GoodStatus_ metadata body -> | |
let | |
-- replace empty body with "{}" so that Decode.succeed () doesn't fail | |
body_ = | |
if body == "" then | |
"{}" | |
else | |
body | |
in | |
case Decode.decodeString decoder body_ of | |
Ok value -> | |
Ok value | |
Err err -> | |
Err (JsonError metadata (Decode.errorToString err)) | |
errorResponseDecoder : Decoder ErrorResponse | |
errorResponseDecoder = | |
Decode.map5 ErrorResponse | |
(Decode.field "error" Decode.string) | |
(Decode.maybe (Decode.field "exception" Decode.string)) | |
(Decode.field "message" | |
(Decode.oneOf | |
[ Decode.doubleEncoded (Decode.field "error" Decode.string) | |
, Decode.string | |
] | |
) | |
) | |
(Decode.field "path" Decode.string) | |
(Decode.field "status" Decode.int) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment