packages feed

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

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

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

module Data.RdsData.Decode.Array
  ( DecodeArray(..)

  , decodeArrayFailedMessage

  , arrays
  , bools
  , doubles
  , integers
  , texts

  , ints
  , int8s
  , int16s
  , int32s
  , int64s
  , words
  , word8s
  , word16s
  , word32s
  , word64s
  , lazyTexts
  , strings
  , jsons
  , timesOfDay
  , utcTimes
  , days
  , uuids
  ) where

import Control.Applicative
import Data.Int
import Data.RdsData.Internal.Aeson
import Data.RdsData.Types.Array
import Data.Text (Text)
import Data.Time
import Data.UUID (UUID)
import Data.Word
import Prelude hiding (maybe, null, words)

import qualified Data.Aeson         as J
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 DecodeArray a = DecodeArray
  { decodeArray :: Array -> Either Text a
  } deriving Functor

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

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

instance Monad DecodeArray where
  DecodeArray a >>= f = DecodeArray \v -> do
    a' <- a v
    decodeArray (f a') v

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

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

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

integers :: DecodeArray [Integer]
integers =
  DecodeArray \v ->
    case v of
      ArrayOfIntegers es -> Right es
      _ -> Left $ decodeArrayFailedMessage "integers" "ArrayOfIntegers" Nothing v

texts :: DecodeArray [Text]
texts =
  DecodeArray \v ->
    case v of
      ArrayOfTexts es -> Right es
      _ -> Left $ decodeArrayFailedMessage "texts" "ArrayOfStrings" Nothing v

bools :: DecodeArray [Bool]
bools =
  DecodeArray \v ->
    case v of
      ArrayOfBools es -> Right es
      _ -> Left $ decodeArrayFailedMessage "bools" "ArrayOfBooleans" Nothing v

doubles :: DecodeArray [Double]
doubles =
  DecodeArray \v ->
    case v of
      ArrayOfDoubles es -> Right es
      _ -> Left $ decodeArrayFailedMessage "doubles" "ArrayOfDoubles" Nothing v

arrays :: DecodeArray [Array]
arrays =
  DecodeArray \v ->
    case v of
      ArrayOfArrays es -> Right es
      _ -> Left $ decodeArrayFailedMessage "arrays" "ArrayOfArrays" Nothing v

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

ints :: DecodeArray [Int]
ints =
  fmap fromIntegral <$> integers

int8s :: DecodeArray [Int8]
int8s =
  fmap fromIntegral <$> integers

int16s :: DecodeArray [Int16]
int16s =
  fmap fromIntegral <$> integers

int32s :: DecodeArray [Int32]
int32s =
  fmap fromIntegral <$> integers

int64s :: DecodeArray [Int64]
int64s =
  fmap fromIntegral <$> integers

words :: DecodeArray [Word]
words =
  fmap fromIntegral <$> integers

word8s :: DecodeArray [Word8]
word8s =
  fmap fromIntegral <$> integers

word16s :: DecodeArray [Word16]
word16s =
  fmap fromIntegral <$> integers

word32s :: DecodeArray [Word32]
word32s =
  fmap fromIntegral <$> integers

word64s :: DecodeArray [Word64]
word64s =
  fmap fromIntegral <$> integers

lazyTexts :: DecodeArray [LT.Text]
lazyTexts =
  fmap LT.fromStrict <$> texts

strings :: DecodeArray [String]
strings =
  fmap T.unpack <$> texts

jsons  :: DecodeArray [J.Value]
jsons = do
  ts <- texts
  case traverse (J.eitherDecodeStrict' . T.encodeUtf8) ts of
    Right js -> pure js
    Left e -> DecodeArray \_ -> Left $ "Failed to decode JSON: " <> T.pack e

timesOfDay :: DecodeArray [TimeOfDay]
timesOfDay = do
  ts <- texts
  case traverse (parseTimeM True defaultTimeLocale "%H:%M:%S". T.unpack) ts of
    Just tod -> pure tod
    Nothing -> DecodeArray \_ -> Left "Failed to decode TimeOfDay"

utcTimes :: DecodeArray [UTCTime]
utcTimes = do
  ts <- texts
  case traverse (parseTimeM True defaultTimeLocale "%Y-%m-%d %H:%M:%S" . T.unpack) ts of
    Just utct -> pure utct
    Nothing -> DecodeArray \_ -> Left "Failed to decode UTCTime"

days :: DecodeArray [Day]
days = do
  ts <- texts
  case traverse (parseTimeM True defaultTimeLocale "%Y-%m-%d" . T.unpack) ts of
    Just d -> pure d
    Nothing -> DecodeArray \_ -> Left "Failed to decode Day"

uuids :: DecodeArray [UUID]
uuids = do
  ts <- texts
  case traverse (UUID.fromString . T.unpack) ts of
    Just u -> pure u
    Nothing -> DecodeArray \_ -> Left "Failed to decode UUID"