packages feed

candid-0.2: src/Codec/Candid/Class.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS -Wno-orphans -Wno-deprecations #-}
-- | This (internal) module contains the encoding and decoding, as well
-- as the relevant classes
module Codec.Candid.Class where

import Numeric.Natural
import qualified Data.Vector as Vec
import qualified Data.Text as T
import qualified Data.ByteString as SBS
import qualified Data.ByteString.Lazy as BS
import qualified Data.ByteString.Builder as B
import Data.Row
import qualified Data.Row.Records as R
import qualified Data.Row.Internal as R
import qualified Data.Row.Variants as V
import Data.Row.Internal (metamorph)
import Control.Monad.State.Lazy
import Control.Monad.Trans.Error
import Control.Applicative ((<|>))
import Data.Functor.Const
import Data.Bifunctor
import Data.Proxy
import Data.Typeable
import Data.Scientific
import Data.Word
import Data.Int
import Data.Void
import Data.Text.Prettyprint.Doc
import Language.Haskell.TH (mkName, tupleDataName)
import Language.Haskell.TH.Lib
  ( appT, tupleT, varT, litT, strTyLit
  , tupP, varP, wildP, infixP
  , labelE, varE, conE, tupE, listE, uInfixE
  )

import Codec.Candid.Tuples
import Codec.Candid.Data
import Codec.Candid.TypTable
import Codec.Candid.Types
import Codec.Candid.FieldName
import Codec.Candid.Decode
import Codec.Candid.Encode
import Codec.Candid.Coerce

-- | Encode based on Haskell type
encode :: CandidArg a => a -> BS.ByteString
encode = B.toLazyByteString . encodeBuilder

-- | Encode to a 'B.Builder' based on Haskell type
encodeBuilder :: forall a. CandidArg a => a -> B.Builder
encodeBuilder x = encodeValues (seqDesc @a) (toCandidVals x)

-- | Decode to Haskell type
decode :: forall a. CandidArg a => BS.ByteString -> Either String a
decode b = do
    -- Decode
    (ts, vs) <- decodeVals b
    -- Coerce to expected type
    c <- coerceSeqDesc ts (buildSeqDesc (asTypes @(AsTuple a)))
    vs' <- c vs
    fromCandidVals vs'

-- | Decode (dynamic) values to Haskell type
--
-- This applies some best-effort subtyping/coercion, suitable for liberal
-- parsing of the textual representation, but not the coercion algorithm as
-- specified in the specification, which requires a provided type.
fromCandidVals :: CandidArg a => [Value] -> Either String a
fromCandidVals = fromVals >=> return . fromTuple

-- | Turn haskell types into a dynamic Candid value. This may lose type information.
toCandidVals :: CandidArg a => a -> [Value]
toCandidVals = seqVal . asTuple

-- Using normal Haskell values

-- | The class of types that can be used as Candid argument sequences.
-- Essentially all types that are in 'Candid', but tuples need to be treated specially.
type CandidArg a = (CandidSeq (AsTuple a), Tuplable a, Typeable a)


class CandidSeq a where
    asTypes :: [Type (Ref TypeRep Type)]
    seqVal :: a -> [Value]
    fromVals :: [Value] -> Either String a

-- | Calculate a Candid type description from a Haskell type. The 'SeqDesc'
-- type is roughly @[Type]@, with extra bookkeeping for recursive types
seqDesc :: forall a. CandidArg a => SeqDesc
seqDesc = buildSeqDesc (asTypes @(AsTuple a))

-- | NB: This will loop with recursive types!
typeDesc :: forall a. Candid a => Type Void
typeDesc = asType @(AsCandid a) >>= go
  where go (Ref _ t) = t >>= go

instance Pretty TypeRep where
    pretty = pretty . show

instance CandidSeq () where
    asTypes = []
    seqVal () = []
    fromVals _ = return () -- Subtyping

instance Candid a => CandidSeq (Unary a) where
    asTypes = [asType' @a]
    seqVal (Unary x) = [ toCandidVal x ]
    fromVals (x:_) = Unary <$> fromCandidVal x -- Subtyping
    fromVals _ = Left "Not enough arguments"

-- see below for tuple  instances

data DeserializeError
    = DecodeError String -- ^ fatal
    | CoerceError String Value -- ^ can be recovered
    | MissingFieldError FieldName -- ^ can be recovered
    | UnexpectedTagError FieldName -- ^ can be recovered

-- TODO: Can we get rid of this?
instance Error DeserializeError where strMsg = DecodeError

isRecoverable :: DeserializeError -> Bool
isRecoverable (DecodeError _) = False
isRecoverable _ = True

recoverWith :: a -> Either DeserializeError a -> Either DeserializeError a
recoverWith x (Left e) | isRecoverable e = Right x
recoverWith _ y = y

showDeserializeError :: DeserializeError -> String
showDeserializeError e = case e of
    DecodeError err -> err
    CoerceError t v -> "Cannot coerce " ++ show (pretty v) ++ " into " ++ t
    MissingFieldError f -> "Missing field " ++ show (pretty f)
    UnexpectedTagError f -> "Unexpected tag " ++ show (pretty f)

cannotDecode :: String -> Either DeserializeError a
cannotDecode s = Left (DecodeError s)
cannotCoerce :: String -> Value -> Either DeserializeError a
cannotCoerce t v = Left (CoerceError t v)
missingField :: FieldName -> Either DeserializeError a
missingField f = Left (MissingFieldError f)
unexpectedTag :: FieldName -> Either DeserializeError a
unexpectedTag f = Left (UnexpectedTagError f)

-- | The internal class of Haskell types that canonically map to Candid.
-- You would add instances to the 'Candid' type class.
class Typeable a => CandidVal a where
    asType :: Type (Ref TypeRep Type)
    toCandidVal' :: a -> Value
    fromCandidVal' :: Value -> Either DeserializeError a
    fromMissingField :: Maybe a
    fromMissingField = Nothing

-- | The class of Haskell types that can be converted to Candid.
--
-- You can create intances of this class for your own types, see the tutorial above for examples. The default instance is mostly for internal use.
class (Typeable a, CandidVal (AsCandid a)) => Candid a where
    type AsCandid a
    toCandid :: a -> AsCandid a
    fromCandid :: AsCandid a -> a

    type AsCandid a = a
    default toCandid :: a ~ AsCandid a => a -> AsCandid a
    toCandid = id
    default fromCandid :: a ~ AsCandid a => AsCandid a -> a
    fromCandid = id

toCandidVal :: Candid a => a -> Value
toCandidVal = toCandidVal' . toCandid

fromCandidVal :: Candid a => Value -> Either String a
fromCandidVal = first showDeserializeError . fromCandidVal''

fromCandidVal'' :: Candid a => Value -> Either DeserializeError a
fromCandidVal'' = fmap fromCandid . fromCandidVal'

asType' :: forall a.  Candid a => Type (Ref TypeRep Type)
asType' = RefT (Ref (typeRep (Proxy @(AsCandid a))) (asType @(AsCandid a)))

instance Candid Bool
instance CandidVal Bool where
    asType = BoolT
    toCandidVal' = BoolV
    fromCandidVal' (BoolV b) = Right b
    fromCandidVal' v = cannotCoerce "bool" v

instance Candid Natural
instance CandidVal Natural where
    asType = NatT
    toCandidVal' = NatV
    fromCandidVal' (NumV n)
        | n >= 0, Right i <- floatingOrInteger @Double n = Right i
        | otherwise = cannotDecode $ "Not a natural number: " ++ show n
    fromCandidVal' (NatV n) = Right n
    fromCandidVal' v = cannotCoerce "nat" v

inBounds :: forall a. (Integral a, Bounded a) => Integer -> Either DeserializeError a
inBounds i
    | fromIntegral (minBound :: a) <= i
    , fromIntegral (maxBound :: a) >= i
    = Right (fromIntegral i)
    | otherwise
    = cannotDecode $ "Out of bounds: " ++ show i

instance Candid Word8
instance CandidVal Word8 where
    asType = Nat8T
    toCandidVal' = Nat8V
    fromCandidVal' (NumV n) | Right i <- floatingOrInteger @Double n = inBounds i
    fromCandidVal' (Nat8V n) = Right n
    fromCandidVal' v = cannotCoerce "word8" v

instance Candid Word16
instance CandidVal Word16 where
    asType = Nat16T
    toCandidVal' = Nat16V
    fromCandidVal' (NumV n) | Right i <- floatingOrInteger @Double n = inBounds i
    fromCandidVal' (Nat16V n) = Right n
    fromCandidVal' v = cannotCoerce "word16" v

instance Candid Word32
instance CandidVal Word32 where
    asType = Nat32T
    toCandidVal' = Nat32V
    fromCandidVal' (NumV n) | Right i <- floatingOrInteger @Double n = inBounds i
    fromCandidVal' (Nat32V n) = Right n
    fromCandidVal' v = cannotCoerce "word32" v

instance Candid Word64
instance CandidVal Word64 where
    asType = Nat64T
    toCandidVal' = Nat64V
    fromCandidVal' (NumV n) | Right i <- floatingOrInteger @Double n = inBounds i
    fromCandidVal' (Nat64V n) = Right n
    fromCandidVal' v = cannotCoerce "word64" v

instance Candid Integer
instance CandidVal Integer where
    asType = IntT
    toCandidVal' = IntV
    fromCandidVal' (NumV n)
        | Right i <- floatingOrInteger @Double n = Right i
        | otherwise = cannotDecode $ "Not an integer: " ++ show n
    fromCandidVal' (NatV n) = Right (fromIntegral n)
    fromCandidVal' (IntV n) = Right n
    fromCandidVal' v = cannotCoerce "int" v

instance Candid Int8
instance CandidVal Int8 where
    asType = Int8T
    toCandidVal' = Int8V
    fromCandidVal' (NumV n) | Right i <- floatingOrInteger @Double n = inBounds i
    fromCandidVal' (Int8V n) = Right n
    fromCandidVal' v = cannotCoerce "int8" v

instance Candid Int16
instance CandidVal Int16 where
    asType = Int16T
    toCandidVal' = Int16V
    fromCandidVal' (NumV n) | Right i <- floatingOrInteger @Double n = inBounds i
    fromCandidVal' (Int16V n) = Right n
    fromCandidVal' v = cannotCoerce "int16" v

instance Candid Int32
instance CandidVal Int32 where
    asType = Int32T
    toCandidVal' = Int32V
    fromCandidVal' (NumV n) | Right i <- floatingOrInteger @Double n = inBounds i
    fromCandidVal' (Int32V n) = Right n
    fromCandidVal' v = cannotCoerce "int32" v

instance Candid Int64
instance CandidVal Int64 where
    asType = Int64T
    toCandidVal' = Int64V
    fromCandidVal' (NumV n) | Right i <- floatingOrInteger @Double n = inBounds i
    fromCandidVal' (Int64V n) = Right n
    fromCandidVal' v = cannotCoerce "int64" v

instance Candid Float
instance CandidVal Float where
    asType = Float32T
    toCandidVal' = Float32V
    fromCandidVal' (NumV n) = Right (toRealFloat n)
    fromCandidVal' (Float32V n) = Right n
    fromCandidVal' v = cannotCoerce "float32" v

instance Candid Double
instance CandidVal Double where
    asType = Float64T
    toCandidVal' = Float64V
    fromCandidVal' (NumV n) = Right (toRealFloat n)
    fromCandidVal' (Float64V n) = Right n
    fromCandidVal' v = cannotCoerce "float64" v

instance Candid Void
instance CandidVal Void where
    asType = EmptyT
    toCandidVal' = absurd
    fromCandidVal' v = cannotCoerce "void" v

instance Candid T.Text
instance CandidVal T.Text where
    asType = TextT
    toCandidVal' = TextV
    fromCandidVal' (TextV t) = return t
    fromCandidVal' v = cannotCoerce "text" v

instance Candid BS.ByteString
instance CandidVal BS.ByteString where
    asType = BlobT
    toCandidVal' = BlobV
    fromCandidVal' (VecV v) =  BS.pack . Vec.toList <$> mapM (fromCandidVal'' @Word8) v
    fromCandidVal' (BlobV t) = return t
    fromCandidVal' v = cannotCoerce "blob" v

instance Candid Principal
instance CandidVal Principal where
    asType = PrincipalT
    toCandidVal' = PrincipalV
    fromCandidVal' (PrincipalV t) = return t
    fromCandidVal' v = cannotCoerce "principal" v

instance CandidMethodsRow r => Candid (ServiceRef r)
instance CandidMethodsRow r => CandidVal (ServiceRef r) where
    asType = ServiceT (methodsOfRow @r)
    toCandidVal' (ServiceRef p) = ServiceV p
    fromCandidVal' (ServiceV p) = return (ServiceRef p)
    fromCandidVal' v = cannotCoerce "service" v

instance (CandidMethodType mt) => Candid (FuncRef mt)
instance (CandidMethodType mt) => CandidVal (FuncRef mt) where
    asType = FuncT (asMethodType @mt)
    toCandidVal' (FuncRef p n) = FuncV p n
    fromCandidVal' (FuncV p n) = return (FuncRef p n)
    fromCandidVal' v = cannotCoerce "func" v

instance Candid Reserved
instance CandidVal Reserved where
    asType = ReservedT
    toCandidVal' Reserved = ReservedV
    fromCandidVal' _ = return Reserved
    fromMissingField = Just Reserved

instance Candid a => Candid (Maybe a)
instance Candid a => CandidVal (Maybe a) where
    asType = OptT (asType' @a)
    toCandidVal' = OptV . fmap toCandidVal
    fromCandidVal' (OptV x) = recoverWith Nothing $
        traverse fromCandidVal'' x
    fromCandidVal' NullV = return Nothing
    fromCandidVal' ReservedV = return Nothing
    fromCandidVal' v = case asType @(AsCandid a) of
        OptT _    -> pure Nothing
        NullT     -> pure Nothing
        ReservedT -> pure Nothing
        _         -> recoverWith Nothing $
            Just <$> fromCandidVal'' v
    fromMissingField = Just Nothing



instance Candid a => Candid (Vec.Vector a)
instance Candid a => CandidVal (Vec.Vector a) where
    asType = VecT (asType' @a)
    toCandidVal' = VecV . fmap toCandidVal
    fromCandidVal' (VecV x) = traverse fromCandidVal'' x
    fromCandidVal' (BlobV b) = traverse (fromCandidVal'' . Nat8V) $ Vec.fromList $ BS.unpack b
    fromCandidVal' v = cannotCoerce "vec" v

-- | Maybe a bit opinionated, but 'null' seems to be the unit of Candid
instance Candid ()
instance CandidVal () where
    asType = NullT
    toCandidVal' () = NullV
    fromCandidVal' NullV = Right ()
    fromCandidVal' v = cannotCoerce "null" v

-- row-types integration

fieldsOfRow :: forall r. Forall r Candid => Fields (Ref TypeRep Type)
fieldsOfRow = getConst $ metamorph @_ @r @Candid @(,) @(Const ()) @(Const (Fields (Ref TypeRep Type))) @Proxy Proxy doNil doUncons doCons (Const ())
      where
        doNil :: Const () Empty -> Const (Fields (Ref TypeRep Type)) Empty
        doNil = const $ Const []
        doUncons :: forall l t r. (KnownSymbol l, Candid t, HasType l t r)
                 => Label l -> Const () r -> (Const () (r .- l), Proxy t)
        doUncons _ _ = (Const (), Proxy)
        doCons :: forall l t r. (KnownSymbol l, Candid t)
               => Label l -> (Const (Fields (Ref TypeRep Type)) r, Proxy t) -> Const (Fields (Ref TypeRep Type)) (R.Extend l t r)
        doCons l (Const lst, Proxy) = Const $ (unescapeFieldName (R.toKey l), asType' @t) : lst

class Typeable a => KnownAnnotation a where isTrue :: Bool
-- | Type-level 'True', to be used in method types annotations
data AnnTrue
-- | Type-level 'False', to be used in method types annotations
data AnnFalse
instance KnownAnnotation AnnTrue where isTrue = True
instance KnownAnnotation AnnFalse where isTrue = False

class Typeable a => CandidMethodType a where
    asMethodType :: MethodType (Ref TypeRep Type)

instance (CandidArg a, CandidArg b, KnownAnnotation q, KnownAnnotation o) => CandidMethodType (a, b, q, o) where
    asMethodType = MethodType (asTypes @(AsTuple a)) (asTypes @(AsTuple b)) (isTrue @q) (isTrue @o)

methodsOfRow :: forall r. Forall r CandidMethodType => [(T.Text, MethodType (Ref TypeRep Type))]
methodsOfRow = getConst $ metamorph @_ @r @CandidMethodType @(,) @(Const ()) @(Const [(T.Text, MethodType (Ref TypeRep Type))]) @Proxy Proxy doNil doUncons doCons (Const ())
      where
        doNil :: Const () Empty -> Const [(T.Text, MethodType (Ref TypeRep Type))] Empty
        doNil = const $ Const []
        doUncons :: forall l t r. (KnownSymbol l, CandidMethodType t, HasType l t r)
                 => Label l -> Const () r -> (Const () (r .- l), Proxy t)
        doUncons _ _ = (Const (), Proxy)
        doCons :: forall l t r. (KnownSymbol l, CandidMethodType t)
               => Label l -> (Const [(T.Text, MethodType (Ref TypeRep Type))] r, Proxy t) -> Const [(T.Text, MethodType (Ref TypeRep Type))] (R.Extend l t r)
        doCons l (Const lst, Proxy) = Const $ (R.toKey l, asMethodType @t) : lst

type CandidRow r = (Typeable r, AllUniqueLabels r, AllUniqueLabels (V.Map (Either String) r), Forall r Candid, Forall r R.Unconstrained1)
type CandidMethodsRow r = (Typeable r, AllUniqueLabels r, AllUniqueLabels (V.Map (Either String) r), Forall r CandidMethodType, Forall r R.Unconstrained1)

instance CandidRow r => Candid (Rec r)
instance CandidRow r => CandidVal (Rec r) where
    asType = RecT $ fieldsOfRow @r

    toCandidVal' = do
        RecV . fmap (first unescapeFieldName) . R.eraseWithLabels @Candid @r @T.Text @Value toCandidVal

    fromCandidVal' = \case
        RecV m -> toRowRec m
        TupV m -> toRowRec (zip (map hashedField [0..]) m)
        v -> cannotCoerce "record" v
      where
        toRowRec m = R.fromLabelsA @Candid $ \l ->
            let fn = unescapeFieldName (R.toKey l) in
            case lookup fn m of
                Just v -> fromCandidVal'' v
                Nothing -> case fromMissingField of
                    Just v -> return (fromCandid v)
                    Nothing -> missingField fn

instance CandidRow r => Candid (V.Var r)
instance CandidRow r => CandidVal (V.Var r) where
    asType = VariantT $ fieldsOfRow @r

    toCandidVal' v = VariantV (unescapeFieldName t) val
      where (t, val) = V.eraseWithLabels @Candid toCandidVal v

    fromCandidVal' (VariantV f v) = do
        needle :: V.Var (V.Map (Either DeserializeError) r) <-
            (V.fromLabelsMap @Candid @_ @_ @r $ \l -> do
                guard (f == unescapeFieldName (R.toKey l))
                return $ fromCandidVal'' v
            ) <|> unexpectedTag f
        V.sequence (needle :: V.Var (V.Map (Either DeserializeError) r))
    fromCandidVal' v = cannotCoerce "variant" v


-- Derived forms

instance Candid SBS.ByteString where
    type AsCandid SBS.ByteString = BS.ByteString
    toCandid = BS.fromStrict
    fromCandid = BS.toStrict

-- Tuples, generated by TH

-- This is what it looks like:
instance (Candid a, Candid b) => Candid (a, b) where
    type AsCandid (a,b) = Rec ("_0_" .== a .+ "_1_" .== b)
    toCandid (a,b) = #_0_ .== a .+ #_1_ .== b
    fromCandid r = (r .! #_0_, r .! #_1_)

instance (Candid a, Candid b) => CandidSeq (a, b) where
    asTypes = [asType' @a, asType' @b]
    seqVal (x, y) = [ toCandidVal x, toCandidVal y ]
    fromVals (x:y:_) = (,) <$> fromCandidVal x <*> fromCandidVal y
    fromVals _ = Left "Not enough arguments"

$(
  let tupT ts  = foldl appT (tupleT (length ts)) ts in
  let fieldLabelT n = litT $ strTyLit ("_" ++ show (n::Int) ++ "_") in
  let fieldLabelE n = labelE ("_" ++ show (n::Int) ++ "_") in

  fmap concat . sequence $
  [
    let names = take n $ map (mkName . (:[])) ['a'..]
        tvs = map varT names
        pvs = map varP names
        vs  = map varE names
    in [d|
      instance  $(tupT [ [t|Candid $v |] | v <- tvs ]) => Candid $(tupT tvs) where
        type AsCandid $(tupT tvs) =
          Rec $(
            foldr1 (\a b -> [t| $a .+ $b |])
              [ [t| $(fieldLabelT n) .== $b |]
              | (n,b) <- zip [0..] tvs ])
        toCandid $(tupP pvs) =
          $( foldr1 (\a b -> [| $a .+ $b |])
              [ [| $(fieldLabelE n) .== $b |]
              | (n,b) <- zip [0..] vs ])
        fromCandid $(varP (mkName "r")) =
          $( tupE [ [| $(varE (mkName "r")) .! $(fieldLabelE n) |]
                  | (n,_) <- zip [0..] vs])

      instance  $(tupT [ [t|Candid $v |] | v <- tvs ]) => CandidSeq $(tupT tvs) where
        asTypes = $(listE [ [| asType' @ $v |] | v <- tvs ])
        seqVal $(tupP pvs) = $(listE [ [| toCandidVal $v |] | v <- vs ])
        fromVals $(foldr (`infixP` '(:)) wildP pvs)
          = $( foldl (`uInfixE` varE '(<*>))
                [| pure $(conE (tupleDataName n)) |]
                [ [| fromCandidVal $v |] | v <- vs ] )
        fromVals _ = Left "Not enough arguments"
     |]
  | n <- [3..15]
  ]
 )


instance Candid a => Candid [a] where
    type AsCandid [a] = Vec.Vector a
    toCandid = Vec.fromList
    fromCandid = Vec.toList


instance (Candid a, Candid b) => Candid (Either a b) where
    type AsCandid (Either a b) = V.Var ("Left" V..== a V..+ "Right" V..== b)
    toCandid (Left x) = IsJust (Label @"Left") x
    toCandid (Right x) = IsJust (Label @"Right") x
    fromCandid v = switch v $ empty
        .+ Label @"Left" .== Left
        .+ Label @"Right" .== Right