packages feed

rds-data-codecs-0.0.0.1: src/Data/RdsData/Decode/Value.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE OverloadedStrings #-}

{- HLINT ignore "Use <&>" -}

module Data.RdsData.Decode.Value
  ( DecodeValue(..)

  , rdsValue
  , decodeValueFailedMessage
  , decodeValueFailed
  , maybe
  , array
  , base64
  , bool
  , double
  , text
  , integer
  , null
  , int
  , int8
  , int16
  , int32
  , int64
  , word
  , word8
  , word16
  , word32
  , word64
  , bytestring
  , lazyText
  , lazyBytestring
  , string
  , json
  , timeOfDay
  , utcTime
  , uuid
  , day

  ) where

import Amazonka.Data.Base64
import Control.Applicative
import Data.ByteString (ByteString)
import Data.Int
import Data.RdsData.Decode.Array (DecodeArray(..))
import Data.RdsData.Internal.Aeson
import Data.RdsData.Types.Value
import Data.Text (Text)
import Data.Time
import Data.UUID (UUID)
import Data.Word
import Prelude hiding (maybe, null)

import qualified Amazonka.Data.ByteString       as AWS
import qualified Data.Aeson                     as J
import qualified Data.ByteString.Lazy           as LBS
import qualified Data.RdsData.Internal.Convert  as CONV
import qualified Data.Text                      as T
import qualified Data.Text.Encoding             as T
import qualified Data.Text.Lazy                 as LT
import qualified Data.UUID                      as UUID
import qualified Prelude                        as P

newtype DecodeValue a = DecodeValue
  { decodeValue :: Value -> Either Text a
  } deriving Functor

instance Applicative DecodeValue where
  pure a = DecodeValue \_ -> Right a
  DecodeValue f <*> DecodeValue a = DecodeValue \v -> f v <*> a v

instance Alternative DecodeValue where
  empty = DecodeValue \_ -> Left "empty"
  DecodeValue a <|> DecodeValue b = DecodeValue \v ->
    either (const (b v)) Right (a v)

instance Monad DecodeValue where
  DecodeValue a >>= f = DecodeValue \v -> do
    a' <- a v
    decodeValue (f a') v

--------------------------------------------------------------------------------

rdsValue :: DecodeValue Value
rdsValue =
  DecodeValue Right

decodeValueFailedMessage :: Text -> Text -> Maybe Text -> Value -> Text
decodeValueFailedMessage item type_ reason value =
  mconcat
    [ "Failed to decode " <> item <> " of type " <> type_ <> " from Value of " <> toJsonText value
    , P.maybe "" (" because " <>) reason
    ]

decodeValueFailed :: Text -> Text -> Maybe Text -> DecodeValue a
decodeValueFailed value type_ reason =
  DecodeValue $ Left . decodeValueFailedMessage value type_ reason

--------------------------------------------------------------------------------

maybe :: DecodeValue a -> DecodeValue (Maybe a)
maybe (DecodeValue f) =
  DecodeValue \v ->
    case v of
      ValueOfNull -> Right Nothing
      _ -> Just <$> f v  

--------------------------------------------------------------------------------

array :: DecodeArray a -> DecodeValue a
array decoder =
  DecodeValue \v ->
    case v of
      ValueOfArray a -> decodeArray decoder a
      _ -> Left $ decodeValueFailedMessage "array" "Array" Nothing v

base64 :: DecodeValue Base64
base64 =
  DecodeValue \v ->
    case v of
      ValueOfBase64 b64 -> Right b64
      _ -> Left $ decodeValueFailedMessage "base64" "Base64" Nothing v

bool :: DecodeValue Bool
bool =
  DecodeValue \v ->
    case v of
      ValueOfBool b -> Right b
      _ -> Left $ decodeValueFailedMessage "bool" "Bool" Nothing v

double :: DecodeValue Double
double =
  DecodeValue \v ->
    case v of
      ValueOfDouble n -> Right n
      ValueOfText t ->
        case CONV.textToDouble t of
          Just n -> Right n
          Nothing -> Left $ decodeValueFailedMessage "double" "Double" (Just "failed to parse text as double") v
      _ -> Left $ decodeValueFailedMessage "double" "Double" Nothing v

text :: DecodeValue Text
text =
  DecodeValue \v ->
    case v of
      ValueOfText s -> Right s
      _ -> Left $ decodeValueFailedMessage "text" "Text" Nothing v

integer :: DecodeValue Integer
integer =
  DecodeValue \v ->
    case v of
      ValueOfInteger n -> Right n
      _ -> Left $ decodeValueFailedMessage "integer" "Integer" Nothing v

null :: DecodeValue ()
null =
  DecodeValue \v ->
    case v of
      ValueOfNull -> Right ()
      _ -> Left $ decodeValueFailedMessage "null" "()" Nothing v

--------------------------------------------------------------------------------

int :: DecodeValue Int
int =
  asum
    [ fromIntegral <$> int64
    , decodeValueFailed "int" "Int" Nothing
    ]

int8 :: DecodeValue Int8
int8 =
  asum
    [ fromIntegral <$> integer
    , decodeValueFailed "int8" "Int8" Nothing
    ]

int16 :: DecodeValue Int16
int16 =
  asum
    [ fromIntegral <$> integer
    , decodeValueFailed "int16" "Int16" Nothing
    ]

int32 :: DecodeValue Int32
int32 =
  asum
    [ fromIntegral <$> integer
    , decodeValueFailed "int32" "Int32" Nothing
    ]

int64 :: DecodeValue Int64
int64 =
  asum
    [ fromIntegral <$> integer
    , decodeValueFailed "int64" "Int64" Nothing
    ]

word :: DecodeValue Word
word =
  asum
    [ fromIntegral <$> integer
    , decodeValueFailed "word" "Word" Nothing
    ]

word8 :: DecodeValue Word8
word8 =
  asum
    [ fromIntegral <$> integer
    , decodeValueFailed "word8" "Word8" Nothing
    ]

word16 :: DecodeValue Word16
word16 =
  asum
    [ fromIntegral <$> integer
    , decodeValueFailed "word16" "Word16" Nothing
    ]

word32 :: DecodeValue Word32
word32 =
  asum
    [ fromIntegral <$> integer
    , decodeValueFailed "word32" "Word32" Nothing
    ]

word64 :: DecodeValue Word64
word64 =
  asum
    [ fromIntegral <$> integer
    , decodeValueFailed "word64" "Word64" Nothing
    ]

bytestring :: DecodeValue ByteString
bytestring =
  AWS.toBS <$> base64

lazyText :: DecodeValue LT.Text
lazyText =
  LT.fromStrict <$> text

lazyBytestring :: DecodeValue LBS.ByteString
lazyBytestring =
  LBS.fromStrict <$> bytestring

string :: DecodeValue String
string =
  T.unpack <$> text

json :: DecodeValue J.Value
json = do
  t <- text
  case J.eitherDecode (LBS.fromStrict (T.encodeUtf8 t)) of
    Right v -> pure v
    Left e -> decodeValueFailed "json" "Value" (Just (T.pack e))

timeOfDay :: DecodeValue TimeOfDay
timeOfDay = do
  t <- text
  case parseTimeM True defaultTimeLocale "%H:%M:%S%Q" (T.unpack t) of
    Just a -> pure a
    Nothing -> decodeValueFailed "timeOfDay" "TimeOfDay" (Just (T.pack (show t)))

utcTime :: DecodeValue UTCTime
utcTime = do
  t <- text
  case parseTimeM True defaultTimeLocale "%Y-%m-%d %H:%M:%S" (T.unpack t) of
    Just a -> pure a
    Nothing -> decodeValueFailed "utcTime" "UTCTime" Nothing

uuid :: DecodeValue UUID
uuid = do
  t <- text
  case UUID.fromString (T.unpack t) of
    Just a -> pure a
    Nothing -> decodeValueFailed "uuid" "UUID" Nothing

day :: DecodeValue Day
day = do
  t <- text
  case parseTimeM True defaultTimeLocale "%Y-%m-%d" (T.unpack t) of
    Just a -> pure a
    Nothing -> decodeValueFailed "day" "Day" Nothing