{-|
Module      : Toml.FromValue
Description : Automation for converting TOML values to application values.
Copyright   : (c) Eric Mertens, 2023
License     : ISC
Maintainer  : emertens@gmail.com

Use 'FromValue' to define a transformation from some 'Value' to an application
domain type.

Use 'ParseTable' to help build 'FromValue' instances that match tables. It
will make it easy to track which table keys have been used and which are left
over.

Warnings can be emitted using 'warning' and 'warnTable' (depending on what)
context you're in. These warnings can provide useful feedback about
problematic decodings or keys that might be unused now but were perhaps
meaningful in an old version of a configuration file.

"Toml.FromValue.Generic" can be used to derive instances of 'FromValue'
automatically for record types.

-}
module Toml.FromValue (
    -- * Deserialization classes
    FromValue(..),

    -- * Matcher
    Matcher,
    Result(..),
    warning,

    -- * Table matching
    ParseTable,
    runParseTable,
    parseTableFromValue,
    reqKey,
    optKey,
    reqKeyOf,
    optKeyOf,
    warnTable,
    KeyAlt(..),
    pickKey,

    -- * Table matching primitives
    getTable,
    setTable,
    liftMatcher,
    ) where

import Control.Applicative (Alternative)
import Control.Monad (MonadPlus, zipWithM)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State.Strict (StateT(..), put, get)
import Data.Int (Int8, Int16, Int32, Int64)
import Data.List (intercalate)
import Data.Map (Map)
import Data.Map qualified as Map
import Data.String (IsString (fromString))
import Data.Text qualified
import Data.Text.Lazy qualified
import Data.Time (ZonedTime, LocalTime, Day, TimeOfDay)
import Data.Word (Word8, Word16, Word32, Word64)
import Numeric.Natural (Natural)
import Toml.FromValue.Matcher (Matcher, Result(..), runMatcher, withScope, warning, inIndex, inKey)
import Toml.FromValue.ParseTable
import Toml.Pretty (prettySimpleKey, prettyValue)
import Toml.Value (Value(..), Table)

-- | Class for types that can be decoded from a TOML value.
class FromValue a where
    -- | Convert a 'Value' or report an error message
    fromValue :: Value -> Matcher a

    -- | Used to implement instance for '[]'. Most implementations rely on the default implementation.
    listFromValue :: Value -> Matcher [a]
    listFromValue (Array [Value]
xs) = forall (m :: * -> *) a b c.
Applicative m =>
(a -> b -> m c) -> [a] -> [b] -> m [c]
zipWithM (\Int
i Value
v -> forall a. Int -> Matcher a -> Matcher a
inIndex Int
i (forall a. FromValue a => Value -> Matcher a
fromValue Value
v)) [Int
0..] [Value]
xs
    listFromValue Value
v = forall a. String -> Value -> Matcher a
typeError String
"array" Value
v

instance (Ord k, IsString k, FromValue v) => FromValue (Map k v) where
    fromValue :: Value -> Matcher (Map k v)
fromValue (Table Table
t) = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
traverse forall {a} {a}.
(IsString a, FromValue a) =>
(String, Value) -> Matcher (a, a)
f (forall k a. Map k a -> [(k, a)]
Map.assocs Table
t)
        where
            f :: (String, Value) -> Matcher (a, a)
f (String
k,Value
v) = (,) (forall a. IsString a => String -> a
fromString String
k) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. String -> Matcher a -> Matcher a
inKey String
k (forall a. FromValue a => Value -> Matcher a
fromValue Value
v)
    fromValue Value
v = forall a. String -> Value -> Matcher a
typeError String
"table" Value
v

-- | Report a type error
typeError :: String {- ^ expected type -} -> Value {- ^ actual value -} -> Matcher a
typeError :: forall a. String -> Value -> Matcher a
typeError String
wanted Value
got = forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String
"type error. wanted: " forall a. [a] -> [a] -> [a]
++ String
wanted forall a. [a] -> [a] -> [a]
++ String
" got: " forall a. [a] -> [a] -> [a]
++ Value -> String
valueType Value
got)

-- | Used to derive a 'fromValue' implementation from a 'ParseTable' matcher.
parseTableFromValue :: ParseTable a -> Value -> Matcher a
parseTableFromValue :: forall a. ParseTable a -> Value -> Matcher a
parseTableFromValue ParseTable a
p (Table Table
t) = forall a. ParseTable a -> Table -> Matcher a
runParseTable ParseTable a
p Table
t
parseTableFromValue ParseTable a
_ Value
v = forall a. String -> Value -> Matcher a
typeError String
"table" Value
v

valueType :: Value -> String
valueType :: Value -> String
valueType = \case
    Integer   {} -> String
"integer"
    Float     {} -> String
"float"
    Array     {} -> String
"array"
    Table     {} -> String
"table"
    Bool      {} -> String
"boolean"
    String    {} -> String
"string"
    TimeOfDay {} -> String
"local time"
    LocalTime {} -> String
"local date-time"
    Day       {} -> String
"locate date"
    ZonedTime {} -> String
"offset date-time"

-- | Matches integer values
instance FromValue Integer where
    fromValue :: Value -> Matcher Integer
fromValue (Integer Integer
x) = forall (f :: * -> *) a. Applicative f => a -> f a
pure Integer
x
    fromValue Value
v = forall a. String -> Value -> Matcher a
typeError String
"integer" Value
v

-- | Matches non-negative integer values
instance FromValue Natural where
    fromValue :: Value -> Matcher Natural
fromValue Value
v =
     do Integer
i <- forall a. FromValue a => Value -> Matcher a
fromValue Value
v
        if Integer
0 forall a. Ord a => a -> a -> Bool
<= Integer
i then
            forall (f :: * -> *) a. Applicative f => a -> f a
pure (forall a. Num a => Integer -> a
fromInteger Integer
i)
        else
            forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"integer out of range for Natural"

fromValueSized :: forall a. (Bounded a, Integral a) => String -> Value -> Matcher a
fromValueSized :: forall a. (Bounded a, Integral a) => String -> Value -> Matcher a
fromValueSized String
name Value
v =
 do Integer
i <- forall a. FromValue a => Value -> Matcher a
fromValue Value
v
    if forall a b. (Integral a, Num b) => a -> b
fromIntegral (forall a. Bounded a => a
minBound :: a) forall a. Ord a => a -> a -> Bool
<= Integer
i Bool -> Bool -> Bool
&& Integer
i forall a. Ord a => a -> a -> Bool
<= forall a b. (Integral a, Num b) => a -> b
fromIntegral (forall a. Bounded a => a
maxBound :: a) then
        forall (f :: * -> *) a. Applicative f => a -> f a
pure (forall a. Num a => Integer -> a
fromInteger Integer
i)
    else
        forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String
"integer out of range for " forall a. [a] -> [a] -> [a]
++ String
name)

instance FromValue Int    where fromValue :: Value -> Matcher Int
fromValue = forall a. (Bounded a, Integral a) => String -> Value -> Matcher a
fromValueSized String
"Int"
instance FromValue Int8   where fromValue :: Value -> Matcher Int8
fromValue = forall a. (Bounded a, Integral a) => String -> Value -> Matcher a
fromValueSized String
"Int8"
instance FromValue Int16  where fromValue :: Value -> Matcher Int16
fromValue = forall a. (Bounded a, Integral a) => String -> Value -> Matcher a
fromValueSized String
"Int16"
instance FromValue Int32  where fromValue :: Value -> Matcher Int32
fromValue = forall a. (Bounded a, Integral a) => String -> Value -> Matcher a
fromValueSized String
"Int32"
instance FromValue Int64  where fromValue :: Value -> Matcher Int64
fromValue = forall a. (Bounded a, Integral a) => String -> Value -> Matcher a
fromValueSized String
"Int64"
instance FromValue Word   where fromValue :: Value -> Matcher Word
fromValue = forall a. (Bounded a, Integral a) => String -> Value -> Matcher a
fromValueSized String
"Word"
instance FromValue Word8  where fromValue :: Value -> Matcher Word8
fromValue = forall a. (Bounded a, Integral a) => String -> Value -> Matcher a
fromValueSized String
"Word8"
instance FromValue Word16 where fromValue :: Value -> Matcher Word16
fromValue = forall a. (Bounded a, Integral a) => String -> Value -> Matcher a
fromValueSized String
"Word16"
instance FromValue Word32 where fromValue :: Value -> Matcher Word32
fromValue = forall a. (Bounded a, Integral a) => String -> Value -> Matcher a
fromValueSized String
"Word32"
instance FromValue Word64 where fromValue :: Value -> Matcher Word64
fromValue = forall a. (Bounded a, Integral a) => String -> Value -> Matcher a
fromValueSized String
"Word64"

-- | Matches single-character strings with 'fromValue' and arbitrary
-- strings with 'listFromValue' to support 'Prelude.String'
instance FromValue Char where
    fromValue :: Value -> Matcher Char
fromValue (String [Char
c]) = forall (f :: * -> *) a. Applicative f => a -> f a
pure Char
c
    fromValue Value
v = forall a. String -> Value -> Matcher a
typeError String
"character" Value
v

    listFromValue :: Value -> Matcher String
listFromValue (String String
xs) = forall (f :: * -> *) a. Applicative f => a -> f a
pure String
xs
    listFromValue Value
v = forall a. String -> Value -> Matcher a
typeError String
"string" Value
v

-- | Matches string literals
instance FromValue Data.Text.Text where
    fromValue :: Value -> Matcher Text
fromValue Value
v = String -> Text
Data.Text.pack forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. FromValue a => Value -> Matcher a
fromValue Value
v

-- | Matches string literals
--
-- @since 1.2.1.0
instance FromValue Data.Text.Lazy.Text where
    fromValue :: Value -> Matcher Text
fromValue Value
v = String -> Text
Data.Text.Lazy.pack forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. FromValue a => Value -> Matcher a
fromValue Value
v

-- | Matches floating-point and integer values
--
-- @since 1.2.1.0
instance FromValue Double where
    fromValue :: Value -> Matcher Double
fromValue (Float Double
x) = forall (f :: * -> *) a. Applicative f => a -> f a
pure Double
x
    fromValue (Integer Integer
x) = forall (f :: * -> *) a. Applicative f => a -> f a
pure (forall a. Num a => Integer -> a
fromInteger Integer
x)
    fromValue Value
v = forall a. String -> Value -> Matcher a
typeError String
"float" Value
v

-- | Matches floating-point and integer values
instance FromValue Float where
    fromValue :: Value -> Matcher Float
fromValue (Float Double
x) = forall (f :: * -> *) a. Applicative f => a -> f a
pure (forall a b. (Real a, Fractional b) => a -> b
realToFrac Double
x)
    fromValue (Integer Integer
x) = forall (f :: * -> *) a. Applicative f => a -> f a
pure (forall a. Num a => Integer -> a
fromInteger Integer
x)
    fromValue Value
v = forall a. String -> Value -> Matcher a
typeError String
"float" Value
v

-- | Matches @true@ and @false@
instance FromValue Bool where
    fromValue :: Value -> Matcher Bool
fromValue (Bool Bool
x) = forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
x
    fromValue Value
v = forall a. String -> Value -> Matcher a
typeError String
"boolean" Value
v

-- | Implemented in terms of 'listFromValue'
instance FromValue a => FromValue [a] where
    fromValue :: Value -> Matcher [a]
fromValue = forall a. FromValue a => Value -> Matcher [a]
listFromValue

-- | Matches local date literals
instance FromValue Day where
    fromValue :: Value -> Matcher Day
fromValue (Day Day
x) = forall (f :: * -> *) a. Applicative f => a -> f a
pure Day
x
    fromValue Value
v = forall a. String -> Value -> Matcher a
typeError String
"local date" Value
v

-- | Matches local time literals
instance FromValue TimeOfDay where
    fromValue :: Value -> Matcher TimeOfDay
fromValue (TimeOfDay TimeOfDay
x) = forall (f :: * -> *) a. Applicative f => a -> f a
pure TimeOfDay
x
    fromValue Value
v = forall a. String -> Value -> Matcher a
typeError String
"local time" Value
v

-- | Matches offset date-time literals
instance FromValue ZonedTime where
    fromValue :: Value -> Matcher ZonedTime
fromValue (ZonedTime ZonedTime
x) = forall (f :: * -> *) a. Applicative f => a -> f a
pure ZonedTime
x
    fromValue Value
v = forall a. String -> Value -> Matcher a
typeError String
"offset date-time" Value
v

-- | Matches local date-time literals
instance FromValue LocalTime where
    fromValue :: Value -> Matcher LocalTime
fromValue (LocalTime LocalTime
x) = forall (f :: * -> *) a. Applicative f => a -> f a
pure LocalTime
x
    fromValue Value
v = forall a. String -> Value -> Matcher a
typeError String
"local date-time" Value
v

-- | Matches all values, used for pass-through
instance FromValue Value where
    fromValue :: Value -> Matcher Value
fromValue = forall (f :: * -> *) a. Applicative f => a -> f a
pure

-- | Convenience function for matching an optional key with a 'FromValue'
-- instance.
--
-- @optKey key = 'optKeyOf' key 'fromValue'@
optKey :: FromValue a => String -> ParseTable (Maybe a)
optKey :: forall a. FromValue a => String -> ParseTable (Maybe a)
optKey String
key = forall a. String -> (Value -> Matcher a) -> ParseTable (Maybe a)
optKeyOf String
key forall a. FromValue a => Value -> Matcher a
fromValue

-- | Convenience function for matching a required key with a 'FromValue'
-- instance.
--
-- @reqKey key = 'reqKeyOf' key 'fromValue'@
reqKey :: FromValue a => String -> ParseTable a
reqKey :: forall a. FromValue a => String -> ParseTable a
reqKey String
key = forall a. String -> (Value -> Matcher a) -> ParseTable a
reqKeyOf String
key forall a. FromValue a => Value -> Matcher a
fromValue

-- | Match a table entry by key if it exists or return 'Nothing' if not.
-- If the key is defined, it is matched by the given function.
--
-- See 'pickKey' for more complex cases.
optKeyOf ::
    String {- ^ key -} ->
    (Value -> Matcher a) {- ^ value matcher -} ->
    ParseTable (Maybe a)
optKeyOf :: forall a. String -> (Value -> Matcher a) -> ParseTable (Maybe a)
optKeyOf String
key Value -> Matcher a
k = forall a. [KeyAlt a] -> ParseTable a
pickKey [forall a. String -> (Value -> Matcher a) -> KeyAlt a
Key String
key (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a. a -> Maybe a
Just forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Matcher a
k), forall a. Matcher a -> KeyAlt a
Else (forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a. Maybe a
Nothing)]

-- | Match a table entry by key or report an error if missing.
--
-- See 'pickKey' for more complex cases.
reqKeyOf ::
    String {- ^ key -} ->
    (Value -> Matcher a) {- ^ value matcher -} ->
    ParseTable a
reqKeyOf :: forall a. String -> (Value -> Matcher a) -> ParseTable a
reqKeyOf String
key Value -> Matcher a
k = forall a. [KeyAlt a] -> ParseTable a
pickKey [forall a. String -> (Value -> Matcher a) -> KeyAlt a
Key String
key Value -> Matcher a
k]