mirror of
https://github.com/extism/extism.git
synced 2026-01-08 21:38:13 -05:00
fix merge conflict
This commit is contained in:
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@@ -57,11 +57,11 @@ jobs:
|
||||
python-version: '3.9'
|
||||
check-latest: true
|
||||
|
||||
- name: Load cached Poetry installation
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.local
|
||||
key: poetry-0
|
||||
# - name: Load cached Poetry installation
|
||||
# uses: actions/cache@v2
|
||||
# with:
|
||||
# path: ~/.local
|
||||
# key: poetry-0
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
@@ -107,5 +107,11 @@ jobs:
|
||||
# opam install -y .
|
||||
# cd ocaml
|
||||
# opam exec -- dune exec extism
|
||||
|
||||
|
||||
|
||||
- name: Setup Haskell env
|
||||
uses: haskell/actions/setup@v2
|
||||
|
||||
- name: Test Haskell SDK
|
||||
run: |
|
||||
cd haskell
|
||||
LD_LIBRARY_PATH=/usr/local/lib cabal test
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -28,3 +28,5 @@ ocaml/duniverse
|
||||
ocaml/_build
|
||||
wasm/rust-pdk/target
|
||||
php/Extism.php
|
||||
dist-newstyle
|
||||
.stack-work
|
||||
@@ -4,7 +4,7 @@ The universal plug-in system. Run WebAssembly extensions inside your app. Use id
|
||||
[Ruby](https://extism.org/docs/integrate-into-your-codebase/ruby-host-sdk), [Python](https://extism.org/docs/integrate-into-your-codebase/python-host-sdk),
|
||||
[Node](https://extism.org/docs/integrate-into-your-codebase/node-host-sdk), [Rust](https://extism.org/docs/integrate-into-your-codebase/rust-host-sdk),
|
||||
[C](https://extism.org/docs/integrate-into-your-codebase/c-host-sdk), [C++](https://extism.org/docs/integrate-into-your-codebase/cpp-host-sdk),
|
||||
[OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk) & more (others coming soon).
|
||||
[OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk), [Haskell](https://extism.org/docs/integrate-into-your-codebase/haskell-host-sdk) & more (others coming soon).
|
||||
|
||||
Plug-in development kits (PDK) for plug-in authors supported in Rust, AssemblyScript, Go, C/C++.
|
||||
|
||||
@@ -48,4 +48,4 @@ Extism is an open-source product from the team at:
|
||||
<a href="https://dylib.so" _target="blanks"><img width="200px" src="https://dylib.so/assets/dylibso-logo.svg"/></a>
|
||||
</p>
|
||||
|
||||
_Reach out and tell us what you're building! We'd love to help._
|
||||
_Reach out and tell us what you're building! We'd love to help._
|
||||
|
||||
16
dune-project
16
dune-project
@@ -7,20 +7,18 @@
|
||||
(source
|
||||
(github extism/extism))
|
||||
|
||||
(authors "Author Name")
|
||||
(authors "Extism Authors <oss@extism.org>")
|
||||
|
||||
(maintainers "Maintainer Name")
|
||||
(maintainers "Extism Authors <oss@extism.org>")
|
||||
|
||||
(license LICENSE)
|
||||
(license BSD-3)
|
||||
|
||||
(documentation https://url/to/documentation)
|
||||
(documentation https://github.com/extism/extism)
|
||||
|
||||
(package
|
||||
(name extism)
|
||||
(synopsis "A short synopsis")
|
||||
(description "A longer description")
|
||||
(synopsis "Extism bindings")
|
||||
(description "Bindings to Extism, the universal plugin system")
|
||||
(depends ocaml dune ctypes-foreign bigstringaf ppx_yojson_conv base64)
|
||||
(tags
|
||||
(topics "to describe" your project)))
|
||||
|
||||
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
|
||||
(topics wasm plugin)))
|
||||
|
||||
15
extism.opam
15
extism.opam
@@ -1,13 +1,13 @@
|
||||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
synopsis: "A short synopsis"
|
||||
description: "A longer description"
|
||||
maintainer: ["Maintainer Name"]
|
||||
authors: ["Author Name"]
|
||||
license: "LICENSE"
|
||||
tags: ["topics" "to describe" "your" "project"]
|
||||
synopsis: "Extism bindings"
|
||||
description: "Bindings to Extism, the universal plugin system"
|
||||
maintainer: ["Extism Authors <oss@extism.org>"]
|
||||
authors: ["Extism Authors <oss@extism.org>"]
|
||||
license: "BSD-3"
|
||||
tags: ["topics" "wasm" "plugin"]
|
||||
homepage: "https://github.com/extism/extism"
|
||||
doc: "https://url/to/documentation"
|
||||
doc: "https://github.com/extism/extism"
|
||||
bug-reports: "https://github.com/extism/extism/issues"
|
||||
depends: [
|
||||
"ocaml"
|
||||
@@ -15,6 +15,7 @@ depends: [
|
||||
"ctypes-foreign"
|
||||
"bigstringaf"
|
||||
"ppx_yojson_conv"
|
||||
"base64"
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
|
||||
5
haskell/CHANGELOG.md
Normal file
5
haskell/CHANGELOG.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Revision history for extism
|
||||
|
||||
## 0.1.0.0 -- YYYY-mm-dd
|
||||
|
||||
* First version. Released on an unsuspecting world.
|
||||
17
haskell/Example.hs
Normal file
17
haskell/Example.hs
Normal file
@@ -0,0 +1,17 @@
|
||||
module Main where
|
||||
|
||||
import System.Exit (exitFailure, exitSuccess)
|
||||
import qualified Data.ByteString as B
|
||||
import Extism
|
||||
import Extism.Manifest
|
||||
|
||||
main = do
|
||||
plugin <- Extism.registerManifest (manifest [wasmFile "../wasm/code.wasm"]) False
|
||||
res <- Extism.call plugin "count_vowels" (Extism.toByteString "this is a test")
|
||||
case res of
|
||||
Right (Error msg) -> do
|
||||
_ <- putStrLn msg
|
||||
exitFailure
|
||||
Left bs -> do
|
||||
_ <- putStrLn (Extism.fromByteString bs)
|
||||
exitSuccess
|
||||
47
haskell/extism.cabal
Normal file
47
haskell/extism.cabal
Normal file
@@ -0,0 +1,47 @@
|
||||
cabal-version: 2.4
|
||||
name: extism
|
||||
version: 0.0.1.0
|
||||
|
||||
-- A short (one-line) description of the package.
|
||||
synopsis: Extism bindings
|
||||
|
||||
-- A longer description of the package.
|
||||
description: Bindings to Extism, the universal plugin system
|
||||
|
||||
-- A URL where users can report bugs.
|
||||
bug-reports: https://github.com/extism/extism
|
||||
|
||||
-- The license under which the package is released.
|
||||
license: BSD-3-Clause
|
||||
|
||||
author: Extism authors
|
||||
maintainer: oss@extism.org
|
||||
|
||||
-- A copyright notice.
|
||||
-- copyright:
|
||||
category: Plugins
|
||||
extra-source-files: CHANGELOG.md
|
||||
|
||||
library
|
||||
exposed-modules: Extism Extism.Manifest
|
||||
|
||||
-- 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
|
||||
, bytestring
|
||||
, base64-bytestring
|
||||
, json
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
||||
extra-libraries: extism
|
||||
extra-lib-dirs: /usr/local/lib
|
||||
|
||||
Test-Suite extism-example
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Example.hs
|
||||
build-depends: base, extism, bytestring
|
||||
default-language: Haskell2010
|
||||
91
haskell/src/Extism.hs
Normal file
91
haskell/src/Extism.hs
Normal file
@@ -0,0 +1,91 @@
|
||||
{-# LANGUAGE ForeignFunctionInterface #-}
|
||||
|
||||
module Extism (module Extism, module Extism.Manifest) where
|
||||
import GHC.Int
|
||||
import GHC.Word
|
||||
import Foreign.C.Types
|
||||
import Foreign.Ptr
|
||||
import Foreign.C.String
|
||||
import Control.Monad (void)
|
||||
import Data.ByteString as B
|
||||
import Data.ByteString.Internal (c2w, w2c)
|
||||
import Data.ByteString.Unsafe (unsafeUseAsCString)
|
||||
import Text.JSON (JSON, toJSObject, encode)
|
||||
import Extism.Manifest (Manifest, toString)
|
||||
|
||||
foreign import ccall unsafe "extism.h extism_plugin_register" extism_plugin_register :: Ptr Word8 -> Word64 -> CBool -> IO Int32
|
||||
foreign import ccall unsafe "extism.h extism_call" extism_call :: Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
|
||||
foreign import ccall unsafe "extism.h extism_function_exists" extism_function_exists :: Int32 -> CString -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_error" extism_error :: Int32 -> IO CString
|
||||
foreign import ccall unsafe "extism.h extism_output_length" extism_output_length :: Int32 -> IO Word64
|
||||
foreign import ccall unsafe "extism.h extism_output_get" extism_output_get :: Int32 -> Ptr Word8 -> Word64 -> IO ()
|
||||
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 :: Int32 -> Ptr Word8 -> Int64 -> IO CBool
|
||||
|
||||
newtype Plugin = Plugin Int32 deriving Show
|
||||
newtype Error = Error String deriving Show
|
||||
|
||||
toByteString :: String -> ByteString
|
||||
toByteString x = B.pack (Prelude.map c2w x)
|
||||
|
||||
fromByteString :: ByteString -> String
|
||||
fromByteString bs = Prelude.map w2c $ B.unpack bs
|
||||
|
||||
register :: B.ByteString -> Bool -> IO Plugin
|
||||
register wasm useWasi =
|
||||
let length = fromIntegral (B.length wasm) in
|
||||
let wasi = fromInteger (if useWasi then 1 else 0) in
|
||||
do
|
||||
p <- unsafeUseAsCString wasm (\s ->
|
||||
extism_plugin_register (castPtr s) length wasi)
|
||||
return $ Plugin p
|
||||
|
||||
registerManifest :: Manifest -> Bool -> IO Plugin
|
||||
registerManifest manifest useWasi =
|
||||
let wasm = toByteString $ toString manifest in
|
||||
register wasm useWasi
|
||||
|
||||
isValid :: Plugin -> Bool
|
||||
isValid (Plugin p) = p >= 0
|
||||
|
||||
setConfig :: Plugin -> [(String, String)] -> IO ()
|
||||
setConfig (Plugin plugin) x =
|
||||
if plugin < 0
|
||||
then return ()
|
||||
else
|
||||
let obj = toJSObject x in
|
||||
let bs = toByteString (encode obj) in
|
||||
let length = fromIntegral (B.length bs) in
|
||||
unsafeUseAsCString bs (\s -> do
|
||||
void $ extism_plugin_config plugin (castPtr s) length)
|
||||
|
||||
setLogFile :: String -> String -> IO ()
|
||||
setLogFile filename level =
|
||||
withCString filename (\f ->
|
||||
withCString level (\l -> do
|
||||
void $ extism_log_file f l))
|
||||
|
||||
functionExists :: Plugin -> String -> IO Bool
|
||||
functionExists (Plugin plugin) name = do
|
||||
b <- withCString name (extism_function_exists plugin)
|
||||
if b == 1 then return True else return False
|
||||
|
||||
call :: Plugin -> String -> B.ByteString -> IO (Either B.ByteString Error)
|
||||
call (Plugin plugin) name input =
|
||||
let length = fromIntegral (B.length input) in
|
||||
do
|
||||
rc <- withCString name (\name ->
|
||||
unsafeUseAsCString input (\input ->
|
||||
extism_call plugin name (castPtr input) length))
|
||||
err <- extism_error plugin
|
||||
if err /= nullPtr
|
||||
then do e <- peekCString err
|
||||
return $ Right (Error e)
|
||||
else if rc == 0
|
||||
then do
|
||||
length <- extism_output_length plugin
|
||||
let output = B.replicate (fromIntegral length) 0
|
||||
() <- unsafeUseAsCString output (\a ->
|
||||
extism_output_get plugin (castPtr a) length)
|
||||
return $ Left output
|
||||
else return $ Right (Error "Call failed")
|
||||
168
haskell/src/Extism/Manifest.hs
Normal file
168
haskell/src/Extism/Manifest.hs
Normal file
@@ -0,0 +1,168 @@
|
||||
module Extism.Manifest where
|
||||
|
||||
import Text.JSON
|
||||
(
|
||||
JSValue(JSNull, JSString, JSArray),
|
||||
toJSString, showJSON, makeObj, encode
|
||||
)
|
||||
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 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])
|
||||
isNull JSNull = True
|
||||
isNull _ = False
|
||||
|
||||
newtype Memory = Memory
|
||||
{
|
||||
memoryMax :: Maybe Int
|
||||
}
|
||||
|
||||
class JSONValue a where
|
||||
toJSONValue :: a -> JSValue
|
||||
|
||||
instance JSONValue Memory where
|
||||
toJSONValue x =
|
||||
case memoryMax x of
|
||||
Nothing -> makeObj []
|
||||
Just max -> makeObj [("max", showJSON max)]
|
||||
|
||||
data HttpRequest = HttpRequest
|
||||
{
|
||||
url :: String
|
||||
, header :: [(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)
|
||||
]
|
||||
|
||||
instance JSONValue HttpRequest where
|
||||
toJSONValue x =
|
||||
makeObj $ requestObj x
|
||||
|
||||
data WasmFile = WasmFile
|
||||
{
|
||||
filePath :: String
|
||||
, fileName :: Maybe String
|
||||
, fileHash :: Maybe String
|
||||
}
|
||||
|
||||
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)
|
||||
]
|
||||
|
||||
data WasmCode = WasmCode
|
||||
{
|
||||
codeBytes :: B.ByteString
|
||||
, codeName :: Maybe String
|
||||
, codeHash :: Maybe String
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
]
|
||||
|
||||
data WasmURL = WasmURL
|
||||
{
|
||||
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
|
||||
|
||||
data Wasm = File WasmFile | Code WasmCode | URL WasmURL
|
||||
|
||||
instance JSONValue Wasm where
|
||||
toJSONValue x =
|
||||
case x of
|
||||
File f -> toJSONValue f
|
||||
Code d -> toJSONValue d
|
||||
URL u -> toJSONValue u
|
||||
|
||||
wasmFile :: String -> Wasm
|
||||
wasmFile path =
|
||||
File WasmFile { filePath = path, fileName = Nothing, fileHash = Nothing}
|
||||
|
||||
wasmURL :: String -> String -> Wasm
|
||||
wasmURL method url =
|
||||
let r = HttpRequest { url = url, header = [], method = Just method } in
|
||||
URL WasmURL { req = r, urlName = Nothing, urlHash = Nothing }
|
||||
|
||||
wasmCode :: B.ByteString -> Wasm
|
||||
wasmCode code =
|
||||
Code WasmCode { codeBytes = code, codeName = Nothing, codeHash = Nothing }
|
||||
|
||||
withName :: Wasm -> String -> Wasm
|
||||
withName (Code code) name = Code code { codeName = Just name }
|
||||
withName (URL url) name = URL url { urlName = Just name }
|
||||
withName (File f) name = File f { fileName = Just name }
|
||||
|
||||
|
||||
withHash :: Wasm -> String -> Wasm
|
||||
withHash (Code code) hash = Code code { codeHash = Just hash }
|
||||
withHash (URL url) hash = URL url { urlHash = Just hash }
|
||||
withHash (File f) hash = File f { fileHash = Just hash }
|
||||
|
||||
data Manifest = Manifest
|
||||
{
|
||||
wasm :: [Wasm]
|
||||
, memory :: Maybe Memory
|
||||
, config :: [(String, String)]
|
||||
}
|
||||
|
||||
manifest :: [Wasm] -> Manifest
|
||||
manifest wasm = Manifest {wasm = wasm, memory = Nothing, config = []}
|
||||
|
||||
withConfig :: Manifest -> [(String, String)] -> Manifest
|
||||
withConfig m config =
|
||||
m { config = config }
|
||||
|
||||
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
|
||||
makeObj $ filterNulls [
|
||||
("wasm", w),
|
||||
("memory", mem),
|
||||
("config", c)
|
||||
]
|
||||
|
||||
toString :: Manifest -> String
|
||||
toString manifest =
|
||||
encode (toJSONValue manifest)
|
||||
69
haskell/stack.yaml
Normal file
69
haskell/stack.yaml
Normal file
@@ -0,0 +1,69 @@
|
||||
# This file was automatically generated by 'stack init'
|
||||
#
|
||||
# Some commonly used options have been documented as comments in this file.
|
||||
# For advanced use and comprehensive documentation of the format, please see:
|
||||
# https://docs.haskellstack.org/en/stable/yaml_configuration/
|
||||
|
||||
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
|
||||
# A snapshot resolver dictates the compiler version and the set of packages
|
||||
# to be used for project dependencies. For example:
|
||||
#
|
||||
# resolver: lts-3.5
|
||||
# resolver: nightly-2015-09-21
|
||||
# resolver: ghc-7.10.2
|
||||
#
|
||||
# The location of a snapshot can be provided as a file or url. Stack assumes
|
||||
# a snapshot provided as a file might change, whereas a url resource does not.
|
||||
#
|
||||
# resolver: ./custom-snapshot.yaml
|
||||
# resolver: https://example.com/snapshots/2018-01-01.yaml
|
||||
resolver:
|
||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml
|
||||
|
||||
# User packages to be built.
|
||||
# Various formats can be used as shown in the example below.
|
||||
#
|
||||
# packages:
|
||||
# - some-directory
|
||||
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
|
||||
# subdirs:
|
||||
# - auto-update
|
||||
# - wai
|
||||
packages:
|
||||
- .
|
||||
- dist-newstyle/tmp/src-3036517/semialign-1.2.0.1
|
||||
- dist-newstyle/tmp/src-3036516/witherable-0.4.2
|
||||
# Dependency packages to be pulled from upstream that are not in the resolver.
|
||||
# These entries can reference officially published versions as well as
|
||||
# forks / in-progress versions pinned to a git hash. For example:
|
||||
#
|
||||
# extra-deps:
|
||||
# - acme-missiles-0.3
|
||||
# - git: https://github.com/commercialhaskell/stack.git
|
||||
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
|
||||
#
|
||||
# extra-deps: []
|
||||
|
||||
# Override default flag values for local packages and extra-deps
|
||||
# flags: {}
|
||||
|
||||
# Extra package databases containing global packages
|
||||
# extra-package-dbs: []
|
||||
|
||||
# Control whether we use the GHC we find on the path
|
||||
# system-ghc: true
|
||||
#
|
||||
# Require a specific version of stack, using version ranges
|
||||
# require-stack-version: -any # Default
|
||||
# require-stack-version: ">=2.7"
|
||||
#
|
||||
# Override the architecture used by stack, especially useful on Windows
|
||||
# arch: i386
|
||||
# arch: x86_64
|
||||
#
|
||||
# Extra directories used by stack for building
|
||||
# extra-include-dirs: [/path/to/dir]
|
||||
# extra-lib-dirs: [/path/to/dir]
|
||||
#
|
||||
# Allow a newer minor version of GHC than the snapshot specifies
|
||||
# compiler-check: newer-minor
|
||||
13
haskell/stack.yaml.lock
Normal file
13
haskell/stack.yaml.lock
Normal file
@@ -0,0 +1,13 @@
|
||||
# This file was autogenerated by Stack.
|
||||
# You should not edit this file by hand.
|
||||
# For more information, please see the documentation at:
|
||||
# https://docs.haskellstack.org/en/stable/lock_files
|
||||
|
||||
packages: []
|
||||
snapshots:
|
||||
- completed:
|
||||
size: 632828
|
||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml
|
||||
sha256: 5b02c2ce430ac62843fb884126765628da2ca2280bb9de0c6635c723e32a9f6b
|
||||
original:
|
||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml
|
||||
@@ -10,4 +10,16 @@ description = "Extism plug-in manifest crate"
|
||||
|
||||
[dependencies]
|
||||
serde = {version = "1", features=["derive"]}
|
||||
base64 = "0.20.0-alpha"
|
||||
base64 = "0.20.0-alpha"
|
||||
schemars = {version = "0.8", optional=true}
|
||||
|
||||
[features]
|
||||
json_schema = ["schemars"]
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
||||
|
||||
[[example]]
|
||||
name = "json_schema"
|
||||
required-features = ["json_schema"]
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ crate-type = ["cdylib"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
wasmtime = "0.39.1"
|
||||
wasmtime-wasi = "0.39.1"
|
||||
wasmtime = "0.40.0"
|
||||
wasmtime-wasi = "0.40.0"
|
||||
anyhow = "1"
|
||||
serde = { version = "1", features=["derive"] }
|
||||
toml = "0.5"
|
||||
@@ -26,14 +26,13 @@ log = "0.4"
|
||||
log4rs = "1.1"
|
||||
ureq = {version = "2.5", optional=true}
|
||||
extism-manifest = { version = "0.0.1-alpha", path = "../manifest" }
|
||||
pretty-hex = { version = "0.3", optional = true }
|
||||
pretty-hex = { version = "0.3" }
|
||||
|
||||
[features]
|
||||
default = ["http", "register-http", "register-filesystem"]
|
||||
register-http = ["ureq"] # enables wasm to be downloaded using http
|
||||
register-filesystem = [] # enables wasm to be loaded from disk
|
||||
http = ["ureq"] # enables extism_http_request
|
||||
debug = ["pretty-hex"]
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.24"
|
||||
|
||||
@@ -16,4 +16,4 @@ pub use plugin_ref::PluginRef;
|
||||
pub type Size = u64;
|
||||
pub type PluginIndex = i32;
|
||||
|
||||
pub(crate) use log::{debug, error, info};
|
||||
pub(crate) use log::{debug, error, info, trace};
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
|
||||
|
||||
use crate::*;
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
use pretty_hex::PrettyHex;
|
||||
|
||||
/// Handles memory for plugins
|
||||
@@ -32,6 +31,7 @@ impl PluginMemory {
|
||||
}
|
||||
|
||||
pub(crate) fn store_u8(&mut self, offs: usize, data: u8) -> Result<(), MemoryAccessError> {
|
||||
trace!("store_u8: {data:x} at offset {offs}");
|
||||
if offs >= self.size() {
|
||||
// This should raise MemoryAccessError
|
||||
let buf = &mut [0];
|
||||
@@ -44,6 +44,7 @@ impl PluginMemory {
|
||||
|
||||
/// Read from memory
|
||||
pub(crate) fn load_u8(&self, offs: usize) -> Result<u8, MemoryAccessError> {
|
||||
trace!("load_u8: offset {offs}");
|
||||
if offs >= self.size() {
|
||||
// This should raise MemoryAccessError
|
||||
let buf = &mut [0];
|
||||
@@ -54,6 +55,7 @@ impl PluginMemory {
|
||||
}
|
||||
|
||||
pub(crate) fn store_u32(&mut self, offs: usize, data: u32) -> Result<(), MemoryAccessError> {
|
||||
trace!("store_u32: {data:x} at offset {offs}");
|
||||
let handle = MemoryBlock {
|
||||
offset: offs,
|
||||
length: 4,
|
||||
@@ -64,6 +66,7 @@ impl PluginMemory {
|
||||
|
||||
/// Read from memory
|
||||
pub(crate) fn load_u32(&self, offs: usize) -> Result<u32, MemoryAccessError> {
|
||||
trace!("load_u32: offset {offs}");
|
||||
let mut buf = [0; 4];
|
||||
|
||||
let handle = MemoryBlock {
|
||||
@@ -75,6 +78,7 @@ impl PluginMemory {
|
||||
}
|
||||
|
||||
pub(crate) fn store_u64(&mut self, offs: usize, data: u64) -> Result<(), MemoryAccessError> {
|
||||
trace!("store_u64: {data:x} at offset {offs}");
|
||||
let handle = MemoryBlock {
|
||||
offset: offs,
|
||||
length: 8,
|
||||
@@ -84,6 +88,7 @@ impl PluginMemory {
|
||||
}
|
||||
|
||||
pub(crate) fn load_u64(&self, offs: usize) -> Result<u64, MemoryAccessError> {
|
||||
trace!("load_u64: offset {offs}");
|
||||
let mut buf = [0; 8];
|
||||
let handle = MemoryBlock {
|
||||
offset: offs,
|
||||
@@ -222,11 +227,10 @@ impl PluginMemory {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub fn dump(&self) {
|
||||
let data = self.memory.data(&self.store);
|
||||
|
||||
println!("{:?}", data[..self.position].hex_dump());
|
||||
trace!("{:?}", data[..self.position].hex_dump());
|
||||
}
|
||||
|
||||
/// Reset memory
|
||||
|
||||
@@ -152,6 +152,7 @@ impl Plugin {
|
||||
|
||||
/// Set `last_error` field
|
||||
pub fn set_error(&mut self, e: impl std::fmt::Debug) {
|
||||
debug!("Set error: {:?}", e);
|
||||
let x = format!("{:?}", e).into_bytes();
|
||||
let x = if x[0] == b'"' && x[x.len() - 1] == b'"' {
|
||||
x[1..x.len() - 1].to_vec()
|
||||
@@ -181,7 +182,6 @@ impl Plugin {
|
||||
internal.plugin = ptr;
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub fn dump_memory(&self) {
|
||||
self.memory.dump();
|
||||
}
|
||||
|
||||
@@ -2,12 +2,17 @@ use crate::*;
|
||||
|
||||
// PluginRef is used to access a plugin from the global plugin registry
|
||||
pub struct PluginRef<'a> {
|
||||
pub id: PluginIndex,
|
||||
pub plugins: std::sync::MutexGuard<'a, Vec<Plugin>>,
|
||||
plugin: *mut Plugin,
|
||||
}
|
||||
|
||||
impl<'a> PluginRef<'a> {
|
||||
pub fn init(mut self) -> Self {
|
||||
trace!(
|
||||
"Resetting memory and clearing error message for plugin {}",
|
||||
self.id,
|
||||
);
|
||||
// Initialize
|
||||
self.as_mut().clear_error();
|
||||
self.as_mut().memory.reset();
|
||||
@@ -17,20 +22,26 @@ impl<'a> PluginRef<'a> {
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is used to access the static `PLUGINS` registry
|
||||
pub unsafe fn new(plugin: PluginIndex) -> Self {
|
||||
pub unsafe fn new(plugin_id: PluginIndex) -> Self {
|
||||
let mut plugins = match PLUGINS.lock() {
|
||||
Ok(p) => p,
|
||||
Err(e) => e.into_inner(),
|
||||
};
|
||||
|
||||
if plugin < 0 || plugin as usize >= plugins.len() {
|
||||
trace!("Loading plugin {plugin_id}");
|
||||
|
||||
if plugin_id < 0 || plugin_id as usize >= plugins.len() {
|
||||
drop(plugins);
|
||||
panic!("Invalid PluginIndex {plugin}");
|
||||
panic!("Invalid PluginIndex {plugin_id}");
|
||||
}
|
||||
|
||||
let plugin = plugins.get_unchecked_mut(plugin as usize) as *mut _;
|
||||
let plugin = plugins.get_unchecked_mut(plugin_id as usize) as *mut _;
|
||||
|
||||
PluginRef { plugins, plugin }
|
||||
PluginRef {
|
||||
id: plugin_id,
|
||||
plugins,
|
||||
plugin,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +59,7 @@ impl<'a> AsMut<Plugin> for PluginRef<'a> {
|
||||
|
||||
impl<'a> Drop for PluginRef<'a> {
|
||||
fn drop(&mut self) {
|
||||
trace!("Dropping plugin {}", self.id);
|
||||
// Cleanup?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ pub unsafe extern "C" fn extism_plugin_register(
|
||||
wasm_size: Size,
|
||||
with_wasi: bool,
|
||||
) -> PluginIndex {
|
||||
trace!(
|
||||
"Call to extism_plugin_register with wasm pointer {:?}",
|
||||
wasm
|
||||
);
|
||||
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
|
||||
let plugin = match Plugin::new(data, with_wasi) {
|
||||
Ok(x) => x,
|
||||
@@ -40,6 +44,12 @@ pub unsafe extern "C" fn extism_plugin_config(
|
||||
) -> bool {
|
||||
let mut plugin = PluginRef::new(plugin);
|
||||
|
||||
trace!(
|
||||
"Call to extism_plugin_config for {} with json pointer {:?}",
|
||||
plugin.id,
|
||||
json
|
||||
);
|
||||
|
||||
let data = std::slice::from_raw_parts(json, json_size as usize);
|
||||
let json: std::collections::BTreeMap<String, String> = match serde_json::from_slice(data) {
|
||||
Ok(x) => x,
|
||||
@@ -53,6 +63,7 @@ pub unsafe extern "C" fn extism_plugin_config(
|
||||
let wasi = &mut plugin.memory.store.data_mut().wasi;
|
||||
let config = &mut plugin.manifest.as_mut().config;
|
||||
for (k, v) in json.into_iter() {
|
||||
trace!("Config, adding {k}");
|
||||
let _ = wasi.push_env(&k, &v);
|
||||
config.insert(k, v);
|
||||
}
|
||||
@@ -107,7 +118,6 @@ pub unsafe extern "C" fn extism_call(
|
||||
Err(e) => return plugin.error(e.context("Unable to allocate bytes"), -1),
|
||||
};
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
plugin.dump_memory();
|
||||
|
||||
// Always needs to be called before `func.call()`
|
||||
@@ -118,14 +128,12 @@ pub unsafe extern "C" fn extism_call(
|
||||
match func.call(&mut plugin.memory.store, &[], results.as_mut_slice()) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
#[cfg(feature = "debug")]
|
||||
plugin.dump_memory();
|
||||
error!("Call: {e:?}");
|
||||
return plugin.error(e.context("Call failed"), -1);
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
plugin.dump_memory();
|
||||
|
||||
// Return result to caller
|
||||
@@ -134,22 +142,31 @@ pub unsafe extern "C" fn extism_call(
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_error(plugin: PluginIndex) -> *const c_char {
|
||||
trace!("Call to extism_error for plugin {plugin}");
|
||||
let plugin = PluginRef::new(plugin);
|
||||
match &plugin.as_ref().last_error {
|
||||
Some(e) => e.as_ptr() as *const _,
|
||||
None => std::ptr::null(),
|
||||
None => {
|
||||
trace!("Error is NULL");
|
||||
std::ptr::null()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_output_length(plugin: PluginIndex) -> Size {
|
||||
trace!("Call to extism_output_length for plugin {plugin}");
|
||||
let plugin = PluginRef::new(plugin);
|
||||
|
||||
plugin.as_ref().memory.store.data().output_length as Size
|
||||
let len = plugin.as_ref().memory.store.data().output_length as Size;
|
||||
trace!("Output length: {len}");
|
||||
len
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_output_get(plugin: PluginIndex, buf: *mut u8, len: Size) {
|
||||
trace!("Call to extism_output_get for plugin {plugin}, length {len}");
|
||||
|
||||
let plugin = PluginRef::new(plugin);
|
||||
let data = plugin.as_ref().memory.store.data();
|
||||
|
||||
@@ -170,16 +187,21 @@ pub unsafe extern "C" fn extism_log_file(
|
||||
log_level: *const c_char,
|
||||
) -> bool {
|
||||
use log::LevelFilter;
|
||||
use log4rs::append::console::ConsoleAppender;
|
||||
use log4rs::append::file::FileAppender;
|
||||
use log4rs::config::{Appender, Config, Root};
|
||||
use log4rs::config::{Appender, Config, Logger, Root};
|
||||
use log4rs::encode::pattern::PatternEncoder;
|
||||
|
||||
let file = std::ffi::CStr::from_ptr(filename);
|
||||
let file = match file.to_str() {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return false;
|
||||
let file = if !filename.is_null() {
|
||||
let file = std::ffi::CStr::from_ptr(filename);
|
||||
match file.to_str() {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
"-"
|
||||
};
|
||||
|
||||
let level = if log_level.is_null() {
|
||||
@@ -201,28 +223,34 @@ pub unsafe extern "C" fn extism_log_file(
|
||||
}
|
||||
};
|
||||
|
||||
let logfile = match FileAppender::builder()
|
||||
.encoder(Box::new(PatternEncoder::new("{d}: {l} - {m}\n")))
|
||||
.build(file)
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Unable to set up log encoder: {e:?}");
|
||||
return false;
|
||||
let encoder = Box::new(PatternEncoder::new("{t} {l} {d} (({f}:{L})) - {m}\n"));
|
||||
|
||||
let logfile: Box<dyn log4rs::append::Append> = if file == "-" {
|
||||
let console = ConsoleAppender::builder().encoder(encoder);
|
||||
Box::new(console.build())
|
||||
} else {
|
||||
match FileAppender::builder().encoder(encoder).build(file) {
|
||||
Ok(x) => Box::new(x),
|
||||
Err(e) => {
|
||||
error!("Unable to set up log encoder: {e:?}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let config = match Config::builder()
|
||||
.appender(Appender::builder().build("logfile", Box::new(logfile)))
|
||||
.build(Root::builder().appender("logfile").build(level))
|
||||
.appender(Appender::builder().build("logfile", logfile))
|
||||
.logger(Logger::builder().appender("logfile").build("extism", level))
|
||||
.build(Root::builder().build(LevelFilter::Off))
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Unable to configure log file: {e:?}");
|
||||
Err(_) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
log4rs::init_config(config).expect("Log initialization failed");
|
||||
if log4rs::init_config(config).is_err() {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user