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

Allow aeson-2.2 #1695

Merged
merged 8 commits into from
Oct 2, 2023
Merged

Allow aeson-2.2 #1695

merged 8 commits into from
Oct 2, 2023

Conversation

maksbotan
Copy link
Contributor

No description provided.

@maksbotan maksbotan force-pushed the maksbotan/aeson-2.2 branch from 29658e5 to a408400 Compare July 11, 2023 11:31
@maksbotan
Copy link
Contributor Author

@theophile-fl @ysangkok @jkarni and co, I need your advice on this. Servant.API.ContentTypes uses Data.Aeson.Parser:

-- | Like 'Data.Aeson.eitherDecode' but allows all JSON values instead of just
-- objects and arrays.
--
-- 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")

Data.Aeson.Parser module was removed in aeson-2.2, so there is no easy way to upgrade our version constraint.

However, I think that this function is not needed anymore. Haddock mentions that it parser any json value, contrary to aeson's behavior to parse only list and object. See haddock for 'json' in aeson-2.1: https://hackage.haskell.org/package/aeson-2.1.2.1/docs/Data-Aeson-Parser.html#v:json

This function is an alias for value. In aeson 0.8 and earlier, it parsed only object or array types, in conformance with the now-obsolete RFC 4627.

Our lower bound on aeson is 1.4 for a long time, so I propose to remove our eitherDecodeLenient and use aeson's native eitherDecode instead. What do you think? I'm not sure if we have some tests to verify that the change is OK.

I will make the change in this PR a bit later.

@tchoutri
Copy link
Contributor

I support your proposition, in any case we will want to make sure that a pre-release is uploaded to Hackage so that users (me included) can test the behaviour. :)

@maksbotan
Copy link
Contributor Author

There was a test that servant's eitherDecodeLenient behaves exactly like eitherDecode from aeson:

-- aeson >= 0.9 decodes top-level strings
describe "eitherDecodeLenient" $ do
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)

I've pushed an update to check if everything works. I've also had to change the tutorial, as it also mentioned old aeson behavior.

@maksbotan maksbotan force-pushed the maksbotan/aeson-2.2 branch from 8b302f4 to 160bf12 Compare July 11, 2023 22:08
@maksbotan maksbotan force-pushed the maksbotan/aeson-2.2 branch from 160bf12 to c194e5c Compare July 12, 2023 07:48
@maksbotan
Copy link
Contributor Author

I've returned the test

@maksbotan
Copy link
Contributor Author

Do you want me to upload candidate to Hackage to test?

@tchoutri
Copy link
Contributor

That would be lovely yes

@maksbotan
Copy link
Contributor Author

@tchoutri
Copy link
Contributor

@maksbotan Wonderful. I think servant-server & servant-client-core are going to need a pre-release as well, I'm getting:


src/Servant/Server/Internal.hs:686:26: error:
    • Couldn't match type ‘a’ with ‘IO a0’
      Expected: SourceIO chunk -> a
        Actual: SourceIO chunk -> IO a0
      ‘a’ is a rigid type variable bound by
        the instance declaration
        at src/Servant/Server/Internal.hs:(673,5)-(675,68)
    • In the first argument of ‘return’, namely ‘fromSourceIO’
      In the expression: return fromSourceIO
      In an equation for ‘ctCheck’: ctCheck = return fromSourceIO

and

src/Servant/Client/Core/HasClient.hs:430:35: error:
    • Couldn't match type ‘a’ with ‘IO a1’
      Expected: Client m (Stream method status framing ct a)
        Actual: m (IO a1)
      ‘a’ is a rigid type variable bound by
        the instance declaration
        at src/Servant/Client/Core/HasClient.hs:(422,3)-(424,54)
    • In the expression:
        withStreamingRequest req'
          $ \ gres
              -> do let ...
                    return $ fromSourceIO $ framingUnrender' $ responseBody gres
      In an equation for ‘clientWithRoute’:
          clientWithRoute _pm Proxy req
            = withStreamingRequest req'
                $ \ gres
                    -> do let ...
                          ....
            where
                req'
                  = req
                      {requestAccept = fromList [...],
                       requestMethod = reflectMethod (Proxy :: Proxy method)}
      In the instance declaration for
        ‘HasClient m (Stream method status framing ct a)’
    • Relevant bindings include
        clientWithRoute :: Proxy m
                           -> Proxy (Stream method status framing ct a)
                           -> Request
                           -> Client m (Stream method status framing ct a)
          (bound at src/Servant/Client/Core/HasClient.hs:430:3)
    |
430 |   clientWithRoute _pm Proxy req = withStreamingRequest req' $ \gres -> do
    |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...

@tchoutri
Copy link
Contributor

I will also send a call for beta-testers on social media to make sure our users can take the time to test things on their own applications

@maksbotan
Copy link
Contributor Author

That should not be happening, looks like you get servant-server <0.20, which is incompatible with servant-0.20.*

@tchoutri
Copy link
Contributor

ok perfect it compiles here

@maksbotan
Copy link
Contributor Author

Okay, so let's wait for a bit for more tests, and I'll merge & release.

@tchoutri
Copy link
Contributor

@maksbotan servant-lucid would need a bound bump, I can do it for you if you give me permission on Hackage. :)

@ysangkok
Copy link
Contributor

@maksbotan Has this been sufficiently tested already?

@Vekhir
Copy link

Vekhir commented Sep 11, 2023

@maksbotan
Is there anything blocking this from getting merged?

Publishing this patch in a new version would allow the Arch package to be updated without carrying an additional patch.

@larskuhtz
Copy link

I would also love to see this merged and released.

(I think, as a web framework, servant should try to always support the latest version of aeson.)

Copy link

@larskuhtz larskuhtz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose to keep eitherDecodeLenient as an deprecated alias of eitherDecode, since both have (probably) the same behavior since version 0.9 of aeson. (I haven't checked possible differences is the handling of of trailing characters, but I expect that to be a negligible issue.)

This would reduce the potential of the PR to break users and will hopefully help this change to be release soon.

Comment on lines +6 to +7
branches:
- master

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems unrelated to this PR.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change seems unrelated to this PR.

@@ -65,7 +65,6 @@ module Servant.API.ContentTypes
, AllMime(..)
, AllMimeRender(..)
, AllMimeUnrender(..)
, eitherDecodeLenient

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the comments below it seems that with currently supported aeson versions eitherDecodeLenient and eitherDecode have the same behavior.

What about making eitherDecodeLenient an alias of eitherDecode and just mark it as deprecated? That way it would not break the API.

@@ -371,28 +364,9 @@ instance NFData NoContent
--------------------------------------------------------------------------
-- * MimeUnrender Instances

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-- | Deprecated: since aeson version 0.9 `eitherDecode` has lenient behavior.
--
eitherDecodeLenient :: FromJSON a => ByteString -> Either String a
eitherDecodeLenient = eitherDecode
{-# DEPRECATED eitherDecodeLenient "use eitherDecode instead" #-}

@@ -65,7 +65,6 @@ module Servant.API.ContentTypes
, AllMime(..)
, AllMimeRender(..)
, AllMimeUnrender(..)
, eitherDecodeLenient
, canHandleAcceptH

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep eitherDecodeLenient as alias of eitherDecde, but mark it as deprecated (cf. below).

Suggested change
, canHandleAcceptH
, eitherDecodeLenient
, canHandleAcceptH

@@ -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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
describe "eitherDecodeLenient" $ do
describe "eitherDecode is lenient" $ do

@TeofilC
Copy link
Contributor

TeofilC commented Sep 29, 2023

Let me know if you'd appreciate any help getting this over the line, but no pressure

@Vekhir
Copy link

Vekhir commented Sep 29, 2023

The CI fails because it tries to build warp-3.3.25 which is incompatible with http2-4.2.0. There is a newer version, warp-3.3.29 which is compatible, but not chosen.

@maksbotan
Copy link
Contributor Author

Thank you @Vekhir. I'll look into this.

@ysangkok
Copy link
Contributor

ysangkok commented Sep 29, 2023

@maksbotan :
You might want to remove or flip constraints: warp < 3.3.26 in cabal.project.

@maksbotan maksbotan force-pushed the maksbotan/aeson-2.2 branch from 92a6426 to 8a4a7f5 Compare October 1, 2023 13:07
@maksbotan
Copy link
Contributor Author

Due to backwards-incompatible changes to master since this PR (see #1697), I will have to make Hackage release from this branch.

@ysangkok @tchoutri @larskuhtz please check out this release candidate: https://hackage.haskell.org/package/servant-0.20.1/candidate

Direct tar gz link: https://hackage.haskell.org/package/servant-0.20.1/candidate/servant-0.20.1.tar.gz

If everything's fine, I'll publish a release.

@tchoutri
Copy link
Contributor

tchoutri commented Oct 1, 2023

Alright, except for a usage of eitherDecodeLenient and odd-jobs lagging a bit behind for aeson-2.2 compat, I haven't encountered any problem.

@maksbotan
Copy link
Contributor Author

What do you mean by "odd-jobs lagging"?

@tchoutri
Copy link
Contributor

tchoutri commented Oct 1, 2023

It still does not support aeson-2.2 saurabhnanda/odd-jobs#101

@maksbotan
Copy link
Contributor Author

Ah, didn't get that it's a name of a package :)

Great, I'll publish that release on Hackage.

@maksbotan
Copy link
Contributor Author

Published!

@maksbotan maksbotan merged commit 459ecef into master Oct 2, 2023
8 checks passed
@ysangkok ysangkok deleted the maksbotan/aeson-2.2 branch October 10, 2023 22:10
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

Successfully merging this pull request may close these issues.

6 participants