-
Notifications
You must be signed in to change notification settings - Fork 3
[WIP] TSD-85 - Attempt #2 with Either and minimal #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…nto mdimjasevic/tsd-85-http-exceptions
…nto mdimjasevic/tsd-85-http-exceptions
…nto mdimjasevic/tsd-85-http-exceptions
…nto mdimjasevic/tsd-85-http-exceptions
…nto mdimjasevic/tsd-85-http-exceptions
ksaric
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some fixes required.
| , transformers-base | ||
| , unliftio | ||
| -- | ||
| -- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally, it's best to leave unchanged lines so they don't confuse people.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not something I've done by hand. Rather, it was done automatically by a tool while I work on the project, I guess by the editor.
src/DataSource/Http.hs
Outdated
| import Data.Aeson (parseJSON) | ||
| import Data.Aeson.Text (encodeToLazyText) | ||
| import Data.List (nub) | ||
| import Data.Text (pack) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need pack, you can look at https://github.com/serokell/universum/blob/master/src/Universum/String/Conversion.hs
src/HttpLayer.hs
Outdated
| :: Config | ||
| -> Text | ||
| -> Either String Request | ||
| apiRequest Config{..} u = mapLeft show $ catches buildRequest handlerList |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need an Either here. All our URLs are "internal" and will fail if incorrect anyway.
I would otherwise consider this, but it seems to be difficult for you right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are multiple external functions that get called there and they can fail with an HttpException or with a JSONException. We shouldn't be hoping it will always be a happy case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where in apiRequest does it say that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a handlerList in the body of the function providing these two types of exceptions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. And how can those exceptions reach this handlerList?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm getting something wrong, but there is this function call: catches buildRequest handlerList. In case buildRequest results in an exception given in the handlerList, it gets handled by a corresponding handler.
| apiRequestAbsolute | ||
| :: Config | ||
| -> Text | ||
| -> Either String Request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean with "Same here"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, as in "think why you return the Either". Why do you return it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the implementation of this function. Now it is almost the same as the apiRequest function, hence it is clear why Either in the return type.
| -- | Request PUT | ||
| addJsonBody :: forall a. ToJSON a => a -> Request -> Request | ||
| addJsonBody body req = setRequestBodyJSON body $ setRequestMethod "PUT" req | ||
| addJsonBody :: forall a. ToJSON a => a -> Request -> Either String Request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a general rule, if you just wrap Either with a single constructor (Right), you probably don't need it.
Can you rewrite this to catch any exceptions and in that case to return it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with your observation. However, an exception can occur and that's why there is a call to the catch function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And where do you catch it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's caught right there, on the first line of addJsonBody.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And where in the type signature does it say that this can fail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type signature contains the return type Either String Request. The String part of the Either sum type represents its left side, which by convention represents a failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mdimjasevic Think about what I'm asking here, don't just answer back - what place is that exception coming from? How do you know that this function needs to address this exception? What kind of signature is on this function, and does that tell you something about its behavior?
Think about exceptions - do they occur on pure functions? In which cases? A lot of things to think about, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these questions are easy to answer by peeking at the code. Functions from the HTTP library that are called in addJsonBody can throw exceptions, hence addJsonBody has a corresponding signature. But you must be right, these are great questions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Functions from the HTTP library
Which ones?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The addJsonBody function's signature follows from your requirement that the HTTPNetworkLayer.hnlAddJsonBody field should have a type hnlAddJsonBody :: forall a. (ToJSON a) => a -> Request -> Either String Request: https://iohk.myjetbrains.com/youtrack/issue/TSD-85#comment=93-26226
src/DataSource/Http.hs
Outdated
|
|
||
| let url = showURL $ TicketCommentsURL tId | ||
| let req = apiRequest cfg url | ||
| case req of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chain computation?
src/DataSource/Http.hs
Outdated
| let req = apiRequest cfg url | ||
|
|
||
| apiCall parseJSON req | ||
| case req of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chain computation?
src/DataSource/Http.hs
Outdated
| let req = addJsonBody responseTicket (apiRequest cfg url) | ||
| void $ apiCall (pure . encodeToLazyText) req | ||
| let req = apiRequest cfg url | ||
| case req of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chain computation?
src/DataSource/Http.hs
Outdated
| case nextPage of | ||
| Just nextUrl -> go page0 nextUrl | ||
| Nothing -> pure page0 | ||
| case req' of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chain computation?
| apiCall <- asksHTTPNetworkLayer hnlApiCall | ||
|
|
||
| Just <$> apiCall parseTicket req | ||
| case req of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chain computation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please explain what you mean with "Chain computation?"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Means, "use a Monad". You first need to address the other issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These computations are chained. If they were not, the code wouldn't compile.
Here we have the resulting monad m and the Either monad. They could be combined in a monad stack, but given that there is a single point where a failure could happen, a monad stack would be overkill. Don't you agree?
ksaric
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some small fixes.
src/HttpLayer.hs
Outdated
| :: Config | ||
| -> Text | ||
| -> Either String Request | ||
| apiRequest Config{..} u = mapLeft show $ catches buildRequest handlerList |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where in apiRequest does it say that?
src/HttpLayer.hs
Outdated
|
|
||
| -- | The mapLeft function takes a function and applies it to an Either value | ||
| -- iff the value takes the form Left _. | ||
| mapLeft :: (a -> c) -> Either a b -> Either c b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In most cases, you don't need such functions, if you need to collapse either you use the either function, otherwise you can case match. Introducing functions for common operations just brings confusion.
| -- | Request PUT | ||
| addJsonBody :: forall a. ToJSON a => a -> Request -> Request | ||
| addJsonBody body req = setRequestBodyJSON body $ setRequestMethod "PUT" req | ||
| addJsonBody :: forall a. ToJSON a => a -> Request -> Either String Request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And where do you catch it?
src/DataSource/Http.hs
Outdated
| let req = apiRequest cfg url | ||
|
|
||
| apiCall parseJSON req | ||
| res <- runEitherT $ do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can simplify if you want to use transformers here and remove transformers-either:
res <- runExceptT $ do
req <- ExceptT . pure $ apiRequest cfg url
ExceptT $ apiCall parseJSON req
But I don't think we will require this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
res <- runExceptT $ (ExceptT . pure $ apiRequest cfg url) >>= ExceptT . apiCall parseJSON
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Putting it all into a single line doesn't seem like a good idea because it is harder to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I know. The reason it's there is because it has nice symmetry, the one that's relevant and tells you how you can get rid of transformers-either and where it says that we won't require this is one above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I'll make the change you suggested above.
ksaric
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Further explanations.
src/HttpLayer.hs
Outdated
| :: Config | ||
| -> Text | ||
| -> Either String Request | ||
| apiRequest Config{..} u = mapLeft show $ catches buildRequest handlerList |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. And how can those exceptions reach this handlerList?
src/HttpLayer.hs
Outdated
| case parseEither parser v of | ||
| Right o -> pure o | ||
| Right o -> pure $ Right o | ||
| Left e -> error $ "couldn't parse response " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the result of parseEither parser v?
| -- | Request PUT | ||
| addJsonBody :: forall a. ToJSON a => a -> Request -> Request | ||
| addJsonBody body req = setRequestBodyJSON body $ setRequestMethod "PUT" req | ||
| addJsonBody :: forall a. ToJSON a => a -> Request -> Either String Request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And where in the type signature does it say that this can fail?
ksaric
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixes.
| apiCall <- asksHTTPNetworkLayer hnlApiCall | ||
|
|
||
| Just <$> apiCall parseTicket req | ||
| case req of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Means, "use a Monad". You first need to address the other issues.
| -- | Request PUT | ||
| addJsonBody :: forall a. ToJSON a => a -> Request -> Request | ||
| addJsonBody body req = setRequestBodyJSON body $ setRequestMethod "PUT" req | ||
| addJsonBody :: forall a. ToJSON a => a -> Request -> Either String Request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mdimjasevic Think about what I'm asking here, don't just answer back - what place is that exception coming from? How do you know that this function needs to address this exception? What kind of signature is on this function, and does that tell you something about its behavior?
Think about exceptions - do they occur on pure functions? In which cases? A lot of things to think about, no?
| apiRequestAbsolute | ||
| :: Config | ||
| -> Text | ||
| -> Either String Request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, as in "think why you return the Either". Why do you return it?
src/HttpLayer.hs
Outdated
| case parseEither parser v of | ||
| Right o -> pure o | ||
| Right o -> pure $ Right o | ||
| Left e -> error $ "couldn't parse response " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like that matches the return type of the function (hint, hint) 😏
src/HttpLayer.hs
Outdated
| :: Config | ||
| -> Text | ||
| -> Either String Request | ||
| apiRequest Config{..} u = showErr $ catches buildRequest handlerList |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing as #50 (comment)
|
Closing in favor of #57 |
This implements error handling with an
Eitherin function return types to reflect the possibility of failure.