cdepillabout / servant-checked-exceptions

type-level errors for Servant APIs.
https://hackage.haskell.org/package/servant-checked-exceptions
BSD 3-Clause "New" or "Revised" License
73 stars 14 forks source link

add note about having to be careful with letting aeson derive FromJSON and ToJSON instances #14

Open cdepillabout opened 7 years ago

cdepillabout commented 7 years ago

By default, Aeson may derive instances for error types that cannot be differentiated from one another.

For example, given the following code:

data FooErr = FooErr deriving (Eq, Read, Show)

$(deriveJSON defaultOptions ''FooErr)

data BarErr = BarErr deriving (Eq, Read, Show)

$(deriveJSON defaultOptions ''BarErr)

deriveJSON will derive instances that work like the following:

> encode (toErrEnvelope FooErr :: Envelope '[FooErr] Int)
"{\"err\":[]}"
> encode (toErrEnvelope BarErr :: Envelope '[BarErr] Int)
"{\"err\":[]}"

Just by looking at the output JSON, it is not possible to tell whether the error was originally a FooErr or a BarErr.

It is necessary to write the ToJSON and FromJSON instances by hand like the following:

data FooErr = FooErr deriving (Eq, Read, Show)

instance FromJSON FooErr where
  parseJSON = withText "FooErr" $ \case
    "FooErr" -> pure FooErr
    other ->
      fail $ "Trying to parse FooErr, but got \"" <> unpack other <> "\""

instance ToJSON FooErr where { toJSON _ = String "FooErr" }

data BarErr = BarErr deriving (Eq, Read, Show)

instance FromJSON BarErr where
  parseJSON = withText "BarErr" $ \case
    "BarErr" -> pure BarErr
    other ->
      fail $ "Trying to parse BarErr, but got \"" <> unpack other <> "\""

instance ToJSON BarErr where { toJSON _ = String "BarErr" }

This lets the FromJSON and ToJSON instance for Envelope work correctly:

> encode (toErrEnvelope FooErr  :: Envelope '[FooErr ] Int)
"{\"err\":\"FooErr\"}"
> encode (toErrEnvelope BarErr  :: Envelope '[BarErr ] Int)
"{\"err\":\"BarErr\"}"

It would be nice to add a note warning about this somewhere in this package. Probably on the ToJSON and FromJSON instances for Envelope.