packages feed

generic-override-aeson-0.4.0.0: src/Data/Override/Aeson/Options/Internal.hs

-- | This is the internal generic-override-aeson API and should be considered
-- unstable and subject to change. In general, you should prefer to use the
-- public, stable API provided by "Data.Override.Aeson".
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Override.Aeson.Options.Internal where

import Data.Aeson
import Data.Coerce (coerce)
import Data.Proxy (Proxy(..))
import GHC.Generics (Generic, Rep)
import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)

import qualified Data.Aeson as Aeson

-- | Use with @DerivingVia@ to override Aeson @Options@ with a type-level
-- list of 'AesonOption'.
newtype WithAesonOptions (a :: *) (options :: [AesonOption]) = WithAesonOptions a

instance
  ( ApplyAesonOptions options
  , Generic a
  , Aeson.GToJSON Aeson.Zero (Rep a)
  , Aeson.GToEncoding Aeson.Zero (Rep a)
  ) => ToJSON (WithAesonOptions a options)
  where
  toJSON = coerce $ genericToJSON @a $ applyAesonOptions (Proxy @options) defaultOptions
  toEncoding = coerce $ genericToEncoding @a $ applyAesonOptions (Proxy @options) defaultOptions

instance
  ( ApplyAesonOptions options
  , Generic a
  , Aeson.GFromJSON Aeson.Zero (Rep a)
  ) => FromJSON (WithAesonOptions a options)
  where
  parseJSON = coerce $ genericParseJSON @a $ applyAesonOptions (Proxy @options) defaultOptions

-- | Provides a type-level subset of fields from 'Options'
data AesonOption =
    AllNullaryToStringTag Bool -- ^ Equivalient to @'allNullaryToStringTag' = b@
  | OmitNothingFields -- ^ Equivalient to @'omitNothingFields' = True@
  | SumEncodingTaggedObject Symbol Symbol -- ^ Equivalient to @'sumEncoding' = 'TaggedObject' k v@
  | SumEncodingUntaggedValue -- ^ Equivalient to @'sumEncoding' = 'UntaggedValue'@
  | SumEncodingObjectWithSingleField -- ^ Equivalient to @'sumEncoding' = 'ObjectWithSingleField'@
  | SumEncodingTwoElemArray -- ^ Equivalient to @'sumEncoding' = 'TwoElemArray'@
  | UnwrapUnaryRecords -- ^ Equivalient to @'unwrapUnaryRecords' = True@
  | TagSingleConstructors -- ^ Equivalient to @'tagSingleConstructors' = True@

-- | Updates 'Options' given a type-level list of 'AesonOption'.
class ApplyAesonOptions (options :: [AesonOption]) where
  applyAesonOptions :: Proxy options -> Options -> Options

instance ApplyAesonOptions '[] where
  applyAesonOptions _ = id

instance
  ( ApplyAesonOption option
  , ApplyAesonOptions options
  ) => ApplyAesonOptions (option ': options)
  where
  applyAesonOptions _ =
    applyAesonOption (Proxy @option) . (applyAesonOptions (Proxy @options))

-- | Updates 'Options' given a single type-level 'AesonOption'.
class ApplyAesonOption (option :: AesonOption) where
  applyAesonOption :: Proxy option -> Options -> Options

instance ApplyAesonOption ('AllNullaryToStringTag 'True) where
  applyAesonOption _ o = o { allNullaryToStringTag = True }

instance ApplyAesonOption ('AllNullaryToStringTag 'False) where
  applyAesonOption _ o = o { allNullaryToStringTag = False }

instance ApplyAesonOption 'OmitNothingFields where
  applyAesonOption _ o = o { omitNothingFields = True }

instance (KnownSymbol k, KnownSymbol v) => ApplyAesonOption ('SumEncodingTaggedObject k v) where
  applyAesonOption _ o = o { sumEncoding = TaggedObject (symbolVal (Proxy @k)) (symbolVal (Proxy @v)) }

instance ApplyAesonOption 'SumEncodingUntaggedValue where
  applyAesonOption _ o = o { sumEncoding = UntaggedValue }

instance ApplyAesonOption 'SumEncodingObjectWithSingleField where
  applyAesonOption _ o = o { sumEncoding = ObjectWithSingleField }

instance ApplyAesonOption 'SumEncodingTwoElemArray where
  applyAesonOption _ o = o { sumEncoding = TwoElemArray }

instance ApplyAesonOption 'UnwrapUnaryRecords where
  applyAesonOption _ o = o { unwrapUnaryRecords = True }

instance ApplyAesonOption 'TagSingleConstructors where
  applyAesonOption _ o = o { tagSingleConstructors = True }