slack-web-1.6.0.0: src/Web/Slack/AesonUtils.hs
module Web.Slack.AesonUtils where
import Data.Aeson
import Data.Aeson qualified as J
import Data.Aeson.Types (Pair)
import Data.Char qualified as Char
import Data.Text qualified as T
import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds)
import Web.FormUrlEncoded qualified as F
import Web.Slack.Prelude
-- | Checks that a record's field labels each start with the given 'prefix',
-- then uses a given 'drop (length prefix)' derivingStrategy to drop that prefix from generated JSON.
--
-- If used in a Template Haskell splice, gives a compile-time error if the prefixes don't match up.
-- Warning: This function should not be used outside of a Template Haskell splice, as it calls `error` in the case that the prefixes don't match up!
--
-- Example usage:
--
-- data PrefixedRecord = PrefixedRecord { prefixedRecordOne :: Int, prefixedRecordTwo :: Char }
-- $(deriveFromJSON (jsonDeriveWithAffix "prefixedRecord" jsonDeriveOptionsSnakeCase) ''PrefixedRecord)
jsonDeriveWithAffix :: Text -> (Int -> Options) -> Options
jsonDeriveWithAffix prefix derivingStrategy =
originalOptions
{ fieldLabelModifier = \fieldLabel ->
if prefix `isPrefixOf` T.pack fieldLabel
then originalModifier fieldLabel
else error $ "Prefixes don't match: `" <> T.unpack prefix <> "` isn't a prefix of `" <> fieldLabel <> "`. Search for jsonDeriveWithAffix to learn more."
}
where
originalOptions = derivingStrategy $ T.length prefix
originalModifier = fieldLabelModifier originalOptions
camelToSnake :: String -> String
camelToSnake = camelTo2 '_'
lowerFirst :: String -> String
lowerFirst [] = []
lowerFirst (c : chars) = Char.toLower c : chars
jsonDeriveOptionsSnakeCase :: Int -> Options
jsonDeriveOptionsSnakeCase n =
defaultOptions
{ fieldLabelModifier = camelToSnake . lowerFirst . drop n
, omitNothingFields = True
, constructorTagModifier = camelToSnake . lowerFirst . drop n
}
-- | Create a 'Value' from a list of name\/value @Maybe Pair@'s.
-- For 'Nothing', instead of outputting @null@, that field will not be output at all.
-- If duplicate keys arise, later keys and their associated values win.
--
-- Example:
--
-- @
-- objectOptional
-- [ "always" .=! 1
-- , "just" .=? Just 2
-- , "nothing" .=? Nothing
-- ]
-- @
--
-- will result in the JSON
--
-- @
-- {
-- "always": 1,
-- "just": 2
-- }
-- @
--
-- The field @nothing@ is ommited because it was 'Nothing'.
objectOptional :: [Maybe Pair] -> Value
objectOptional = J.object . catMaybes
-- | Encode a value for 'objectOptional'
(.=!) :: ToJSON v => Key -> v -> Maybe Pair
key .=! val = Just (key .= val)
infixr 8 .=!
-- | Encode a Maybe value for 'objectOptional'
(.=?) :: ToJSON v => Key -> Maybe v -> Maybe Pair
key .=? mVal = fmap (key .=) mVal
infixr 8 .=?
-- | Conditionally encode a value for 'objectOptional'
(?.>) :: Bool -> Pair -> Maybe Pair
True ?.> pair = Just pair
False ?.> _ = Nothing
infixr 7 ?.>
-- | Conditionally express a pair in a JSON series
thenPair :: Bool -> J.Series -> J.Series
thenPair True s = s
thenPair False _ = mempty
infixr 7 `thenPair`
snakeCaseOptions :: Options
snakeCaseOptions =
defaultOptions
{ fieldLabelModifier = camelTo2 '_'
, constructorTagModifier = camelTo2 '_'
}
snakeCaseFormOptions :: F.FormOptions
snakeCaseFormOptions =
F.defaultFormOptions
{ F.fieldLabelModifier = camelTo2 '_'
}
newtype UnixTimestamp = UnixTimestamp {unUnixTimestamp :: UTCTime}
deriving newtype (Show, Eq)
instance FromJSON UnixTimestamp where
parseJSON a = UnixTimestamp . posixSecondsToUTCTime <$> parseJSON a
instance ToJSON UnixTimestamp where
toJSON (UnixTimestamp a) = toJSON (utcTimeToPOSIXSeconds a)