11 files changed,
122 insertions(+),
68 deletions(-)
Author:
Smirnov Oleksandr
ss2316544@gmail.com
Committed by:
GitHub
noreply@github.com
Committed at:
2025-06-20 16:57:47 +0300
Parent:
9de58ad
jump to
M
web/src/Api.elm
··· 1 -module Api exposing (HttpRequestDetails, Response(..), errorToFriendlyMessage) 1 +module Api exposing (Error(..), Response(..), errorMessage) 2 2 3 3 import Http 4 4 import Json.Decode 5 5 6 6 7 +type Error 8 + = HttpError 9 + { message : String 10 + , reason : Http.Error 11 + } 12 + | JsonDecodeError 13 + { message : String 14 + , reason : Json.Decode.Error 15 + } 16 + 17 + 7 18 type Response value 8 19 = Loading 9 20 | Success value 10 - | Failure Http.Error 21 + | Failure Error 11 22 12 23 13 -type alias HttpRequestDetails msg = 14 - { endpoint : String 15 - , method : String 16 - , body : Http.Body 17 - , decoder : Json.Decode.Decoder msg 18 - , onHttpError : Http.Error -> msg 19 - } 24 +errorMessage : Error -> String 25 +errorMessage error = 26 + case error of 27 + HttpError err -> 28 + err.message 20 29 21 - 22 -errorToFriendlyMessage : Http.Error -> String 23 -errorToFriendlyMessage httpError = 24 - case httpError of 25 - Http.BadUrl _ -> 26 - "This page requested a bad URL" 27 - 28 - Http.Timeout -> 29 - "Request took too long to respond" 30 - 31 - Http.NetworkError -> 32 - "Could not connect to the API" 33 - 34 - Http.BadStatus code -> 35 - case code of 36 - 404 -> 37 - "Not found" 38 - 39 - 401 -> 40 - "Unauthorized" 41 - 42 - _ -> 43 - "API returned an error code" 44 - 45 - Http.BadBody _ -> 46 - "Unexpected response from API" 30 + JsonDecodeError err -> 31 + err.message
M
web/src/Api/Auth.elm
··· 1 1 module Api.Auth exposing (refreshToken, signin, signup) 2 2 3 +import Api 3 4 import Data.Credentials as Credentials exposing (Credentials) 4 5 import Effect exposing (Effect) 5 6 import Http ··· 8 9 9 10 10 11 signin : 11 - { onResponse : Result Http.Error Credentials -> msg 12 + { onResponse : Result Api.Error Credentials -> msg 12 13 , email : String 13 14 , password : String 14 15 } ··· 32 33 33 34 34 35 signup : 35 - { onResponse : Result Http.Error () -> msg 36 + { onResponse : Result Api.Error () -> msg 36 37 , email : String 37 38 , password : String 38 39 } ··· 56 57 57 58 58 59 refreshToken : 59 - { onResponse : Result Http.Error Credentials -> msg 60 + { onResponse : Result Api.Error Credentials -> msg 60 61 , refreshToken : String 61 62 } 62 63 -> Effect msg
M
web/src/Api/Me.elm
··· 1 1 module Api.Me exposing (get) 2 2 3 +import Api 3 4 import Data.Me as Me exposing (Me) 4 5 import Effect exposing (Effect) 5 6 import Http 6 7 7 8 8 -get : { onResponse : Result Http.Error Me -> msg } -> Effect msg 9 +get : { onResponse : Result Api.Error Me -> msg } -> Effect msg 9 10 get options = 10 11 Effect.sendApiRequest 11 12 { endpoint = "/api/v1/me"
M
web/src/Effect.elm
··· 28 28 29 29 -} 30 30 31 -import Api exposing (HttpRequestDetails) 31 +import Api 32 32 import Auth.User 33 33 import Browser.Navigation 34 34 import Data.Credentials exposing (Credentials) 35 +import Data.Error 35 36 import Dict exposing (Dict) 36 37 import Http 37 38 import Json.Decode ··· 59 60 | SendSharedMsg Shared.Msg.Msg 60 61 | SendToLocalStorage { key : String, value : Json.Encode.Value } 61 62 | SendApiRequest (HttpRequestDetails msg) 63 + 64 + 65 +type alias HttpRequestDetails msg = 66 + { endpoint : String 67 + , method : String 68 + , body : Http.Body 69 + , decoder : Json.Decode.Decoder msg 70 + , onHttpError : Api.Error -> msg 71 + } 62 72 63 73 64 74 ··· 161 171 , method : String 162 172 , body : Http.Body 163 173 , decoder : Json.Decode.Decoder value 164 - , onResponse : Result Http.Error value -> msg 174 + , onResponse : Result Api.Error value -> msg 165 175 } 166 176 -> Effect msg 167 177 sendApiRequest opts = 168 178 let 169 - onHttpError : Http.Error -> msg 170 - onHttpError httpError = 171 - opts.onResponse (Err httpError) 179 + onSuccess : value -> msg 180 + onSuccess value = 181 + opts.onResponse (Ok value) 172 182 173 - decoder : Json.Decode.Decoder msg 174 - decoder = 175 - opts.decoder 176 - |> Json.Decode.map Ok 177 - |> Json.Decode.map opts.onResponse 183 + onHttpError : Api.Error -> msg 184 + onHttpError err = 185 + opts.onResponse (Err err) 178 186 in 179 187 SendApiRequest 180 188 { endpoint = opts.endpoint 181 189 , method = opts.method 182 190 , body = opts.body 183 191 , onHttpError = onHttpError 184 - , decoder = decoder 192 + , decoder = Json.Decode.map onSuccess opts.decoder 185 193 } 186 194 187 195 ··· 326 334 , headers = headers 327 335 , body = opts.body 328 336 , expect = 329 - Http.expectJson 337 + Http.expectStringResponse 330 338 (\httpResult -> 331 339 case httpResult of 332 340 Ok msg -> ··· 335 343 Err err -> 336 344 opts.onHttpError err 337 345 ) 338 - opts.decoder 346 + (\resp -> fromHttpResponseToCustomError opts.decoder resp) 339 347 , timeout = Just (1000 * 60) -- 60 second timeout 340 348 , tracker = Nothing 341 349 } 350 + 351 + 352 +fromHttpResponseToCustomError : Json.Decode.Decoder msg -> Http.Response String -> Result Api.Error msg 353 +fromHttpResponseToCustomError decoder response = 354 + case response of 355 + Http.GoodStatus_ _ body -> 356 + case Json.Decode.decodeString decoder body of 357 + Ok data -> 358 + Ok data 359 + 360 + Err err -> 361 + Err (Api.JsonDecodeError { message = "Something unexpected happened", reason = err }) 362 + 363 + Http.BadStatus_ { statusCode } body -> 364 + case Json.Decode.decodeString Data.Error.decode body of 365 + Ok err -> 366 + Err (Api.HttpError { message = err.message, reason = Http.BadStatus statusCode }) 367 + 368 + Err err -> 369 + Err (Api.JsonDecodeError { message = "Something unexpected happened", reason = err }) 370 + 371 + Http.BadUrl_ url -> 372 + Err (Api.HttpError { message = "Unexpected URL format", reason = Http.BadUrl url }) 373 + 374 + Http.Timeout_ -> 375 + Err (Api.HttpError { message = "Request timed out, please try again", reason = Http.Timeout }) 376 + 377 + Http.NetworkError_ -> 378 + Err (Api.HttpError { message = "Could not connect, please try again", reason = Http.NetworkError })
M
web/src/Pages/Auth.elm
··· 8 8 import Html exposing (Html) 9 9 import Html.Attributes as Attr 10 10 import Html.Events 11 -import Http 12 11 import Layouts 13 12 import Page exposing (Page) 14 13 import Route exposing (Route) ··· 38 37 , passwordAgain : String 39 38 , isSubmittingForm : Bool 40 39 , formVariant : Variant 41 - , error : Maybe Http.Error 40 + , error : Maybe Api.Error 42 41 } 43 42 44 43 ··· 71 70 = UserUpdatedInput Field String 72 71 | UserChangedFormVariant Variant 73 72 | UserClickedSubmit 74 - | ApiSignInResponded (Result Http.Error Credentials) 75 - | ApiSignUpResponded (Result Http.Error ()) 73 + | ApiSignInResponded (Result Api.Error Credentials) 74 + | ApiSignUpResponded (Result Api.Error ()) 76 75 77 76 78 77 type Field ··· 198 197 ) 199 198 200 199 201 -viewError : Maybe Http.Error -> Html Msg 200 +viewError : Maybe Api.Error -> Html Msg 202 201 viewError maybeError = 203 202 case maybeError of 204 203 Just error -> 205 204 Html.div [ Attr.class "box bad" ] 206 205 [ Html.strong [ Attr.class "block titlebar" ] [ Html.text "Error" ] 207 - , Html.text (Api.errorToFriendlyMessage error) 206 + , Html.text (Api.errorMessage error) 208 207 ] 209 208 210 209 Nothing -> ··· 230 229 viewForgotPassword = 231 230 Html.div [] 232 231 [ Html.a 233 - [ Attr.href "/forgot-password" 234 - , Attr.class "gray" 235 - ] 232 + [ Attr.href "/forgot-password" ] 236 233 [ Html.text "Forgot password?" ] 237 234 ] 238 235
M
web/src/Pages/Profile/Me.elm
··· 6 6 import Data.Me exposing (Me) 7 7 import Effect exposing (Effect) 8 8 import Html exposing (Html) 9 -import Http 10 9 import Layouts 11 10 import Page exposing (Page) 12 11 import Route exposing (Route) ··· 45 44 46 45 47 46 type Msg 48 - = ApiMeResponded (Result Http.Error Me) 47 + = ApiMeResponded (Result Api.Error Me) 49 48 50 49 51 50 update : Msg -> Model -> ( Model, Effect Msg ) ··· 88 87 viewUserDetails shared user 89 88 90 89 Api.Failure err -> 91 - Html.text (Api.errorToFriendlyMessage err) 90 + Html.text (Api.errorMessage err) 92 91 93 92 94 93 viewUserDetails : Shared.Model -> Me -> Html Msg
A
web/tests/UnitTests/Data/Error.elm
··· 1 +module UnitTests.Data.Error exposing (suite) 2 + 3 +import Data.Error 4 +import Expect 5 +import Json.Decode as Json 6 +import Test exposing (Test, describe, test) 7 + 8 + 9 +suite : Test 10 +suite = 11 + describe "Data.Error" 12 + [ test "decode" <| 13 + \_ -> 14 + """ 15 + { 16 + "message": "some kind of an error" 17 + } 18 + """ 19 + |> Json.decodeString Data.Error.decode 20 + |> Expect.equal (Ok { message = "some kind of an error" }) 21 + ]