1
1
mirror of https://github.com/aelve/guide.git synced 2024-11-27 18:12:44 +03:00

Introduce EndpointError (#202)

to improve error handling
This commit is contained in:
Jens Krause 2017-09-09 20:37:00 +02:00
parent ac6ad91d34
commit bb510a9f1d
No known key found for this signature in database
GPG Key ID: 3B2FAFBCEFA5906D
6 changed files with 61 additions and 32 deletions

View File

@ -8,7 +8,7 @@ import Data.Maybe (Maybe(..))
import Guide.Api.Types (CCategoryDetail, CUid)
import Guide.CategoryDetail.Routes (Route(..))
import Guide.CategoryDetail.State (State(..))
import Guide.Common.Api (ApiError, getCategory)
import Guide.Common.Api (EndpointError, getCategory)
import Guide.Common.Types (AppEffects, CategoryName)
import Network.RemoteData (RemoteData(..), isNotAsked)
import Pux (EffModel, noEffects)
@ -19,7 +19,7 @@ data Event
| RequestCategory CategoryName (CUid String)
-- TODO: ^ Use `CUid Category` instead of `CUid String` as second type parameter
-- if we have found a way to bridge `Uid a` properly from `Haskell` to `PS`
| ReceiveCategory (Either ApiError CCategoryDetail)
| ReceiveCategory (Either EndpointError CCategoryDetail)
foldp :: ∀ fx. Event -> State -> EffModel State Event (AppEffects fx)

View File

@ -5,14 +5,14 @@ import Data.Newtype (class Newtype)
import Data.Show (class Show)
import Guide.Api.Types (CCategoryDetail)
import Guide.CategoryDetail.Routes (Route, match)
import Guide.Common.Api (ApiError)
import Guide.Common.Api (EndpointError)
import Network.RemoteData (RemoteData(..))
newtype State = State
{ title :: String
, route :: Route
, errors :: Array String
, category :: RemoteData ApiError CCategoryDetail
, category :: RemoteData EndpointError CCategoryDetail
, loaded :: Boolean
}
@ -25,7 +25,7 @@ init :: String -> State
init url = State
{ title: "CategoryDetail page" -- TODO (sectore): Change title
, route: match url
, errors: []
, errors: []
, category: NotAsked
, loaded: false
}

View File

@ -5,9 +5,9 @@ import Prelude
import Data.Array ((:))
import Data.Either (Either(..))
import Data.Maybe (Maybe(..))
import Guide.Common.Api (ApiError, getCategories)
import Guide.CategoryOverview.Routes (Route(..))
import Guide.CategoryOverview.State (State(..))
import Guide.Common.Api (EndpointError, getCategories)
import Guide.Common.Types (AppEffects, CategoryName, CCategories)
import Network.RemoteData (RemoteData(..), isNotAsked)
import Pux (EffModel, noEffects)
@ -16,13 +16,13 @@ data Event =
PageView Route
-- API
| GetCategories CategoryName
| ReceiveCategories (Either ApiError CCategories)
| ReceiveCategories (Either EndpointError CCategories)
foldp :: forall eff. Event -> State -> EffModel State Event (AppEffects eff)
foldp (GetCategories catName) (State st) =
{ state: State $ st { categories = Loading
}
foldp (GetCategories catName) (State state) =
{ state: State $ state { categories = Loading
}
, effects:
[ getCategories catName >>= pure <<< Just <<< ReceiveCategories
]

View File

@ -3,8 +3,8 @@ module Guide.CategoryOverview.State where
import Data.Generic (class Generic, gShow)
import Data.Newtype (class Newtype)
import Data.Show (class Show)
import Guide.Common.Api (ApiError)
import Guide.CategoryOverview.Routes (Route, match)
import Guide.Common.Api (EndpointError)
import Guide.Common.Types (CategoryName(..), CCategories)
import Network.RemoteData (RemoteData(..))
@ -13,7 +13,7 @@ newtype State = State
, route :: Route
, loaded :: Boolean
, errors :: Array String
, categories :: RemoteData ApiError CCategories
, categories :: RemoteData EndpointError CCategories
, categoryName :: CategoryName
}

View File

@ -12,7 +12,7 @@ import Guide.Common.Types (CCategories)
import Network.RemoteData (RemoteData(..))
import Pux.DOM.HTML (HTML) as P
import Pux.DOM.HTML.Attributes (key) as P
import Text.Smolder.HTML (div, h1, h2, a, ul, li) as S
import Text.Smolder.HTML (div, h1, a, ul, li) as S
import Text.Smolder.HTML.Attributes (href) as S
import Text.Smolder.Markup ((!))
import Text.Smolder.Markup (text) as S

View File

@ -3,12 +3,13 @@ module Guide.Common.Api where
import Prelude
import Control.Monad.Aff (Aff)
import Data.Argonaut.Generic.Aeson (options)
import Data.Argonaut.Generic.Decode (genericDecodeJson)
import Data.Argonaut.Generic.Aeson (userDecoding, userEncoding)
import Data.Argonaut.Generic.Decode (Options(..), SumEncoding(..), genericDecodeJson)
import Data.Argonaut.Generic.Util (stripModulePath)
import Data.Bifunctor (bimap)
import Data.Either (Either(..), either)
import Data.Foreign (Foreign, unsafeFromForeign)
import Data.Generic (class Generic, gShow)
import Data.Newtype (class Newtype)
import Data.Generic (class Generic)
import Guide.Api.Types (CCategoryDetail, CUid(..))
import Guide.Common.Types (CCategories, CategoryName)
import IsomorphicFetch (FETCH, get, json)
@ -16,29 +17,57 @@ import IsomorphicFetch (FETCH, get, json)
endpoint :: String
endpoint = "http://localhost:4400"
-- TODO (sectore): Provide more API errors
-- such as
-- data ApiError
-- = StatusError String
-- | JSONError String
-- | ServerError B.ApiError
newtype ApiError = ApiError String
derive instance gApiError :: Generic ApiError
derive instance ntApiError :: Newtype ApiError _
instance sApiError :: Show ApiError where
show = gShow
data EndpointError
= JSONDecodingError String
| ServerError String
derive instance gEndpointError :: Generic EndpointError
instance showEndpointError :: Show EndpointError where
show (JSONDecodingError e) =
"[JSONDecodingError]: " <> show e
show (ServerError e) =
"[ServerError]: " <> show e
-- custom encode options (because `unpackRecords` should be `false`)
sumEncoding :: SumEncoding
sumEncoding = TaggedObject
{ tagFieldName: "tag"
, contentsFieldName: "contents"
, unpackRecords: false
}
options :: Options
options = Options
{ constructorTagModifier: stripModulePath
, allNullaryToStringTag: true
, sumEncoding
, flattenContentsArray: true
, encodeSingleConstructors: false
, userEncoding
, userDecoding
, fieldLabelModifier: id
, omitNothingFields: false
}
-- | Decoder for json data
decodeJson :: forall a. (Generic a) => Foreign -> Either String a
decodeJson = genericDecodeJson options <<< unsafeFromForeign
getCategories :: forall eff. CategoryName -> Aff (fetch :: FETCH | eff) (Either ApiError CCategories)
-- | Decodes a result considering JSON and Server errors
decodeResult :: forall a. Generic a => Foreign -> Either EndpointError a
decodeResult = either (Left <<< JSONDecodingError) (bimap ServerError id) <<< decodeJson
-- | Fetches all categories
getCategories :: forall eff. CategoryName -> Aff (fetch :: FETCH | eff) (Either EndpointError CCategories)
getCategories _ = do
response <- get $ endpoint <> "/categories"
json' <- json response
pure $ either (Left <<< ApiError) pure $ decodeJson json'
pure $ decodeResult json'
getCategory :: forall eff. CategoryName -> (CUid String) -> Aff (fetch :: FETCH | eff) (Either ApiError CCategoryDetail)
-- | Fetches a categories by a given category id
getCategory :: forall eff. CategoryName -> (CUid String) -> Aff (fetch :: FETCH | eff) (Either EndpointError CCategoryDetail)
getCategory _ (CUid catId) = do
response <- get $ endpoint <> "/category/" <> catId
json' <- json response
pure $ either (Left <<< ApiError) pure $ decodeJson json'
pure $ decodeResult json'