calamity-0.1.0.0: src/Calamity/Internal/AesonThings.hs
module Calamity.Internal.AesonThings
( WithSpecialCases
, WithSpecialCasesInner(..)
, type IfNoneThen
, type ExtractField
, type ExtractFields
, type InjectID
, SpecialRule
, DefaultToEmptyArray
, DefaultToZero
, DefaultToFalse
, CalamityJSON(..)
, jsonOptions
, jsonOptionsKeepNothing ) where
import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import Data.Aeson.Types ( Parser )
import Data.Kind
import Data.Reflection ( Reifies(..) )
import Data.Text ( Text )
import Data.Text.Strict.Lens
import Data.Typeable
import GHC.Generics
import qualified GHC.TypeLits as TL
import GHC.TypeLits ( KnownSymbol, Symbol, symbolVal )
textSymbolVal :: forall n. KnownSymbol n => Proxy n -> Text
textSymbolVal _ = symbolVal @n Proxy ^. packed
data SpecialCaseList
= SpecialCaseNil
| forall label action inner. SpecialCaseElem label action inner
data SpecialRule (label :: Symbol) (action :: SpecialRuleAction)
data SpecialRuleAction
= forall d. IfNoneThen d
| forall field. ExtractField field
| forall field. ExtractFields field
| forall mn idn. InjectID idn mn
type IfNoneThen label d =
SpecialRule label ('IfNoneThen d)
type ExtractField label field =
SpecialRule label ('ExtractField field)
type ExtractFields label field =
SpecialRule label ('ExtractFields field)
type InjectID label mn idn =
SpecialRule label ('InjectID mn idn)
class PerformAction (action :: SpecialRuleAction) where
runAction :: Proxy action -> Value -> Parser Value
instance Reifies d Value => PerformAction ('IfNoneThen d) where
runAction _ Null = pure $ reflect @d Proxy
runAction _ x = pure x
instance (KnownSymbol field) => PerformAction ('ExtractField field) where
runAction _ Null = pure Null
runAction _ o = withObject (("extracting field " <> textSymbolVal @field Proxy) ^. unpacked)
(.: textSymbolVal @field Proxy) o
instance (KnownSymbol field) => PerformAction ('ExtractFields field) where
runAction _ Null = pure Null
runAction _ o = withArray (("extracting fields " <> textSymbolVal @field Proxy) ^. unpacked)
((Array <$>) . traverse (withObject "extracting field" (.: textSymbolVal @field Proxy))) o
instance (KnownSymbol idn, KnownSymbol mn) => PerformAction ('InjectID idn mn) where
runAction _ = withObject
(("injecting id from " <> textSymbolVal @idn Proxy <> " into " <> textSymbolVal @mn Proxy) ^. unpacked) $ \o -> do
id <- o .: "id"
pure (Object o
& key (textSymbolVal @mn Proxy) . values . _Object . at (textSymbolVal @idn Proxy) ?~ id)
type family FoldSpecialCases (rules :: [Type]) :: SpecialCaseList where
FoldSpecialCases '[] = 'SpecialCaseNil
FoldSpecialCases (SpecialRule label action ': xs) = 'SpecialCaseElem label action (FoldSpecialCases xs)
FoldSpecialCases _ = TL.TypeError ('TL.Text "What did you do?")
newtype WithSpecialCasesInner (rules :: SpecialCaseList) a = WithSpecialCasesInner
{ unwrapWithSpecialCases :: a
}
type family WithSpecialCases rules a :: Type where
WithSpecialCases rules a = WithSpecialCasesInner (FoldSpecialCases rules) a
class RunSpecialCase a where
runSpecialCases :: Proxy a -> Object -> Parser Object
instance RunSpecialCase 'SpecialCaseNil where
runSpecialCases _ = pure . id
instance (RunSpecialCase inner, KnownSymbol label, PerformAction action)
=> RunSpecialCase ('SpecialCaseElem label action inner) where
runSpecialCases _ o = do
o' <- runSpecialCases (Proxy @inner) o
v <- o' .:? textSymbolVal @label Proxy .!= Null
v' <- runAction (Proxy @action) v
pure (o' & at (textSymbolVal @label Proxy) ?~ v')
instance (RunSpecialCase rules, Typeable a, Generic a, GFromJSON Zero (Rep a))
=> FromJSON (WithSpecialCasesInner rules a) where
parseJSON = withObject (show . typeRep $ Proxy @a) $ \o -> do
o' <- runSpecialCases (Proxy @rules) o
WithSpecialCasesInner <$> genericParseJSON jsonOptions (Object o')
data DefaultToEmptyArray
instance Reifies DefaultToEmptyArray Value where
reflect _ = Array mempty
data DefaultToZero
instance Reifies DefaultToZero Value where
reflect _ = Number 0
data DefaultToFalse
instance Reifies DefaultToFalse Value where
reflect _ = Bool False
newtype CalamityJSON a = CalamityJSON
{ unCalamityJSON :: a
}
instance (Typeable a, Generic a, GToJSON Zero (Rep a), GToEncoding Zero (Rep a)) => ToJSON (CalamityJSON a) where
toJSON = genericToJSON jsonOptions . unCalamityJSON
toEncoding = genericToEncoding jsonOptions . unCalamityJSON
instance (Typeable a, Generic a, GFromJSON Zero (Rep a)) => FromJSON (CalamityJSON a) where
parseJSON = fmap CalamityJSON . genericParseJSON jsonOptions
jsonOptions :: Options
jsonOptions = defaultOptions { sumEncoding = UntaggedValue
, fieldLabelModifier = camelTo2 '_' . filter (/= '_')
, omitNothingFields = True }
jsonOptionsKeepNothing :: Options
jsonOptionsKeepNothing = defaultOptions { sumEncoding = UntaggedValue
, fieldLabelModifier = camelTo2 '_' . filter (/= '_')
, omitNothingFields = False }