pixiv-0.1.0: src/Web/Pixiv/TH.hs
{-# LANGUAGE TemplateHaskell #-}
-- | Copyright: (c) 2021 The closed eye of love
-- SPDX-License-Identifier: BSD-3-Clause
-- Maintainer: Poscat <poscat@mail.poscat.moe>, berberman <berberman@yandex.com>
-- Stability: alpha
-- Portability: portable
-- This module provides some TH functions to create instances
-- of 'FromJSON', 'ToJSON', and 'ToHttpApiData'.
-- You can find usages in "Web.Pixiv.Types" and "Web.Pixiv.Auth".
module Web.Pixiv.TH
( derivePixivJSON,
derivePixivJSON',
deriveEnumJSON,
deriveEnumJSON',
deriveEnumToHttpApiData,
deriveEnumToHttpApiData',
ToHttpApiData (..),
FromJSON (..),
ToJSON (..),
)
where
import Data.Aeson (FromJSON (..), ToJSON (..), camelTo2)
import Data.Aeson.TH
import Data.List (stripPrefix)
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import Language.Haskell.TH
import Servant.API (ToHttpApiData (..))
-----------------------------------------------------------------------------
-- | Creates instances of 'FromJSON' and 'ToJSON',
-- stripping @_@ and @prefix@ from field labels (making sure result is non-empty),
-- then converting into snake case.
derivePixivJSON :: String -> Name -> DecsQ
derivePixivJSON prefix =
deriveJSON
defaultOptions
{ fieldLabelModifier = modifyFieldName prefix
}
-- | Like 'derivePixivJSON' but does not strip prefix.
derivePixivJSON' :: Name -> DecsQ
derivePixivJSON' = derivePixivJSON ""
-----------------------------------------------------------------------------
-- | Creates instances of 'FromJSON' and 'ToJSON',
-- stripping @prefix@ from constructor tags then converting
-- into snake case.
deriveEnumJSON :: String -> Name -> DecsQ
deriveEnumJSON prefix =
deriveJSON
defaultOptions
{ constructorTagModifier = modifyConsturctorName prefix
}
-- | Like 'deriveEnumJSON' but does not strip prefix.
deriveEnumJSON' :: Name -> DecsQ
deriveEnumJSON' = deriveEnumJSON ""
-----------------------------------------------------------------------------
-- | Creates instance of 'ToHttpApiData' for a enum-like data type
-- which contains only plain normal constructors.
--
-- Constructor tags will be stripped @prefix@, then converted into snake case.
deriveEnumToHttpApiData :: String -> Name -> DecsQ
deriveEnumToHttpApiData prefix name =
reify name >>= \case
TyConI dec -> case dec of
(DataD _ _dataName [] _ cons _) -> do
let conNames = [n | (NormalC n []) <- cons]
if length cons /= length conNames
then error "Data type is not simply an enum"
else do
let clauses =
[ clause
[conP n []]
(normalB [|T.pack $ modifyConsturctorName prefix n'|])
[]
| n <- conNames,
let n' = nameBase n
]
func <- funD (mkName "toQueryParam") clauses
pure [InstanceD Nothing [] (ConT ''ToHttpApiData `AppT` ConT name) [func]]
_ -> error "Unsupported data declaration"
_ -> error "Not a plain type constructor"
-- | Like 'deriveEnumToHttpApiData' but does not strip prefix.
deriveEnumToHttpApiData' :: Name -> DecsQ
deriveEnumToHttpApiData' = deriveEnumToHttpApiData ""
-----------------------------------------------------------------------------
camel2Snake :: String -> String
camel2Snake = camelTo2 '_'
modifyFieldName :: String -> String -> String
modifyFieldName prefix s = camel2Snake $ case stripPrefix prefix s' of
Nothing -> s'
Just "" -> s'
Just x -> x
where
s' = fromMaybe s (stripPrefix "_" s)
modifyConsturctorName :: String -> String -> String
modifyConsturctorName prefix = camel2Snake . (fromMaybe <*> stripPrefix prefix)