packages feed

mmzk-env-0.6.0.0: src/Data/Env/TypeParserW.hs

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}

-- |
-- Module: Data.Env.TypeParserW
-- Description: Type class provides parsers for types parameterized by witness types.
--
-- This module provides the 'TypeParserW' type class, which allows parsing
-- environment variables using witness types to control parsing behavior.
-- This enables multiple parsing strategies for the same type by using
-- different witness types.
module Data.Env.TypeParserW (
  TypeParserW (..),
) where

import Data.Env.TypeParser
import Data.Proxy ( Proxy(..) )
import Data.Tuple ( Solo )

-- | Type class for parsers parameterized by a witness type.
--
-- This is similar to 'TypeParser'', but parameterised by a witness type @p@ that
-- determines the parsing strategy, giving you explicit control over behaviour.
--
-- The witness pattern is useful when you need multiple parsing strategies
-- for the same type or want to compose parsers in different ways.
--
-- The functional dependency @p -> a@ ensures that each witness type uniquely
-- determines the result type. For example, 'Data.Env.Witness.DefaultNum.DefaultNum' 5432 Int
-- uniquely determines the result type as Int.
--
-- See 'Data.Env.Witness.DefaultNum.DefaultNum' for an example of how to use witness types with
-- this class.
class TypeParserW p a | p -> a where
  -- | Parse a value from its string representation.
  parseTypeW :: Proxy p -> String -> Either String a

  -- | Result to use when the environment variable is absent.
  -- Default delegates to @'parseTypeW' proxy ""@. Override for witnesses that
  -- delegate to 'TypeParser' — see the 'Solo' instance below.
  parseMissingW :: Proxy p -> Either String a
  parseMissingW proxy = parseTypeW proxy ""
  {-# INLINE parseMissingW #-}

  -- | Convenience wrapper that returns 'Maybe' instead of 'Either'.
  parseTypeW' :: Proxy p -> String -> Maybe a
  parseTypeW' proxy str = case parseTypeW proxy str of
    Right val -> Just val
    Left _    -> Nothing

instance TypeParser a => TypeParserW (Solo a) a where
  parseTypeW :: Proxy (Solo a) -> String -> Either String a
  parseTypeW _ = parseType

  -- | Delegates to 'parseMissing' from 'TypeParser', so a 'Solo' field
  -- with no default behaves the same as a plain 'TypeParser' field.
  parseMissingW :: Proxy (Solo a) -> Either String a
  parseMissingW _ = parseMissing @a

-- | Compose two witnesses into a pipeline: @p1@ preprocesses the raw string
-- (e.g. trims or validates it), and its output is fed as the input string to
-- @p2@, which resolves the final value of type @a@. Only @p1@ is constrained
-- to produce 'String' — @p2@ (and hence the composed witness) may resolve to
-- any type.
instance (TypeParserW p1 String, TypeParserW p2 a) => TypeParserW (p1, p2) a where
  parseTypeW :: Proxy (p1, p2) -> String -> Either String a
  parseTypeW _ str = parseTypeW @p1 Proxy str >>= parseTypeW @p2 Proxy