packages feed

ribosome-host-0.9.9.9: lib/Ribosome/Host/Handler/Codec.hs

{-# options_haddock prune #-}

module Ribosome.Host.Handler.Codec where

import Data.Aeson (eitherDecodeStrict')
import qualified Data.ByteString as ByteString
import Data.MessagePack (Object)
import qualified Data.Text as Text
import Exon (exon)
import qualified Options.Applicative as Optparse
import Options.Applicative (defaultPrefs, execParserPure, info, renderFailure)

import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode (fromMsgpack))
import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode (toMsgpack))
import Ribosome.Host.Data.Args (ArgList (ArgList), Args (Args), JsonArgs (JsonArgs), OptionParser (optionParser), Options (Options))
import Ribosome.Host.Data.Bang (Bang (NoBang))
import Ribosome.Host.Data.Bar (Bar (Bar))
import Ribosome.Host.Data.Report (Report, basicReport)
import Ribosome.Host.Data.RpcHandler (Handler, RpcHandlerFun)

decodeArg ::
  Member (Stop Report) r =>
  MsgpackDecode a =>
  Object ->
  Sem r a
decodeArg =
  stopEither . first fromText . fromMsgpack

extraError ::
  Member (Stop Report) r =>
  [Object] ->
  Sem r a
extraError o =
  stop (fromString [exon|Extraneous arguments: #{show o}|])

optArg ::
  Member (Stop Report) r =>
  MsgpackDecode a =>
  a ->
  [Object] ->
  Sem r ([Object], a)
optArg dflt = \case
  [] -> pure ([], dflt)
  (o : rest) -> do
    a <- decodeArg o
    pure (rest, a)

-- |This class is used by 'HandlerCodec' to decode handler function parameters.
-- Each parameter may consume zero or arbitrarily many of the RPC message's arguments.
--
-- Users may create instances for their types to implement custom decoding, especially for commands, since those don't
-- have structured arguments.
--
-- See also 'Ribosome.CommandHandler'.
class HandlerArg a r where
  -- Take an arbitrary number of arguments from the list and return a value of type @a@ as well as the remaining
    -- arguments.
  handlerArg :: [Object] -> Sem r ([Object], a)

instance {-# overlappable #-} (
    Member (Stop Report) r,
    MsgpackDecode a
  ) => HandlerArg a r where
    handlerArg = \case
      [] -> stop "too few arguments"
      (o : rest) -> do
        a <- decodeArg o
        pure (rest, a)

instance (
    HandlerArg a r
  ) => HandlerArg (Maybe a) r where
    handlerArg = \case
      [] -> pure ([], Nothing)
      os -> second Just <$> handlerArg os

instance HandlerArg Bar r where
  handlerArg os =
    pure (os, Bar)

instance (
    Member (Stop Report) r
  ) => HandlerArg Bang r where
    handlerArg =
      optArg NoBang

instance (
    Member (Stop Report) r
  ) => HandlerArg Args r where
  handlerArg os =
    case traverse fromMsgpack os of
      Right a ->
        pure ([], Args (Text.unwords a))
      Left e ->
        basicReport [exon|Invalid arguments: #{show os}|] ["Invalid type for Args", show os, e]

instance (
    Member (Stop Report) r
  ) => HandlerArg ArgList r where
  handlerArg os =
    case traverse fromMsgpack os of
      Right a ->
        pure ([], ArgList a)
      Left e ->
        basicReport [exon|Invalid arguments: #{show os}|] ["Invalid type for ArgList", show os, e]

instance (
    Member (Stop Report) r,
    FromJSON a
  ) => HandlerArg (JsonArgs a) r where
  handlerArg os =
    case first toText . eitherDecodeStrict' . ByteString.concat =<< traverse fromMsgpack os of
      Right a ->
        pure ([], JsonArgs a)
      Left e ->
        basicReport [exon|Invalid arguments: #{show os}|] ["Invalid type for JsonArgs", show os, e]

instance (
    Member (Stop Report) r,
    OptionParser a
  ) => HandlerArg (Options a) r where
  handlerArg os =
    case result . execParserPure defaultPrefs (info (optionParser @a) mempty) =<< traverse fromMsgpack os of
      Right a ->
        pure ([], Options a)
      Left e ->
        basicReport [exon|Invalid arguments: #{show os}|] ["Invalid type for Options", show os, e]
    where
      result = \case
        Optparse.Success a -> Right a
        Optparse.Failure e -> Left (toText (fst (renderFailure e "Ribosome")))
        Optparse.CompletionInvoked _ -> Left "Internal optparse error"

-- |The class of functions that can be converted to canonical RPC handlers of type 'RpcHandlerFun'.
class HandlerCodec h r | h -> r where
  -- |Convert a type containing a 'Sem' to a canonicalized 'RpcHandlerFun' by transforming each function parameter with
  -- 'HandlerArg'.
  handlerCodec :: h -> RpcHandlerFun r

instance (
    MsgpackEncode a
  ) => HandlerCodec (Handler r a) r where
    handlerCodec h = \case
      [] -> toMsgpack <$> h
      o -> extraError o

instance (
    HandlerArg a (Stop Report : r),
    HandlerCodec b r
  ) => HandlerCodec (a -> b) r where
  handlerCodec h o = do
    (rest, a) <- handlerArg o
    handlerCodec (h a) rest