packages feed

argo-0.2021.10.17: source/library/Argo/Class/FromValue.hs

{-# LANGUAGE FlexibleInstances #-}

module Argo.Class.FromValue where

import Control.Monad ((<=<))

import qualified Argo.Type as Type
import qualified Argo.Type.Array as Array
import qualified Argo.Type.Boolean as Boolean
import qualified Argo.Type.Number as Number
import qualified Argo.Type.Object as Object
import qualified Argo.Type.Pair as Pair
import qualified Argo.Type.String as String
import qualified Argo.Type.Value as Value
import qualified Data.Array
import qualified Data.Bits as Bits
import qualified Data.Int as Int
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map as Map
import qualified Data.Text as Text
import qualified Data.Text.Lazy as LazyText
import qualified Data.Word as Word

class FromValue a where
    fromValue :: Type.Value -> Maybe a

instance FromValue Type.Value where
    fromValue = Just

instance FromValue Bool where
    fromValue = withBoolean "Bool" pure

instance FromValue Char where
    fromValue = withString "Char" $ \ x -> case Text.uncons x of
        Just (y, z) | Text.null z -> pure y
        _ -> fail "not singleton"

instance FromValue Int where
    fromValue =
        let
            integerToInt :: Integer -> Maybe Int
            integerToInt = Bits.toIntegralSized
        in integerToInt <=< fromValue

instance FromValue Int.Int8 where
    fromValue =
        let
            integerToInt8 :: Integer -> Maybe Int.Int8
            integerToInt8 = Bits.toIntegralSized
        in integerToInt8 <=< fromValue

instance FromValue Int.Int16 where
    fromValue =
        let
            integerToInt16 :: Integer -> Maybe Int.Int16
            integerToInt16 = Bits.toIntegralSized
        in integerToInt16 <=< fromValue

instance FromValue Int.Int32 where
    fromValue =
        let
            integerToInt32 :: Integer -> Maybe Int.Int32
            integerToInt32 = Bits.toIntegralSized
        in integerToInt32 <=< fromValue

instance FromValue Int.Int64 where
    fromValue =
        let
            integerToInt64 :: Integer -> Maybe Int.Int64
            integerToInt64 = Bits.toIntegralSized
        in integerToInt64 <=< fromValue

instance FromValue Word where
    fromValue =
        let
            integerToWord :: Integer -> Maybe Word
            integerToWord = Bits.toIntegralSized
        in integerToWord <=< fromValue

instance FromValue Word.Word8 where
    fromValue =
        let
            integerToWord8 :: Integer -> Maybe Word.Word8
            integerToWord8 = Bits.toIntegralSized
        in integerToWord8 <=< fromValue

instance FromValue Word.Word16 where
    fromValue =
        let
            integerToWord16 :: Integer -> Maybe Word.Word16
            integerToWord16 = Bits.toIntegralSized
        in integerToWord16 <=< fromValue

instance FromValue Word.Word32 where
    fromValue =
        let
            integerToWord32 :: Integer -> Maybe Word.Word32
            integerToWord32 = Bits.toIntegralSized
        in integerToWord32 <=< fromValue

instance FromValue Word.Word64 where
    fromValue =
        let
            integerToWord64 :: Integer -> Maybe Word.Word64
            integerToWord64 = Bits.toIntegralSized
        in integerToWord64 <=< fromValue

instance FromValue Integer where
    fromValue = withNumber "Integer" $ \ x y ->
        if y < 0 then fail "fractional" else pure $ x * 10 ^ y

instance FromValue Float where
    fromValue = withNumber "Float" $ \ x y ->
        pure . fromRational . Number.toRational $ Number.Number x y

instance FromValue Double where
    fromValue = withNumber "Double" $ \ x y ->
        pure . fromRational . Number.toRational $ Number.Number x y

instance {-# OVERLAPPING #-} FromValue String where
    fromValue = fmap Text.unpack . fromValue

instance FromValue Text.Text where
    fromValue = withString "Text" pure

instance FromValue LazyText.Text where
    fromValue = fmap LazyText.fromStrict . fromValue

instance FromValue a => FromValue (Maybe a) where
    fromValue x = case x of
        Value.Null _ -> pure Nothing
        _ -> Just <$> fromValue x

instance FromValue () where
    fromValue x = do
        [] <- fromValue x :: Maybe [Type.Value]
        pure ()

instance (FromValue a, FromValue b) => FromValue (a, b) where
    fromValue x = do
        [y, z] <- fromValue x
        (,) <$> fromValue y <*> fromValue z

instance FromValue a => FromValue (Data.Array.Array Int a) where
    fromValue = withArray "Array" $ traverse fromValue

instance FromValue a => FromValue [a] where
    fromValue =
        let
            arrayToList :: Data.Array.Array Int b -> [b]
            arrayToList = Data.Array.elems
        in fmap arrayToList . fromValue

instance FromValue a => FromValue (NonEmpty.NonEmpty a) where
    fromValue = NonEmpty.nonEmpty <=< fromValue

instance FromValue a => FromValue (Map.Map Text.Text a) where
    fromValue = withObject "Map"
        $ fmap Map.fromList
        . traverse (\ (Pair.Pair (String.String k, v)) -> (,) k <$> fromValue v)
        . Data.Array.elems

withBoolean :: String -> (Bool -> Maybe a) -> Type.Value -> Maybe a
withBoolean s f x = case x of
    Value.Boolean (Boolean.Boolean y) -> f y
    _ -> fail s

withNumber :: String -> (Integer -> Integer -> Maybe a) -> Type.Value -> Maybe a
withNumber s f x = case x of
    Value.Number (Number.Number y z) -> f y z
    _ -> fail s

withString :: String -> (Text.Text -> Maybe a) -> Type.Value -> Maybe a
withString s f x = case x of
    Value.String (String.String y) -> f y
    _ -> fail s

withArray :: String -> (Data.Array.Array Int Type.Value -> Maybe a) -> Type.Value -> Maybe a
withArray s f x = case x of
    Value.Array (Array.Array y) -> f y
    _ -> fail s

withObject :: String -> (Data.Array.Array Int (Pair.Pair String.String Type.Value) -> Maybe a) -> Type.Value -> Maybe a
withObject s f x = case x of
    Value.Object (Object.Object y) -> f y
    _ -> fail s