Compare commits

..

4 Commits

Author SHA1 Message Date
Benjamin Eckel
3e6a0071e9 fix: Fix the release action (#101) 2022-11-29 10:33:26 -06:00
Benjamin Eckel
d17b693c4b release: Bump to 0.0.1 (#97) 2022-11-29 09:48:40 -06:00
zach
18fceec8f8 Cleanup Haskell SDK, split out manifest sublibrary (#99) 2022-11-28 21:03:13 -08:00
Benjamin Eckel
f5cf4f184e chore: fix ruby release publish (#78) 2022-11-28 19:31:26 -06:00
7 changed files with 130 additions and 108 deletions

View File

@@ -21,5 +21,5 @@ jobs:
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_TOKEN }}
run: |
cd ruby
make publish
make publish RUBYGEMS_API_KEY=$RUBYGEMS_API_KEY

View File

@@ -18,6 +18,6 @@ handlePlugin plugin = do
exitSuccess) res
main = do
context <- Extism.newContext ()
context <- Extism.newContext
plugin <- Extism.pluginFromManifest context (manifest [wasmFile "../wasm/code.wasm"]) False
try handlePlugin plugin

View File

@@ -1,4 +1,4 @@
cabal-version: 2.4
cabal-version: 3.0
name: extism
version: 0.0.1
@@ -23,22 +23,41 @@ category: Plugins, WebAssembly
extra-source-files: CHANGELOG.md
library
exposed-modules: Extism Extism.Manifest
exposed-modules: Extism
reexported-modules: Extism.Manifest
-- Modules included in this library but not exported.
other-modules: Extism.Bindings
-- LANGUAGE extensions used by modules in this package.
-- other-extensions:
build-depends:
base >= 1.6.0
, bytestring
, json
, manifest
hs-source-dirs: src
default-language: Haskell2010
extra-libraries: extism
extra-lib-dirs: /usr/local/lib
library manifest
exposed-modules: Extism.Manifest
visibility: public
-- Modules included in this library but not exported.
other-modules:
-- LANGUAGE extensions used by modules in this package.
-- other-extensions:
build-depends:
base ^>=4.16.1.0
base
, bytestring
, base64-bytestring
, json
hs-source-dirs: src
hs-source-dirs: manifest
default-language: Haskell2010
extra-libraries: extism
extra-lib-dirs: /usr/local/lib
Test-Suite extism-example
type: exitcode-stdio-1.0

View File

@@ -1,7 +1,10 @@
{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
module Extism.Manifest where
import Text.JSON
(
JSON,
JSValue(JSNull, JSString, JSArray),
toJSString, showJSON, makeObj, encode
)
@@ -9,16 +12,12 @@ import qualified Data.ByteString as B
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Char8 as BS (unpack)
valueOrNull f Nothing = JSNull
valueOrNull f (Just x) = f x
makeString s = JSString (toJSString s)
stringOrNull = valueOrNull makeString
makeArray f [] = JSNull
makeArray f x = JSArray [f a | a <- x]
filterNulls obj = [(a, b) | (a, b) <- obj, not (isNull b)]
mapObj f x = makeObj (filterNulls [(a, f b) | (a, b) <- x])
makeArray x = JSArray [toJSONValue a | a <- x]
isNull JSNull = True
isNull _ = False
filterNulls obj = [(a, b) | (a, b) <- obj, not (isNull b)]
object x = makeObj $ filterNulls x
(.=) a b = (a, toJSONValue b)
newtype Memory = Memory
{
@@ -27,32 +26,37 @@ newtype Memory = Memory
class JSONValue a where
toJSONValue :: a -> JSValue
instance {-# OVERLAPS #-} (JSON a) => (JSONValue a) where
toJSONValue j = showJSON j
instance {-# OVERLAPS #-} (JSONValue a) => (JSONValue (Maybe a)) where
toJSONValue Nothing = JSNull
toJSONValue (Just x) = toJSONValue x
instance JSONValue Memory where
toJSONValue x =
case memoryMax x of
Nothing -> makeObj []
Just max -> makeObj [("max", showJSON max)]
toJSONValue (Memory max) =
object [
"max" .= max
]
data HttpRequest = HttpRequest
data HTTPRequest = HTTPRequest
{
url :: String
, header :: [(String, String)]
, header :: Maybe [(String, String)]
, method :: Maybe String
}
requestObj x =
let meth = stringOrNull $ method x in
let h = mapObj makeString $ header x in
filterNulls [
("url", makeString $ url x),
("header", h),
("method", meth)
requestObj (HTTPRequest url header method) =
[
"url" .= url ,
"header" .= header,
"method" .= method
]
instance JSONValue HttpRequest where
instance JSONValue HTTPRequest where
toJSONValue x =
makeObj $ requestObj x
object $ requestObj x
data WasmFile = WasmFile
{
@@ -62,14 +66,11 @@ data WasmFile = WasmFile
}
instance JSONValue WasmFile where
toJSONValue x =
let path = makeString $ filePath x in
let name = stringOrNull $ fileName x in
let hash = stringOrNull $ fileHash x in
makeObj $ filterNulls [
("path", path),
("name", name),
("hash", hash)
toJSONValue (WasmFile path name hash) =
object [
"path" .= path,
"name" .= name,
"hash" .= hash
]
data WasmCode = WasmCode
@@ -81,30 +82,26 @@ data WasmCode = WasmCode
instance JSONValue WasmCode where
toJSONValue x =
let bytes = makeString $ BS.unpack $ B64.encode $ codeBytes x in
let name = stringOrNull $ codeName x in
let hash = stringOrNull $ codeHash x in
makeObj $ filterNulls [
("data", bytes),
("name", name),
("hash", hash)
toJSONValue (WasmCode x name hash) =
let bytes = BS.unpack $ B64.encode x in
object [
"data" .= bytes,
"name" .= name,
"hash" .= hash
]
data WasmURL = WasmURL
{
req :: HttpRequest
req :: HTTPRequest
, urlName :: Maybe String
, urlHash :: Maybe String
}
instance JSONValue WasmURL where
toJSONValue x =
let request = requestObj $ req x in
let name = stringOrNull $ urlName x in
let hash = stringOrNull $ urlHash x in
makeObj $ filterNulls $ ("name", name) : ("hash", hash) : request
toJSONValue (WasmURL req name hash) =
let request = requestObj $ req in
object $ "name" .= name : "hash" .= hash : request
data Wasm = File WasmFile | Code WasmCode | URL WasmURL
@@ -121,7 +118,7 @@ wasmFile path =
wasmURL :: String -> String -> Wasm
wasmURL method url =
let r = HttpRequest { url = url, header = [], method = Just method } in
let r = HTTPRequest { url = url, header = Nothing, method = Just method } in
URL WasmURL { req = r, urlName = Nothing, urlHash = Nothing }
wasmCode :: B.ByteString -> Wasm
@@ -143,8 +140,8 @@ data Manifest = Manifest
{
wasm :: [Wasm]
, memory :: Maybe Memory
, config :: [(String, String)]
, allowed_hosts :: [String]
, config :: Maybe [(String, String)]
, allowedHosts :: Maybe [String]
}
manifest :: [Wasm] -> Manifest
@@ -152,32 +149,29 @@ manifest wasm =
Manifest {
wasm = wasm,
memory = Nothing,
config = [],
allowed_hosts = []
config = Nothing,
allowedHosts = Nothing
}
withConfig :: Manifest -> [(String, String)] -> Manifest
withConfig m config =
m { config = config }
m { config = Just config }
withHosts :: Manifest -> [String] -> Manifest
withHosts m hosts =
m { allowed_hosts = hosts }
m { allowedHosts = Just hosts }
instance JSONValue Manifest where
toJSONValue x =
let w = makeArray toJSONValue $ wasm x in
let mem = valueOrNull toJSONValue $ memory x in
let c = mapObj makeString $ config x in
let hosts = makeArray makeString $ allowed_hosts x in
makeObj $ filterNulls [
("wasm", w),
("memory", mem),
("config", c),
("allowed_hosts", hosts)
toJSONValue (Manifest wasm memory config hosts) =
let w = makeArray wasm in
object [
"wasm" .= w,
"memory" .= memory,
"config" .= config,
"allowed_hosts" .= hosts
]
toString :: Manifest -> String
toString manifest =
encode (toJSONValue manifest)
toString :: (JSONValue a) => a -> String
toString v =
encode (toJSONValue v)

View File

@@ -1,36 +1,17 @@
{-# LANGUAGE ForeignFunctionInterface #-}
module Extism (module Extism, module Extism.Manifest) where
import GHC.Int
import GHC.Word
import Foreign.C.Types
import Foreign.Ptr
import Data.Int
import Data.Word
import Control.Monad (void)
import Foreign.ForeignPtr
import Foreign.C.String
import Control.Monad (void)
import Foreign.Ptr
import Data.ByteString as B
import Data.ByteString.Internal (c2w, w2c)
import Data.ByteString.Unsafe (unsafeUseAsCString)
import Data.Bifunctor (second)
import Text.JSON (JSON, toJSObject, toJSString, encode, JSValue(JSNull, JSString))
import Extism.Manifest (Manifest, toString)
newtype ExtismContext = ExtismContext () deriving Show
foreign import ccall unsafe "extism.h extism_context_new" extism_context_new :: IO (Ptr ExtismContext)
foreign import ccall unsafe "extism.h &extism_context_free" extism_context_free :: FunPtr (Ptr ExtismContext -> IO ())
foreign import ccall unsafe "extism.h extism_plugin_new" extism_plugin_new :: Ptr ExtismContext -> Ptr Word8 -> Word64 -> CBool -> IO Int32
foreign import ccall unsafe "extism.h extism_plugin_update" extism_plugin_update :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Word64 -> CBool -> IO CBool
foreign import ccall unsafe "extism.h extism_plugin_call" extism_plugin_call :: Ptr ExtismContext -> Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
foreign import ccall unsafe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismContext -> Int32 -> CString -> IO CBool
foreign import ccall unsafe "extism.h extism_error" extism_error :: Ptr ExtismContext -> Int32 -> IO CString
foreign import ccall unsafe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismContext -> Int32 -> IO Word64
foreign import ccall unsafe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismContext -> Int32 -> IO (Ptr Word8)
foreign import ccall unsafe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
foreign import ccall unsafe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Int64 -> IO CBool
foreign import ccall unsafe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismContext -> Int32 -> IO ()
foreign import ccall unsafe "extism.h extism_context_reset" extism_context_reset :: Ptr ExtismContext -> IO ()
foreign import ccall unsafe "extism.h extism_version" extism_version :: IO CString
import Text.JSON (encode, toJSObject)
import Extism.Manifest (Manifest, toString, toJSONValue)
import Extism.Bindings
-- Context manages plugins
newtype Context = Context (ForeignPtr ExtismContext)
@@ -64,8 +45,8 @@ reset (Context ctx) =
withForeignPtr ctx extism_context_reset
-- Create a new context
newContext :: () -> IO Context
newContext () = do
newContext :: IO Context
newContext = do
ptr <- extism_context_new
fptr <- newForeignPtr extism_context_free ptr
return (Context fptr)
@@ -73,7 +54,7 @@ newContext () = do
-- Execute a function with a new context that is destroyed when it returns
withContext :: (Context -> IO a) -> IO a
withContext f = do
ctx <- newContext ()
ctx <- newContext
f ctx
-- Create a plugin from a WASM module, `useWasi` determines if WASI should
@@ -126,16 +107,13 @@ updateManifest plugin manifest useWasi =
isValid :: Plugin -> Bool
isValid (Plugin _ p) = p >= 0
convertMaybeString Nothing = JSNull
convertMaybeString (Just s) = JSString (toJSString s)
-- Set configuration values for a plugin
setConfig :: Plugin -> [(String, Maybe String)] -> IO Bool
setConfig (Plugin (Context ctx) plugin) x =
if plugin < 0
then return False
else
let obj = toJSObject [(k, convertMaybeString v) | (k, v) <- x] in
let obj = toJSObject [(k, toJSONValue v) | (k, v) <- x] in
let bs = toByteString (encode obj) in
let length = fromIntegral (B.length bs) in
unsafeUseAsCString bs (\s -> do

View File

@@ -0,0 +1,26 @@
{-# LANGUAGE ForeignFunctionInterface #-}
module Extism.Bindings where
import Foreign.C.Types
import Foreign.Ptr
import Foreign.C.String
import Data.Int
import Data.Word
newtype ExtismContext = ExtismContext () deriving Show
foreign import ccall unsafe "extism.h extism_context_new" extism_context_new :: IO (Ptr ExtismContext)
foreign import ccall unsafe "extism.h &extism_context_free" extism_context_free :: FunPtr (Ptr ExtismContext -> IO ())
foreign import ccall unsafe "extism.h extism_plugin_new" extism_plugin_new :: Ptr ExtismContext -> Ptr Word8 -> Word64 -> CBool -> IO Int32
foreign import ccall unsafe "extism.h extism_plugin_update" extism_plugin_update :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Word64 -> CBool -> IO CBool
foreign import ccall unsafe "extism.h extism_plugin_call" extism_plugin_call :: Ptr ExtismContext -> Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
foreign import ccall unsafe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismContext -> Int32 -> CString -> IO CBool
foreign import ccall unsafe "extism.h extism_error" extism_error :: Ptr ExtismContext -> Int32 -> IO CString
foreign import ccall unsafe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismContext -> Int32 -> IO Word64
foreign import ccall unsafe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismContext -> Int32 -> IO (Ptr Word8)
foreign import ccall unsafe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
foreign import ccall unsafe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Int64 -> IO CBool
foreign import ccall unsafe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismContext -> Int32 -> IO ()
foreign import ccall unsafe "extism.h extism_context_reset" extism_context_reset :: Ptr ExtismContext -> IO ()
foreign import ccall unsafe "extism.h extism_version" extism_version :: IO CString

View File

@@ -1,3 +1,4 @@
RUBYGEMS_API_KEY ?=
.PHONY: prepare test
@@ -9,11 +10,15 @@ test: prepare
bundle exec rake test
clean:
rm extism-*.gem
rm -f extism-*.gem
publish-local: clean prepare
gem build extism.gemspec
gem push extism-*.gem
publish: clean prepare
gem build extism.gemspec
gem push extism-*.gem
GEM_HOST_API_KEY=$(RUBYGEMS_API_KEY) gem push extism-*.gem
lint:
bundle exec rufo --check .