ron-hs-0.4.0: src/Data/Ron/Class/Deriving.hs
{-# LANGUAGE KindSignatures, DataKinds #-}
{-# LANGUAGE UndecidableInstances, FlexibleContexts #-} -- type family in instance head
{-# LANGUAGE PolyKinds #-}
-- | Allows deriving ron encoding-decoding instances with @DerivingVia@. See 'RonWith'
module Data.Ron.Class.Deriving
( RonWith (..)
-- * Settings
, UseStrict
, EncodeWith
, DecodeWith
, ConvertWith
, FieldsToSnakeCase
, FieldsDropPrefix
, ImplicitSome
, NoImplicitSome
, SkipSingleConstructor
, NoSkipSingleConstructor
-- * Undelying typeclasses
, ReflectSettingsOptions (..)
, ReflectFlagOptions (..)
) where
import Data.Char (isUpper, toLower, isLower)
import Data.Proxy (Proxy (..))
import GHC.Generics (Generic, Rep)
import Data.Ron.Class
-- | Helper for deriving Ron instances. Use like this:
--
-- @
-- data MyType = MyType {...}
-- deriving (Eq, Show, Generic)
-- deriving (ToRon, FromRon)
-- via RonWith '[FieldsDropPrefix, EncodeWith SkipSingleConstructor] MyType
-- @
--
-- This is identical to
--
-- @
-- data MyType = MyType {...}
-- deriving (Eq, Show, Generic)
-- instance ToRon MyType where
-- toRon =
-- let settings
-- = (\s{encodeFlags} -> s{encodeFlags = encodeFlags{skipSingleConstructor = True}})
-- . (\s{fieldModifier} -> s{dropPrefix . fieldModifier})
-- $ laxRonSettings
-- in toRonGeneric settings
-- @
--
-- The options are applied left to right, and the starting options are
-- 'laxRonSettings'
--
-- All of built-in settings are documented in 'Data.Ron.Class.Deriving' module.
-- You can define your own settings by creating new datatypes and creating
-- instances for 'ReflectSettingsOptions' or 'ReflectFlagOptions'
newtype RonWith s a = RonWith a
instance (Generic a, GToRon (Rep a), ReflectSettingsList s)
=> ToRon (RonWith s a) where
toRon (RonWith x) = flip toRonGeneric x $ reflectL (Proxy @s) laxRonSettings
instance (Generic a, GFromRon (Rep a), ReflectSettingsList s)
=> FromRon (RonWith s a) where
fromRon = fmap RonWith . fromRonGeneric (reflectL (Proxy @s) laxRonSettings)
class ReflectSettingsList a where
reflectL :: Proxy a -> RonSettings -> RonSettings
instance ReflectSettingsList '[] where
reflectL Proxy s = s
{-# INLINE reflectL #-}
instance (ReflectSettingsOptions s, ReflectSettingsList ss)
=> ReflectSettingsList (s:ss) where
reflectL Proxy
= reflectL (Proxy @ss)
. reflectS (Proxy @s)
{-# INLINE reflectL #-}
-- | Replace the current settings with 'strictRonSettings'
data UseStrict
-- | Sets a flag in @encodeFlags@. Can set anything with 'ReflectFlagOptions'
-- instances, like 'ImplicitSome', 'SkipSingleConstructor'
data EncodeWith a
-- | Sets a flag in @decodeFlags@. Can set anything with 'ReflectFlagOptions'
-- instances
data DecodeWith a
-- | Same as setting both 'EncodeWith' and 'DecodeWith'
data ConvertWith a
-- | Convert fields to snake case. If your field also has prefix, you want to
-- have this option after 'FieldsDropPrefix', as options are applied left to
-- right
data FieldsToSnakeCase
-- | Drop the prefix that is usually used with lenses, that is an underscore
-- followed by several lowercase letters; regex for this prefix is
-- @s\/^_[[:lowercase:]]+([[:uppercase:]].*)\/\\1\/@. You probably want to use
-- this field modifier before other field modifiers, as they are applied left
-- to right
data FieldsDropPrefix
-- | Typeclass for you to implement your own options. Here's how
-- 'FieldsToSnakeCase' uses it:
--
-- @
-- instance ReflectSettingsOptions FieldsToSnakeCase where
-- reflectS Proxy s@RonSettings {fieldModifier} = s
-- { fieldModifier = toSnake . fieldModifier }
-- @
--
-- Be careful when composing functions to apply them after the already present,
-- to preserve the left-to-right semantics of adding the options
class ReflectSettingsOptions a where
reflectS :: Proxy a -> RonSettings -> RonSettings
-- | Encode or decode flag that sets 'Data.Ron.Class.implicitSome' to @True@
data ImplicitSome
-- | Encode or decode flag that sets 'Data.Ron.Class.implicitSome' to @False@
data NoImplicitSome
-- | Encode or decode flag that sets 'Data.Ron.Class.skipSingleConstructor' to @True@
data SkipSingleConstructor
-- | Encode or decode flag that sets 'Data.Ron.Class.skipSingleConstructor' to @False@
data NoSkipSingleConstructor
-- | Typeclass for you to implement your own symmetric options. Here's how
-- 'ImplicitSome' uses it
--
-- @
-- instance ReflectFlagOptions ImplicitSome where
-- reflectF Proxy flags = flags { implicitSome = True }
-- @
class ReflectFlagOptions a where
reflectF :: Proxy a -> RonFlags -> RonFlags
instance ReflectSettingsOptions UseStrict where
reflectS Proxy _ = strictRonSettings
{-# INLINE reflectS #-}
instance ReflectFlagOptions a => ReflectSettingsOptions (EncodeWith a) where
reflectS Proxy s@RonSettings {encodeFlags} = s
{ encodeFlags = reflectF (Proxy @a) encodeFlags }
{-# INLINE reflectS #-}
instance ReflectFlagOptions a => ReflectSettingsOptions (DecodeWith a) where
reflectS Proxy s@RonSettings {decodeFlags} = s
{ decodeFlags = reflectF (Proxy @a) decodeFlags }
{-# INLINE reflectS #-}
instance ReflectFlagOptions a => ReflectSettingsOptions (ConvertWith a) where
reflectS Proxy s@RonSettings {encodeFlags, decodeFlags} = s
{ encodeFlags = reflectF (Proxy @a) encodeFlags
, decodeFlags = reflectF (Proxy @a) decodeFlags
}
{-# INLINE reflectS #-}
instance ReflectSettingsOptions FieldsDropPrefix where
reflectS Proxy conf@RonSettings {fieldModifier} = conf
{ fieldModifier = dropPrefix . fieldModifier }
where
dropPrefix ('_':s) =
case dropWhile (not . isUpper) s of
"" -> '_':s
c:s' -> toLower c : s'
dropPrefix s = s
{-# INLINE reflectS #-}
instance ReflectSettingsOptions FieldsToSnakeCase where
reflectS Proxy s@RonSettings {fieldModifier} = s
{ fieldModifier = toSnake . fieldModifier }
where
toSnake = camelTo2 '_'
{-# INLINE reflectS #-}
-- | Better version of camelTo. Example where it works better:
--
-- > camelTo '_' "CamelAPICase" == "camel_apicase"
-- > camelTo2 '_' "CamelAPICase" == "camel_api_case"
--
-- Extracted from aeson at Feb 2022
camelTo2 :: Char -> String -> String
camelTo2 c = map toLower . go2 . go1
where
go1 "" = ""
go1 (x:u:l:xs) | isUpper u && isLower l = x : c : u : l : go1 xs
go1 (x:xs) = x : go1 xs
go2 "" = ""
go2 (l:u:xs) | isLower l && isUpper u = l : c : u : go2 xs
go2 (x:xs) = x : go2 xs
instance ReflectFlagOptions ImplicitSome where
reflectF Proxy f =
f { implicitSome = True }
{-# INLINE reflectF #-}
instance ReflectFlagOptions NoImplicitSome where
reflectF Proxy f =
f { implicitSome = False }
{-# INLINE reflectF #-}
instance ReflectFlagOptions SkipSingleConstructor where
reflectF Proxy f =
f { skipSingleConstructor = True }
{-# INLINE reflectF #-}
instance ReflectFlagOptions NoSkipSingleConstructor where
reflectF Proxy f =
f { skipSingleConstructor = False }
{-# INLINE reflectF #-}