Skip to content
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

Custom error type #683

Open
asheshambasta opened this issue Dec 21, 2018 · 6 comments
Open

Custom error type #683

asheshambasta opened this issue Dec 21, 2018 · 6 comments

Comments

@asheshambasta
Copy link

asheshambasta commented Dec 21, 2018

We're using Haskell to write one of our services, and we're using Aeson for input-json parsing.

However, the API of the platform as a whole must expose standardised validation errors like my example:

{
  "errors": [
    {
      "code": "ERR.SOMEFIELD.INVALID",
      "field": "foo",
      "type": "field",
      "message": "Expecting an 'int' but got 'string'"
    },
    {
      "code": "ERR.PLAN.INVALID_NAME",
      "type": "field",
      "field" : "user.plan", 
      "message": "All plan names must start with a 'urn:plan'" // nullable 
    }
  ],
  "service": "SomeServiceIdentifier/version"
}

I see that in Aeson this is not possible, since the Error data constructor only uses String for its output errors. Also, my understanding is that accumulating JSON errors is achieved using (<*>+) which is quite cool.

What I'd like to understand is how we achieve some sort of a custom error type that can be represented like the example above, if possible at all. The naive approach would be to use fail with some sort of standardized string and then parse the custom error fields out of it, but that is rather unsatisfactory.

Is there a way to achieve this?

Edit: I'd like to contribute to this, but I'm quite new to Haskell.

@phadej
Copy link
Collaborator

phadej commented Dec 21, 2018

The best way forward is to implement your own Value -> Either YourError a combinators, and see if you can reuse anything from aeson. I suspect there's not much, except the raw instances for primitive types like Int or UTCTime, but I you have to "lift" their instances too (from String to YourError).


I'm personally not against of having more structured Error type in aeson, but there should be a good&extensive experiment done; so we can get all various separate cases included in the Error type from the beginning.

@kuribas
Copy link

kuribas commented Dec 22, 2018

Could it be possible to do make a more expressive data type ResultE, then Result a type synonym:

data ResultE e a =
  Error e |
  Success a

type Result = ResultE String

data AesonError =
  KeyMissing Text |
  ...
data Pointer = ...
-- like in http://hackage.haskell.org/package/aeson-diff-1.1.0.5/docs/Data-Aeson-Pointer.html

type DescrResult = ResultE [(Pointer, AesonError)]

Then write core functions in terms of DescrResult, convert the existing functions to ResultE String.

That should be mostly backwards compatible, and provide better errors.

@asheshambasta
Copy link
Author

@phadej Okay, that was the direction I was taking as well. I do understand our use case is quite specific. The way our API is designed means that all client input/output is done via JSON and we can have several clients (Web, Mobile), so having some structured errors becomes necessary.

However, it seems to me that there is some dissatisfaction about error reporting about Aeson, and I'd be happy to contribute to that in any way. I do thinking having some custom error type, left to the discretion of the user, would be a good way forward.

@kuribas Yes, something like that should work, I'm going to try and plug that in. What it still doesn't solve (correct me if I'm wrong) is accumulative errors. Our API clients prefer to have the backend throw all the errors at them so they can correct their inputs accordingly, in one go.

@kuribas
Copy link

kuribas commented Dec 22, 2018

@asheshambasta that's not possible with a monad instance. And the applicative <*> should behave the same as app. However you can write your own combinator works the same as applicative, except that it collects errors.

@kuribas
Copy link

kuribas commented Dec 22, 2018

Or you could make a newtype CollectErrors which has only an applicative. Not sure that buys us much. We could also add an option to make the generic and template haskell code collect errors by default.

@bergmark
Copy link
Collaborator

bergmark commented Jan 4, 2019

There was some work to do error accumulation in #597 and #600 already

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants