packages feed

candid-0.1: src/Codec/Candid/Service.hs

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ConstraintKinds #-}
module Codec.Candid.Service where

import qualified Data.Text as T
import qualified Data.HashMap.Strict as H
import qualified Data.ByteString.Lazy as BS
import Data.Row
import Data.Row.Records
import Data.Row.Internal

import Codec.Candid.Class

-- | A raw service, operating on bytes
type RawService m = T.Text -> BS.ByteString -> m BS.ByteString
type RawMethod m = BS.ByteString -> m BS.ByteString

class CandidMethod (m :: * -> *) f  | f -> m where
  fromMeth :: (forall a. String -> m a) -> f -> RawMethod m
  toMeth :: (forall a. String -> m a) -> RawMethod m -> f

instance (CandidArg a, CandidArg b, Monad m) => CandidMethod m (a -> m b) where
  fromMeth onErr m b = case decode b of
    Left err -> onErr err
    Right x -> encode <$> m x

  toMeth onErr f x = do
    b <- f (encode x)
    case decode b of
      Left err -> onErr err
      Right y -> return y

-- | A Candid service. The @r@ describes the type of a 'Rec'.
type CandidService m r = (Forall r (CandidMethod m), AllUniqueLabels r)

-- | Turns a raw service (function operating on bytes) into a typed Candid service (a record of typed methods). The raw service is typically code that talks over the network.
toCandidService ::
  forall m r.
  CandidService m r =>
   -- | What to do if the raw service returns unparsable data
  (forall a. String -> m a) ->
  RawService m ->
  Rec r
toCandidService onErr f = fromLabels @ (CandidMethod m) $ \l ->
  toMeth onErr (f (toKey l))

-- | Turns a typed candid service into a raw service. Typically used in a framework warpping Candid services.
fromCandidService ::
  forall m r.
  CandidService m r =>
  -- | What to do if the method name does not exist
  (forall a. T.Text -> m a) ->
  -- | What to do when the caller provides unparsable data
  (forall a. String -> m a) ->
  Rec r ->
  RawService m
fromCandidService notFound onErr r =
    \meth a -> case H.lookup meth m of
      Just f -> f a
      Nothing -> notFound meth
  where
    m :: H.HashMap T.Text (RawMethod m)
    m = eraseToHashMap @(CandidMethod m) (fromMeth onErr) r