packages feed

cretheus-1.2.0: src/Cretheus/Internal/Decode.hs

module Cretheus.Internal.Decode
  ( Decoder,
    ObjectDecoder,
    array,
    bool,
    double,
    float,
    fromBytes,
    fromLazyBytes,
    fromText,
    fromValue,
    int,
    int32,
    int64,
    integer,
    keyMap,
    list,
    map,
    null,
    nullable,
    object,
    optionalProperty,
    property,
    refine,
    set,
    text,
    tuple2,
    tuple3,
    tuple4,
    tuple5,
    tuple6,
    tuple7,
    tuple8,
    tuple9,
    utcTime,
    value,
    vector,
  )
where

import Control.Monad qualified as Monad
import Data.Aeson qualified as Aeson
import Data.Aeson.KeyMap qualified as Aeson (KeyMap)
import Data.Aeson.KeyMap qualified as Aeson.KeyMap
import Data.Aeson.Types qualified as Aeson
import Data.ByteString (ByteString)
import Data.ByteString.Lazy qualified as Lazy (ByteString)
import Data.Coerce (coerce)
import Data.Data (Proxy (..))
import Data.Int (Int32, Int64)
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Primitive.Array (Array)
import Data.Reflection (Reifies (reflect), reify)
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text (Text)
import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text
import Data.Time (UTCTime)
import Data.Vector (Vector)
import Data.Vector qualified as Vector
import Prelude hiding (map, null)

newtype GDecoder a b = GDecoder
  { unGDecoder :: a -> Aeson.Parser b
  }
  deriving stock (Functor)

instance Applicative (GDecoder a) where
  pure x = GDecoder \_ -> pure x
  (<*>) = Monad.ap

instance Monad (GDecoder a) where
  return = pure
  GDecoder mx >>= f =
    GDecoder \i ->
      mx i >>= \x ->
        unGDecoder (f x) i

-- | A value decoder.
newtype Decoder a
  = Decoder (Aeson.Value -> Aeson.Parser a)
  deriving (Applicative, Functor, Monad) via (GDecoder Aeson.Value)

-- | An object decoder.
newtype ObjectDecoder a
  = ObjectDecoder (Aeson.Object -> Aeson.Parser a)
  deriving (Applicative, Functor, Monad) via (GDecoder Aeson.Object)

-- This didn't suck as bad before aeson-2.2, when they got rid of `eitherDecodeWith`...
newtype D (s :: k) a = D a

instance (Reifies s (Decoder a)) => Aeson.FromJSON (D s a) where
  parseJSON :: Aeson.Value -> Aeson.Parser (D s a)
  parseJSON =
    coerce
      @(Decoder a)
      @(Aeson.Value -> Aeson.Parser (D s a))
      (reflect (Proxy :: Proxy s))

-- | Decode bytes.
fromBytes :: Decoder a -> ByteString -> Either Text a
fromBytes decoder bytes =
  reify decoder \(_ :: Proxy s) ->
    case Aeson.eitherDecodeStrict bytes of
      Left err -> Left (Text.pack err)
      Right (D result :: D s a) -> Right result
{-# INLINEABLE fromBytes #-}

-- | Decode lazy bytes.
fromLazyBytes :: Decoder a -> Lazy.ByteString -> Either Text a
fromLazyBytes decoder bytes =
  reify decoder \(_ :: Proxy s) ->
    case Aeson.eitherDecode bytes of
      Left err -> Left (Text.pack err)
      Right (D result :: D s a) -> Right result
{-# INLINEABLE fromLazyBytes #-}

-- | Decode text.
fromText :: Decoder a -> Text -> Either Text a
fromText decoder str =
  fromBytes decoder (Text.encodeUtf8 str)
{-# INLINEABLE fromText #-}

-- | Decode a value.
fromValue :: Decoder a -> Aeson.Value -> Either Text a
fromValue (Decoder decoder) val =
  case Aeson.parseEither decoder val of
    Left err -> Left (Text.pack err)
    Right result -> Right result
{-# INLINEABLE fromValue #-}

-- | A value decoder.
value :: Decoder Aeson.Value
value =
  Decoder Aeson.parseJSON
{-# INLINEABLE value #-}

-- | A bool decoder.
bool :: Decoder Bool
bool =
  Decoder Aeson.parseJSON
{-# INLINEABLE bool #-}

-- | An int decoder.
int :: Decoder Int
int =
  Decoder Aeson.parseJSON
{-# INLINEABLE int #-}

-- | A 32-bit int decoder.
int32 :: Decoder Int32
int32 =
  Decoder Aeson.parseJSON
{-# INLINEABLE int32 #-}

-- | A 64-bit int decoder.
int64 :: Decoder Int64
int64 =
  Decoder Aeson.parseJSON
{-# INLINEABLE int64 #-}

-- | An integer decoder.
integer :: Decoder Integer
integer =
  Decoder Aeson.parseJSON
{-# INLINEABLE integer #-}

-- | A 32-bit float decoder.
float :: Decoder Float
float =
  Decoder Aeson.parseJSON
{-# INLINEABLE float #-}

-- | A 64-bit float decoder.
double :: Decoder Double
double =
  Decoder Aeson.parseJSON
{-# INLINEABLE double #-}

-- | A text decoder.
text :: Decoder Text
text =
  Decoder Aeson.parseJSON
{-# INLINEABLE text #-}

-- | A timestamp decoder (ISO 8601).
utcTime :: Decoder UTCTime
utcTime =
  Decoder Aeson.parseJSON
{-# INLINEABLE utcTime #-}

-- | A list decoder.
list :: Decoder a -> Decoder [a]
list =
  fmap Vector.toList . vector
{-# INLINEABLE list #-}

-- | An array decoder.
array :: Decoder a -> Decoder (Array a)
array (Decoder f) =
  Decoder (Aeson.withArray "" (traverse f . Vector.toArray))
{-# INLINEABLE array #-}

-- | A vector decoder.
vector :: Decoder a -> Decoder (Vector a)
vector (Decoder f) =
  Decoder (Aeson.withArray "" (traverse f))
{-# INLINEABLE vector #-}

-- | A set decoder.
set :: (Ord a) => Decoder a -> Decoder (Set a)
set =
  fmap Set.fromList . list
{-# INLINEABLE set #-}

-- | A 2-tuple decoder.
tuple2 :: (a -> b -> c) -> Decoder a -> Decoder b -> Decoder c
tuple2 f (Decoder fa) (Decoder fb) =
  Decoder
    ( Aeson.withArray "" \v ->
        if Vector.length v == 2
          then
            f
              <$> fa (Vector.unsafeIndex v 0)
              <*> fb (Vector.unsafeIndex v 1)
          else
            fail ("expected 2-element array, but found " ++ show (Vector.length v))
    )
{-# INLINEABLE tuple2 #-}

-- | A 3-tuple decoder.
tuple3 :: (a -> b -> c -> d) -> Decoder a -> Decoder b -> Decoder c -> Decoder d
tuple3 f (Decoder fa) (Decoder fb) (Decoder fc) =
  Decoder
    ( Aeson.withArray "" \v ->
        if Vector.length v == 3
          then
            f
              <$> fa (Vector.unsafeIndex v 0)
              <*> fb (Vector.unsafeIndex v 1)
              <*> fc (Vector.unsafeIndex v 2)
          else
            fail ("expected 3-element array, but found " ++ show (Vector.length v))
    )
{-# INLINEABLE tuple3 #-}

-- | A 4-tuple decoder.
tuple4 :: (a -> b -> c -> d -> e) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e
tuple4 f (Decoder fa) (Decoder fb) (Decoder fc) (Decoder fd) =
  Decoder
    ( Aeson.withArray "" \v ->
        if Vector.length v == 4
          then
            f
              <$> fa (Vector.unsafeIndex v 0)
              <*> fb (Vector.unsafeIndex v 1)
              <*> fc (Vector.unsafeIndex v 2)
              <*> fd (Vector.unsafeIndex v 3)
          else
            fail ("expected 4-element array, but found " ++ show (Vector.length v))
    )
{-# INLINEABLE tuple4 #-}

-- | A 5-tuple decoder.
tuple5 :: (a -> b -> c -> d -> e -> f) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f
tuple5 f (Decoder fa) (Decoder fb) (Decoder fc) (Decoder fd) (Decoder fe) =
  Decoder
    ( Aeson.withArray "" \v ->
        if Vector.length v == 5
          then
            f
              <$> fa (Vector.unsafeIndex v 0)
              <*> fb (Vector.unsafeIndex v 1)
              <*> fc (Vector.unsafeIndex v 2)
              <*> fd (Vector.unsafeIndex v 3)
              <*> fe (Vector.unsafeIndex v 4)
          else
            fail ("expected 5-element array, but found " ++ show (Vector.length v))
    )
{-# INLINEABLE tuple5 #-}

-- | A 6-tuple decoder.
tuple6 ::
  (a -> b -> c -> d -> e -> f -> g) ->
  Decoder a ->
  Decoder b ->
  Decoder c ->
  Decoder d ->
  Decoder e ->
  Decoder f ->
  Decoder g
tuple6 f (Decoder fa) (Decoder fb) (Decoder fc) (Decoder fd) (Decoder fe) (Decoder ff) =
  Decoder
    ( Aeson.withArray "" \v ->
        if Vector.length v == 6
          then
            f
              <$> fa (Vector.unsafeIndex v 0)
              <*> fb (Vector.unsafeIndex v 1)
              <*> fc (Vector.unsafeIndex v 2)
              <*> fd (Vector.unsafeIndex v 3)
              <*> fe (Vector.unsafeIndex v 4)
              <*> ff (Vector.unsafeIndex v 5)
          else
            fail ("expected 6-element array, but found " ++ show (Vector.length v))
    )
{-# INLINEABLE tuple6 #-}

-- | A 7-tuple decoder.
tuple7 ::
  (a -> b -> c -> d -> e -> f -> g -> h) ->
  Decoder a ->
  Decoder b ->
  Decoder c ->
  Decoder d ->
  Decoder e ->
  Decoder f ->
  Decoder g ->
  Decoder h
tuple7 f (Decoder fa) (Decoder fb) (Decoder fc) (Decoder fd) (Decoder fe) (Decoder ff) (Decoder fg) =
  Decoder
    ( Aeson.withArray "" \v ->
        if Vector.length v == 7
          then
            f
              <$> fa (Vector.unsafeIndex v 0)
              <*> fb (Vector.unsafeIndex v 1)
              <*> fc (Vector.unsafeIndex v 2)
              <*> fd (Vector.unsafeIndex v 3)
              <*> fe (Vector.unsafeIndex v 4)
              <*> ff (Vector.unsafeIndex v 5)
              <*> fg (Vector.unsafeIndex v 6)
          else
            fail ("expected 7-element array, but found " ++ show (Vector.length v))
    )
{-# INLINEABLE tuple7 #-}

-- | A 8-tuple decoder.
tuple8 ::
  (a -> b -> c -> d -> e -> f -> g -> h -> i) ->
  Decoder a ->
  Decoder b ->
  Decoder c ->
  Decoder d ->
  Decoder e ->
  Decoder f ->
  Decoder g ->
  Decoder h ->
  Decoder i
tuple8 f (Decoder fa) (Decoder fb) (Decoder fc) (Decoder fd) (Decoder fe) (Decoder ff) (Decoder fg) (Decoder fh) =
  Decoder
    ( Aeson.withArray "" \v ->
        if Vector.length v == 8
          then
            f
              <$> fa (Vector.unsafeIndex v 0)
              <*> fb (Vector.unsafeIndex v 1)
              <*> fc (Vector.unsafeIndex v 2)
              <*> fd (Vector.unsafeIndex v 3)
              <*> fe (Vector.unsafeIndex v 4)
              <*> ff (Vector.unsafeIndex v 5)
              <*> fg (Vector.unsafeIndex v 6)
              <*> fh (Vector.unsafeIndex v 7)
          else
            fail ("expected 8-element array, but found " ++ show (Vector.length v))
    )
{-# INLINEABLE tuple8 #-}

-- | A 9-tuple decoder.
tuple9 ::
  (a -> b -> c -> d -> e -> f -> g -> h -> i -> j) ->
  Decoder a ->
  Decoder b ->
  Decoder c ->
  Decoder d ->
  Decoder e ->
  Decoder f ->
  Decoder g ->
  Decoder h ->
  Decoder i ->
  Decoder j
tuple9 f (Decoder fa) (Decoder fb) (Decoder fc) (Decoder fd) (Decoder fe) (Decoder ff) (Decoder fg) (Decoder fh) (Decoder fi) =
  Decoder
    ( Aeson.withArray "" \v ->
        if Vector.length v == 9
          then
            f
              <$> fa (Vector.unsafeIndex v 0)
              <*> fb (Vector.unsafeIndex v 1)
              <*> fc (Vector.unsafeIndex v 2)
              <*> fd (Vector.unsafeIndex v 3)
              <*> fe (Vector.unsafeIndex v 4)
              <*> ff (Vector.unsafeIndex v 5)
              <*> fg (Vector.unsafeIndex v 6)
              <*> fh (Vector.unsafeIndex v 7)
              <*> fi (Vector.unsafeIndex v 8)
          else
            fail ("expected 9-element array, but found " ++ show (Vector.length v))
    )
{-# INLINEABLE tuple9 #-}

-- | An object decoder.
object :: ObjectDecoder a -> Decoder a
object (ObjectDecoder f) =
  Decoder (Aeson.withObject "" f)
{-# INLINEABLE object #-}

-- | An object property decoder.
property :: Aeson.Key -> Decoder a -> ObjectDecoder a
property k (Decoder f) =
  ObjectDecoder \o -> Aeson.explicitParseField f o k
{-# INLINEABLE property #-}

-- | An optional object property decoder.
optionalProperty :: Aeson.Key -> Decoder a -> ObjectDecoder (Maybe a)
optionalProperty k (Decoder f) =
  ObjectDecoder \o -> Aeson.explicitParseFieldMaybe' f o k
{-# INLINEABLE optionalProperty #-}

-- | A map decoder.
map :: (Ord k) => (Aeson.Key -> k) -> Decoder a -> Decoder (Map k a)
map fromKey (Decoder f) =
  object (ObjectDecoder (Aeson.KeyMap.foldrWithKey (\k v -> liftA2 (Map.insert (fromKey k)) (f v)) (pure Map.empty)))
{-# INLINEABLE map #-}

-- | A key map decoder.
keyMap :: Decoder a -> Decoder (Aeson.KeyMap a)
keyMap (Decoder f) =
  object (ObjectDecoder (Aeson.KeyMap.traverse f))
{-# INLINEABLE keyMap #-}

-- | A null decoder.
null :: Decoder ()
null =
  Decoder \case
    Aeson.Null -> pure ()
    _ -> fail "expected null"
{-# INLINEABLE null #-}

-- | A nullable decoder.
nullable :: Decoder v -> Decoder (Maybe v)
nullable (Decoder f) =
  Decoder \case
    Aeson.Null -> pure Nothing
    val -> Just <$> f val
{-# INLINEABLE nullable #-}

-- | Refine a decoder with a predicate.
refine :: (a -> Either Text b) -> Decoder a -> Decoder b
refine p (Decoder f) =
  Decoder \val -> do
    x <- f val
    case p x of
      Left err -> fail (Text.unpack err)
      Right y -> pure y
{-# INLINEABLE refine #-}