packages feed

MultiChor-1.0.0.0: examples/KVS5Fig17.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}

{-
This is an implementation of the choreogrpahy shown in fig17 of We Know I Know You Know.
-}

module KVS5Fig17 where

import CLI
import Choreography
import Choreography.Network.Http
import Data (TestArgs, reference)
import Data.List (sort)
import Data.Maybe (fromMaybe)
import System.Environment
import Test.QuickCheck (Arbitrary, arbitrary, elements)

$(mkLoc "client")
$(mkLoc "primary")
$(mkLoc "backup")

type Servers = ["primary", "backup"]

type Participants = "client" ': Servers

servers :: Subset Servers Participants
servers = primary @@ backup @@ nobody

data Request = Put String String | Get String deriving (Eq, Show, Read)

instance Arbitrary Request where
  arbitrary =
    ( \case
        Nothing -> Get
        Just s -> Put s
    )
      <$> arbitrary
      <*> arbitrary

type Response = String

data Args = Args
  { request :: Request,
    handler :: String
  }
  deriving (Eq, Show, Read)

instance TestArgs Args Response where
  reference Args {request, handler} =
    let f = fromMaybe defaultHandler $ handler `lookup` handlers
     in handleRequest f request

instance Arbitrary Args where
  arbitrary =
    Args
      <$> arbitrary
      <*> elements (fst <$> handlers)

handlers :: [(String, String -> String)]
handlers =
  [ ("reverse", reverse),
    ("alphabetize", sort)
  ]

defaultHandler :: String -> String
defaultHandler = const "No Handler"

-- | `handleRequest` handle a request. Since we don't have a way of locking paralell state, this is a mock.
handleRequest :: (String -> Response) -> Request -> Response
handleRequest handler request = case request of
  Put key value -> show key ++ " saved as " ++ show value ++ "."
  Get key -> handler key

setup :: Choreo Servers (CLI m) (Located Servers (Request -> Response))
setup = do
  handlerName <-
    (primary, getstr "How should we mock `Get` Requests? (reverse or alphabetize)")
      -~> primary
      @@ backup
      @@ nobody
  primary @@ backup @@ nobody `congruently` \un -> handleRequest (fromMaybe defaultHandler $ un refl handlerName `lookup` handlers)

-- | `kvs` is a choreography that processes a single request located at the client and returns the response.
-- If the request is a `PUT`, it will forward the request to the backup node.
kvs :: Choreo Participants (CLI m) ()
kvs = do
  handler <- enclaveToAll servers setup
  request <- (client, getInput "Enter the `read`able Request:") -~> primary @@ backup @@ nobody
  response <- primary @@ backup @@ nobody `congruently` \un -> un refl handler $ un refl request
  response' <- (primary, response) ~> client @@ nobody
  client `locally_` \un -> putOutput "Recieved:" $ un client response'

main :: IO ()
main = do
  [loc] <- getArgs
  case loc of
    "client" -> runCLIIO $ runChoreography config kvs "client"
    "primary" -> runCLIIO $ runChoreography config kvs "primary"
    "backup" -> runCLIIO $ runChoreography config kvs "backup"
    _ -> error "unknown party"
  return ()
  where
    config =
      mkHttpConfig
        [ ("client", ("localhost", 3000)),
          ("primary", ("localhost", 4000)),
          ("backup", ("localhost", 5000))
        ]