{-| Inter-Process Communication Used for when the user project is called from a layer. This is used to call the @haskell_lambda@ executable, which is provided by the user, when they want to use the layer. This IPC protocol is based on printing an UUID that is created by the layer, and then the result. So everything that is printed before the UUID, is considered STDOUT printed by the lambda, while what comes after the UUID is considered. In the case that the lambda execution fails, the exit code won't be 0 (exit-success), so it will use the STDERR. -} module Aws.Lambda.Runtime.IPC ( invoke , returnAndFail , returnAndSucceed ) where import Data.Function ((&)) import qualified Data.Maybe as Maybe import qualified Data.String as String import qualified System.Exit as Exit import qualified System.IO as IO import qualified System.Process as Process import Control.Exception.Safe.Checked import Data.Aeson import qualified Data.ByteString.Lazy.Char8 as ByteString import qualified Data.UUID as UUID import qualified Data.UUID.V4 as UUID import Aws.Lambda.Runtime.Context (Context (..)) import qualified Aws.Lambda.Runtime.Environment as Environment import qualified Aws.Lambda.Runtime.Error as Error import Aws.Lambda.Runtime.Result (LambdaResult (..)) -- | Returns the JSON value failing, according to the protocol returnAndFail :: ToJSON a => String -> a -> IO () returnAndFail uuid v = do IO.hFlush IO.stdout putStrLn uuid IO.hFlush IO.stdout putStrLn (ByteString.unpack $ encode v) IO.hFlush IO.stdout IO.hFlush IO.stderr Exit.exitFailure -- | Returns the JSON value succeeding, according to the protocol returnAndSucceed :: ToJSON a => String -> a -> IO () returnAndSucceed uuid v = do IO.hFlush IO.stdout putStrLn uuid IO.hFlush IO.stdout putStrLn (ByteString.unpack $ encode v) IO.hFlush IO.stdout Exit.exitSuccess -- | Invokes a function defined by the user as the @haskell_lambda@ executable invoke :: Throws Error.Invocation => Throws Error.Parsing => Throws Error.EnvironmentVariableNotSet => ByteString.ByteString -> Context -> IO LambdaResult invoke event context = do handlerName <- Environment.handlerName runningDirectory <- Environment.taskRoot let contextJSON = ByteString.unpack $ encode context uuid <- UUID.nextRandom out <- Process.readProcessWithExitCode (runningDirectory <> "/haskell_lambda") [ "--eventObject", ByteString.unpack event , "--contextObject", contextJSON , "--functionHandler", handlerName , "--executionUuid", UUID.toString uuid ] "" case out of (Exit.ExitSuccess, stdOut, _) -> do res <- getFunctionResult uuid stdOut case res of Nothing -> throw (Error.Parsing "parsing result" stdOut) Just value -> pure (LambdaResult value) (_, stdOut, stdErr) -> if stdErr /= "" then throw (Error.Invocation stdErr) else do res <- getFunctionResult uuid stdOut case res of Nothing -> throw (Error.Parsing "parsing error" stdOut) Just value -> throw (Error.Invocation value) getFunctionResult :: UUID.UUID -> String -> IO (Maybe String) getFunctionResult u stdOut = do let out = String.lines stdOut let uuid = UUID.toString u printAfterUuid uuid out returnAfterUuid uuid out where printAfterUuid uuid out = out & takeWhile (/= uuid) & mapM_ ( \t -> do putStrLn t IO.hFlush IO.stdout ) returnAfterUuid uuid out = out & dropWhile (/= uuid) & dropWhile (== uuid) & Maybe.listToMaybe & pure