packages feed

eventlog-live-otelcol-0.6.0.0: src/GHC/Eventlog/Live/Otelcol/Config/Default.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}

{- |
Module      : GHC.Eventlog.Live.Otelcol.Config
Description : The implementation of @eventlog-live-otelcol@.
Stability   : experimental
Portability : portable
-}
module GHC.Eventlog.Live.Otelcol.Config.Default (
  defaultConfig,

  -- * Internal helpers for defining `Default` instances
  MissingDefaultException (..),
  getDefault,
) where

import Control.Exception (Exception (..), throw)
import Data.Bifunctor (Bifunctor (..))
import Data.Kind (Type)
import Data.List (intercalate)
import Data.Proxy (Proxy (..))
import GHC.Eventlog.Live.Otelcol.Config.Default.Raw (decodeThrow, defaultConfigByteString)
import GHC.Eventlog.Live.Otelcol.Config.Types (Config)
import GHC.Records (HasField (..))
import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)
import Language.Haskell.TH.Lift.Compat (Exp, Lift (..), Q)

{- |
Internal helper.
The default configuration.
-}
defaultConfig :: Config
defaultConfig = $(lift =<< decodeThrow @Q @Config defaultConfigByteString)

{- |
__Warning:__ Compile-time only.

Internal helper.
This exception is thrown when a property is missing from the default configuration file.
-}
newtype MissingDefaultException
  = MissingDefaultException
  { accessors :: [String]
  }

instance Show MissingDefaultException where
  show :: MissingDefaultException -> String
  show e =
    "Missing property '" <> intercalate "." e.accessors <> "' in default configuration"

instance Exception MissingDefaultException

{- |
__Warning:__ Compile-time only.

Internal helper.
Get the default value under the given path of accessors and `lift` it.
-}
getDefault ::
  forall xs a b.
  (GetDefault xs a b, Lift b) =>
  a ->
  Q Exp
getDefault a = either throw lift (getDefaultEither @xs @a @b a)

{- |
__Warning:__ Compile-time only.

Internal helper.
Get the default value under the given accessor after calling a recursor.
This function is used to implement the `GetDefault` instances.
-}
getDefaultEither' ::
  forall x a b c.
  (KnownSymbol x, HasField x a (Maybe b)) =>
  (b -> Either MissingDefaultException c) ->
  a ->
  Either MissingDefaultException c
getDefaultEither' rec a =
  first addAccessor . maybe (Left $ MissingDefaultException []) rec $ getField @x a
 where
  addAccessor :: MissingDefaultException -> MissingDefaultException
  addAccessor e = MissingDefaultException{accessors = symbolVal (Proxy @x) : e.accessors}

{- |
__Warning:__ Compile-time only.

Internal helper.
This class guides the search for `getDefaultEither` functions from a list of accessors.
-}
class GetDefault (xs :: [Symbol]) (a :: Type) (b :: Type) where
  getDefaultEither :: a -> Either MissingDefaultException b

instance (KnownSymbol x, HasField x a (Maybe b)) => GetDefault (x ': '[]) a b where
  getDefaultEither :: a -> Either MissingDefaultException b
  getDefaultEither = getDefaultEither' @x Right
  {-# INLINE getDefaultEither #-}

instance (KnownSymbol x, HasField x a (Maybe b), GetDefault (y ': ys) b c) => GetDefault (x ': y ': ys) a c where
  getDefaultEither :: a -> Either MissingDefaultException c
  getDefaultEither = getDefaultEither' @x (getDefaultEither @(y ': ys))
  {-# INLINE getDefaultEither #-}