From 8585e4ba307215cb2b71ccfaf4680f1ba4bc1ad6 Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 1 Nov 2023 13:32:26 -0700 Subject: [PATCH 1/3] cleanup: make it easier to to derived JSON implementations --- src/Extism/Encoding.hs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Extism/Encoding.hs b/src/Extism/Encoding.hs index f0fb4e9..344beb0 100644 --- a/src/Extism/Encoding.hs +++ b/src/Extism/Encoding.hs @@ -21,7 +21,8 @@ import Data.ByteString.Internal (c2w, unsafePackLenAddress, w2c) import Data.Int import Data.Word import qualified Extism.JSON (JSON (..)) -import qualified Text.JSON (Result (..), decode, encode, showJSON, toJSObject) +import qualified Text.JSON (JSValue, Result (..), decode, encode, showJSON, toJSObject) +import qualified Text.JSON.Generic (Data, decodeJSON, encodeJSON, fromJSON, toJSON) -- | Helper function to convert a 'String' to a 'ByteString' toByteString :: String -> B.ByteString @@ -124,12 +125,16 @@ instance FromBytes Double where -- Wraps a `JSON` value for input/output newtype JSONValue x = JSONValue x -instance (Extism.JSON.JSON a) => ToBytes (JSONValue a) where +instance (Text.JSON.Generic.Data a) => ToBytes (JSONValue a) where toBytes (JSONValue x) = - toByteString $ Text.JSON.encode x + toByteString $ Text.JSON.Generic.encodeJSON x -instance (Extism.JSON.JSON a) => FromBytes (JSONValue a) where - fromBytes bs = do - case Text.JSON.decode (fromByteString bs) of - Text.JSON.Error x -> Left (ExtismError x) - Text.JSON.Ok x -> Right (JSONValue x) +instance (Text.JSON.Generic.Data a) => FromBytes (JSONValue a) where + fromBytes bs = + let x = Text.JSON.decode (fromByteString bs) + in case x of + Text.JSON.Error e -> Left (ExtismError e) + Text.JSON.Ok x -> + case Text.JSON.Generic.fromJSON (x :: Text.JSON.JSValue) of + Text.JSON.Error e -> Left (ExtismError e) + Text.JSON.Ok x -> Right (JSONValue x) From 28c585d9c10acd81366b72c4e24910ebfb78777c Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 1 Nov 2023 14:02:31 -0700 Subject: [PATCH 2/3] refactor: rename JSONValue -> JSON --- Example.hs | 11 ++++++++--- README.md | 24 +++++++++++++++--------- extism.cabal | 4 ++-- manifest/Extism/JSON.hs | 4 ++-- manifest/Extism/Manifest.hs | 5 +---- src/Extism.hs | 7 +++---- src/Extism/Encoding.hs | 13 ++++++------- 7 files changed, 37 insertions(+), 31 deletions(-) diff --git a/Example.hs b/Example.hs index ce1a87d..87e59e4 100644 --- a/Example.hs +++ b/Example.hs @@ -1,14 +1,19 @@ +{-# LANGUAGE DeriveDataTypeable #-} + module Main where import Extism import Extism.HostFunction +import Extism.JSON import Extism.Manifest (manifest, wasmFile) +newtype Count = Count {count :: Int} deriving (Data, Typeable, Show) + hello currPlugin msg = do putStrLn . unwrap <$> input currPlugin 0 putStrLn "Hello from Haskell!" putStrLn msg - output currPlugin 0 "{\"count\": 999}" + output currPlugin 0 (JSON $ Count 999) main = do setLogFile "stdout" LogError @@ -17,5 +22,5 @@ main = do plugin <- unwrap <$> newPlugin m [f] True id <- pluginID plugin print id - res <- unwrap <$> call plugin "count_vowels" "this is a test" - putStrLn res + JSON res <- (unwrap <$> call plugin "count_vowels" "this is a test" :: IO (JSON Count)) + print res diff --git a/README.md b/README.md index 131fd30..894f333 100644 --- a/README.md +++ b/README.md @@ -99,27 +99,33 @@ In this example, we want to expose a single function to our plugin (in Haskell t Let's load the manifest like usual but load up `wasm/code-functions.wasm` plug-in: ```haskell +{-# LANGUAGE DeriveDataTypeable #-} + module Main where import Extism import Extism.HostFunction +import Extism.JSON import Extism.Manifest (manifest, wasmFile) -hello currPlugin () = do +newtype Count = Count {count :: Int} deriving (Data, Typeable, Show) + +hello currPlugin msg = do putStrLn . unwrap <$> input currPlugin 0 putStrLn "Hello from Haskell!" - output currPlugin 0 "{\"count\": 999}" + putStrLn msg + output currPlugin 0 (JSON $ Count 999) main = do - setLogFile "stdout" LogError -- Enable logging to stdout + setLogFile "stdout" LogError let m = manifest [wasmFile "wasm/code-functions.wasm"] - f <- hostFunction "hello_world" [I64] [I64] hello () -- Create host function, the final argument - -- is a userData argument that can store any - -- Haskell value + f <- hostFunction "hello_world" [I64] [I64] hello "Hello, again" plugin <- unwrap <$> newPlugin m [f] True - res <- unwrap <$> call plugin "count_vowels" "this is a test" - putStrLn res --- Prints: {"count": 999} + id <- pluginID plugin + print id + JSON res <- (unwrap <$> call plugin "count_vowels" "this is a test" :: IO (JSON Count)) + print res +-- Prints: Count {count = 999} ``` > *Note*: In order to write host functions you should get familiar with the methods on the [Extism.HostFunction](https://hackage.haskell.org/package/extism/docs/Extism-HostFunction.html) module. diff --git a/extism.cabal b/extism.cabal index 83f3bb1..b55f968 100644 --- a/extism.cabal +++ b/extism.cabal @@ -11,8 +11,8 @@ category: Plugins, WebAssembly extra-doc-files: cabal.project README.md CHANGELOG.md library - exposed-modules: Extism Extism.HostFunction Extism.Encoding - reexported-modules: Extism.Manifest + exposed-modules: Extism, Extism.HostFunction, Extism.Encoding + reexported-modules: Extism.Manifest, Extism.JSON hs-source-dirs: src other-modules: Extism.Bindings default-language: Haskell2010 diff --git a/manifest/Extism/JSON.hs b/manifest/Extism/JSON.hs index 717b7d4..dcb5829 100644 --- a/manifest/Extism/JSON.hs +++ b/manifest/Extism/JSON.hs @@ -1,7 +1,7 @@ module Extism.JSON ( module Extism.JSON, - module Text.JSON, - module Text.JSON.Generic, + Data, + Typeable, ) where diff --git a/manifest/Extism/Manifest.hs b/manifest/Extism/Manifest.hs index b3b9b18..6845ea8 100644 --- a/manifest/Extism/Manifest.hs +++ b/manifest/Extism/Manifest.hs @@ -3,6 +3,7 @@ module Extism.Manifest where import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as BS (unpack) import Extism.JSON +import Text.JSON -- | Memory options newtype Memory = Memory @@ -234,7 +235,3 @@ withTimeout m t = withMaxPages :: Manifest -> Int -> Manifest withMaxPages m pages = m {memory = NotNull $ Memory (NotNull pages)} - -toString :: (JSON a) => a -> String -toString v = - encode (showJSON v) diff --git a/src/Extism.hs b/src/Extism.hs index 5b9b31e..f404653 100644 --- a/src/Extism.hs +++ b/src/Extism.hs @@ -24,7 +24,7 @@ module Extism ToBytes (..), Encoding, FromBytes (..), - JSONValue (..), + JSON (..), ) where @@ -37,8 +37,7 @@ import qualified Data.UUID (UUID, fromByteString, toString) import Data.Word import Extism.Bindings import Extism.Encoding -import qualified Extism.JSON (JSON (..)) -import Extism.Manifest (Manifest, toString) +import Extism.Manifest (Manifest) import Foreign.C.String import Foreign.Concurrent import Foreign.ForeignPtr @@ -76,7 +75,7 @@ instance PluginInput B.ByteString where pluginInput = id instance PluginInput Manifest where - pluginInput m = toByteString $ toString m + pluginInput m = toByteString $ Text.JSON.encode m -- | Create a 'Plugin' from a WASM module, `useWasi` determines if WASI should -- | be linked diff --git a/src/Extism/Encoding.hs b/src/Extism/Encoding.hs index 344beb0..b4ad3f9 100644 --- a/src/Extism/Encoding.hs +++ b/src/Extism/Encoding.hs @@ -10,7 +10,7 @@ module Extism.Encoding ToBytes (..), FromBytes (..), Encoding (..), - JSONValue (..), + JSON (..), ) where @@ -20,7 +20,6 @@ import qualified Data.ByteString as B import Data.ByteString.Internal (c2w, unsafePackLenAddress, w2c) import Data.Int import Data.Word -import qualified Extism.JSON (JSON (..)) import qualified Text.JSON (JSValue, Result (..), decode, encode, showJSON, toJSObject) import qualified Text.JSON.Generic (Data, decodeJSON, encodeJSON, fromJSON, toJSON) @@ -123,13 +122,13 @@ instance FromBytes Double where Right (_, _, x) -> Right x -- Wraps a `JSON` value for input/output -newtype JSONValue x = JSONValue x +newtype JSON x = JSON x -instance (Text.JSON.Generic.Data a) => ToBytes (JSONValue a) where - toBytes (JSONValue x) = +instance (Text.JSON.Generic.Data a) => ToBytes (JSON a) where + toBytes (JSON x) = toByteString $ Text.JSON.Generic.encodeJSON x -instance (Text.JSON.Generic.Data a) => FromBytes (JSONValue a) where +instance (Text.JSON.Generic.Data a) => FromBytes (JSON a) where fromBytes bs = let x = Text.JSON.decode (fromByteString bs) in case x of @@ -137,4 +136,4 @@ instance (Text.JSON.Generic.Data a) => FromBytes (JSONValue a) where Text.JSON.Ok x -> case Text.JSON.Generic.fromJSON (x :: Text.JSON.JSValue) of Text.JSON.Error e -> Left (ExtismError e) - Text.JSON.Ok x -> Right (JSONValue x) + Text.JSON.Ok x -> Right (JSON x) From 0b4396d90111c37ac481023ab7c9a3321ea4a633 Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 1 Nov 2023 15:24:41 -0700 Subject: [PATCH 3/3] cleanup: add functions to load manifest from file --- manifest/Extism/Manifest.hs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/manifest/Extism/Manifest.hs b/manifest/Extism/Manifest.hs index 6845ea8..b789c6e 100644 --- a/manifest/Extism/Manifest.hs +++ b/manifest/Extism/Manifest.hs @@ -4,6 +4,7 @@ import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as BS (unpack) import Extism.JSON import Text.JSON +import Text.JSON.Generic -- | Memory options newtype Memory = Memory @@ -235,3 +236,20 @@ withTimeout m t = withMaxPages :: Manifest -> Int -> Manifest withMaxPages m pages = m {memory = NotNull $ Memory (NotNull pages)} + +fromString :: String -> Either String Manifest +fromString s = do + let x = decode s + resultToEither x + +fromFile :: FilePath -> IO (Either String Manifest) +fromFile path = do + s <- readFile path + return $ fromString s + +toString :: Manifest -> String +toString = encode + +toFile :: FilePath -> Manifest -> IO () +toFile path m = do + writeFile path (toString m)