Skip to content

Commit

Permalink
Merge pull request #1695 from haskell-servant/maksbotan/aeson-2.2
Browse files Browse the repository at this point in the history
Allow aeson-2.2
  • Loading branch information
maksbotan authored Oct 2, 2023
2 parents a14cd7e + 8a4a7f5 commit 459ecef
Show file tree
Hide file tree
Showing 6 changed files with 23 additions and 51 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name: CI
on:
pull_request:
push:
branches:
- master

jobs:
cabal:
Expand All @@ -20,6 +22,7 @@ jobs:
- "9.2.8"
- "9.4.5"
- "9.6.2"
fail-fast: false

steps:
- uses: actions/checkout@v2
Expand Down
19 changes: 2 additions & 17 deletions doc/tutorial/Server.lhs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import System.Directory
import Text.Blaze
import Text.Blaze.Html.Renderer.Utf8
import Servant.Types.SourceT (source)
import qualified Data.Aeson.Parser
import qualified Text.Blaze.Html
```
Expand Down Expand Up @@ -431,25 +430,11 @@ class Accept ctype => MimeUnrender ctype a where
mimeUnrender :: Proxy ctype -> ByteString -> Either String a
```
We don't have much work to do there either, `Data.Aeson.eitherDecode` is
precisely what we need. However, it only allows arrays and objects as toplevel
JSON values and this has proven to get in our way more than help us so we wrote
our own little function around **aeson** and **attoparsec** that allows any type of
JSON value at the toplevel of a "JSON document". Here's the definition in case
you are curious.
``` haskell
eitherDecodeLenient :: FromJSON a => ByteString -> Either String a
eitherDecodeLenient input = do
v :: Value <- parseOnly (Data.Aeson.Parser.value <* endOfInput) (cs input)
parseEither parseJSON v
```
This function is exactly what we need for our `MimeUnrender` instance.
As with `MimeRender`, we can use a function already available in `aeson`: `Data.Aeson.eitherDecode`.
``` haskell ignore
instance FromJSON a => MimeUnrender JSON a where
mimeUnrender _ = eitherDecodeLenient
mimeUnrender _ = eitherDecode
```
And this is all the code that lets you use `JSON` with `ReqBody`, `Get`,
Expand Down
5 changes: 5 additions & 0 deletions servant/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

Package versions follow the [Package Versioning Policy](https://pvp.haskell.org/): in A.B.C, bumps to either A or B represent major versions.

0.20.1
----

- Support aeson-2.2 [#1695](https://github.com/haskell-servant/servant/pull/1695)

0.20
----

Expand Down
4 changes: 2 additions & 2 deletions servant/servant.cabal
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cabal-version: 2.2
name: servant
version: 0.20
version: 0.20.1

synopsis: A family of combinators for defining webservices APIs
category: Servant, Web
Expand Down Expand Up @@ -100,7 +100,7 @@ library
-- Here can be exceptions if we really need features from the newer versions.
build-depends:
base-compat >= 0.10.5 && < 0.14
, aeson >= 1.4.1.0 && < 2.2
, aeson >= 1.4.1.0 && < 2.3
, attoparsec >= 0.13.2.2 && < 0.15
, bifunctors >= 5.5.3 && < 5.7
, case-insensitive >= 1.2.0.11 && < 1.3
Expand Down
29 changes: 5 additions & 24 deletions servant/src/Servant/API/ContentTypes.hs
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,7 @@ import Control.Monad.Compat
import Control.DeepSeq
(NFData)
import Data.Aeson
(FromJSON (..), ToJSON (..), encode)
import Data.Aeson.Parser
(value)
import Data.Aeson.Types
(parseEither)
import Data.Attoparsec.ByteString.Char8
(endOfInput, parseOnly, skipSpace, (<?>))
(FromJSON (..), ToJSON (..), encode, eitherDecode)
import Data.Bifunctor
(bimap)
import qualified Data.ByteString as BS
Expand Down Expand Up @@ -371,28 +365,15 @@ instance NFData NoContent
--------------------------------------------------------------------------
-- * MimeUnrender Instances

-- | Like 'Data.Aeson.eitherDecode' but allows all JSON values instead of just
-- objects and arrays.
-- | Deprecated: since aeson version 0.9 `eitherDecode` has lenient behavior.
--
-- Will handle trailing whitespace, but not trailing junk. ie.
--
-- >>> eitherDecodeLenient "1 " :: Either String Int
-- Right 1
--
-- >>> eitherDecodeLenient "1 junk" :: Either String Int
-- Left "trailing junk after valid JSON: endOfInput"
eitherDecodeLenient :: FromJSON a => ByteString -> Either String a
eitherDecodeLenient input =
parseOnly parser (cs input) >>= parseEither parseJSON
where
parser = skipSpace
*> Data.Aeson.Parser.value
<* skipSpace
<* (endOfInput <?> "trailing junk after valid JSON")
eitherDecodeLenient = eitherDecode
{-# DEPRECATED eitherDecodeLenient "use eitherDecode instead" #-}

-- | `eitherDecode`
instance FromJSON a => MimeUnrender JSON a where
mimeUnrender _ = eitherDecodeLenient
mimeUnrender _ = eitherDecode

-- | @urlDecodeAsForm@
-- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only
Expand Down
14 changes: 6 additions & 8 deletions servant/test/Servant/API/ContentTypesSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Prelude ()
import Prelude.Compat

import Data.Aeson
(FromJSON, ToJSON (..), Value, decode, encode, object, (.=))
(FromJSON, ToJSON (..), Value, decode, encode, object, (.=), eitherDecode)
import Data.ByteString.Char8
(ByteString, append, pack)
import qualified Data.ByteString.Lazy as BSL
Expand Down Expand Up @@ -219,15 +219,13 @@ spec = describe "Servant.API.ContentTypes" $ do
handleCTypeH (Proxy :: Proxy '[JSONorText]) "image/jpeg"
"foobar" `shouldBe` (Nothing :: Maybe (Either String Int))

-- aeson >= 0.9 decodes top-level strings
describe "eitherDecodeLenient" $ do
describe "eitherDecode is lenient" $ do

-- Since servant-0.20.1 MimeUnrender JSON instance uses eitherDecode,
-- as aeson >= 0.9 supports decoding top-level strings and numbers.
it "parses top-level strings" $ do
let toMaybe = either (const Nothing) Just
-- The Left messages differ, so convert to Maybe
property $ \x -> toMaybe (eitherDecodeLenient x)
`shouldBe` (decode x :: Maybe String)

property $ \x -> mimeUnrender (Proxy :: Proxy JSON) x
`shouldBe` (eitherDecode x :: Either String String)

data SomeData = SomeData { record1 :: String, record2 :: Int }
deriving (Generic, Eq, Show)
Expand Down

0 comments on commit 459ecef

Please sign in to comment.