discord-haskell icon indicating copy to clipboard operation
discord-haskell copied to clipboard

Changed most ToJSON instances to use a new operator

Open L0neGamer opened this issue 3 years ago • 2 comments

This may help with readability.

I made some small other changes as well, mostly just linting hints.

L0neGamer avatar Jun 28 '22 21:06 L0neGamer

I took a look at it, and it might be better to just alias the type instead of introducing toKey. Since both Key.Key and T.Text have IsString instances, no changes to other parts of the code is necessary. Or maybe keep both toKey and the alias?

Here's a patch of what I mean (I didn't know how to submit a PR to a PR? or however it works):

diff --git discord-haskell.cabal discord-haskell.cabal
index d5fd220..a0e74dd 100644
--- discord-haskell.cabal
+++ discord-haskell.cabal
@@ -89,7 +89,7 @@ library
     , Discord.Internal.Types.ScheduledEvents
   build-depends:
     base ==4.*,
-    aeson >=1.5 && <1.6,
+    aeson >= 1.5 && < 1.6 || >= 2.0 && < 2.2,
     async >=2.2 && <2.3,
     bytestring >=0.10 && <0.11,
     base64-bytestring >=1.1 && <1.2,
diff --git src/Discord/Internal/Rest/Webhook.hs src/Discord/Internal/Rest/Webhook.hs
index ad4b419..7b4a545 100644
--- src/Discord/Internal/Rest/Webhook.hs
+++ src/Discord/Internal/Rest/Webhook.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE GADTs #-}
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE RecordWildCards #-}
@@ -25,18 +24,6 @@ import Network.HTTP.Client.MultipartFormData (partBS, partFileRequestBody)
 import Discord.Internal.Rest.Prelude
 import Discord.Internal.Types
 
--- aeson introduced type name for json key (text)
--- https://github.com/haskell/aeson/issues/881
--- this is unneeded due to version bounds.
--- # if MIN_VERSION_aeson(2, 0, 0)
--- import qualified Data.Aeson.Key as Key
--- toKey :: T.Text -> Key.Key
--- toKey = Key.fromText
--- # else
--- toKey :: T.Text -> T.Text
--- toKey = id
--- # endif
-
 instance Request (WebhookRequest a) where
   majorRoute = webhookMajorRoute
   jsonRequest = webhookJsonRequest
@@ -137,7 +124,7 @@ data WebhookContent = WebhookContentText T.Text
                     | WebhookContentEmbeds [CreateEmbed]
   deriving (Show, Read, Eq, Ord)
 
-webhookContentJson :: WebhookContent -> [(T.Text, Value)]
+webhookContentJson :: WebhookContent -> [(AesonKey, Value)]
 webhookContentJson c = case c of
                       WebhookContentText t -> [("content", toJSON t)]
                       WebhookContentFile _ _  -> []
diff --git src/Discord/Internal/Types/Prelude.hs src/Discord/Internal/Types/Prelude.hs
index 4f0b491..20c163e 100644
--- src/Discord/Internal/Types/Prelude.hs
+++ src/Discord/Internal/Types/Prelude.hs
@@ -3,6 +3,7 @@
 {-# LANGUAGE MultiParamTypeClasses  #-}
 {-# LANGUAGE FlexibleInstances  #-}
 {-# LANGUAGE RankNTypes  #-}
+{-# LANGUAGE CPP #-}
 
 -- | Provides base types and utility functions needed for modules in Discord.Internal.Types
 module Discord.Internal.Types.Prelude
@@ -45,6 +46,7 @@ module Discord.Internal.Types.Prelude
 
   , (.==)
   , (.=?)
+  , AesonKey
   , objectFromMaybes
   )
 
@@ -65,6 +67,10 @@ import Web.Internal.HttpApiData
 import qualified Data.ByteString as B
 import qualified Data.Text as T
 
+#if MIN_VERSION_aeson(2, 0, 0)
+import qualified Data.Aeson.Key as Key
+#endif
+
 -- | Authorization token for the Discord API
 newtype Auth = Auth T.Text
   deriving (Show, Read, Eq, Ord)
@@ -241,12 +247,21 @@ class Data a => InternalDiscordEnum a where
         | fromIntegral (round i) == i = Just $ round i
         | otherwise = Nothing
 
--- when we upgrade aeson, we need to change Text to Key
-
-(.==) :: ToJSON a => T.Text -> a -> Maybe Pair
+-- Aeson 2.0 uses KeyMaps with a defined Key type for its objects. Aeson up to
+-- 1.5 uses HashMaps with Text for the key. Both types have an IsString instance.
+-- To keep our version bounds as loose as possible while the Haskell ecosystem
+-- (and thus our users) switch over to Aeson 2.0, we use some CPP to define a
+-- AesonKey as an alias.
+#if MIN_VERSION_aeson(2, 0, 0)
+type AesonKey = Key.Key
+# else
+type AesonKey = T.Text
+# endif
+
+(.==) :: ToJSON a => AesonKey -> a -> Maybe Pair
 k .== v = Just (k .= v)
 
-(.=?) :: ToJSON a => T.Text -> Maybe a -> Maybe Pair
+(.=?) :: ToJSON a => AesonKey -> Maybe a -> Maybe Pair
 k .=? (Just v) = Just (k .= v)
 _ .=? Nothing = Nothing

yutotakano avatar Aug 15 '22 07:08 yutotakano

I think yutotakano's suggestion is the right call, as it allows potential support for both Aeson 1.x and 2.x with next to no change to user code (I think in most cases the only change needed would be to bump the Aeson version in cabal/stack/whatever dependency manager the user uses)

Annwan avatar Aug 15 '22 07:08 Annwan

You can submit a PR to my repo which can then get merged in here, but that seems messy; I'm gonna apply that patch soon.

L0neGamer avatar Aug 15 '22 09:08 L0neGamer

Looks great!

Goal is to get this and https://github.com/discord-haskell/discord-haskell/pull/135 into a 1.15 release

I have a long day ahead, should get around to the release process late tonight

aquarial avatar Aug 15 '22 13:08 aquarial