module Extism.Manifest where
import Extism.JSON
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BS (unpack)
-- | Memory options
newtype Memory = Memory
{
memoryMaxPages :: Nullable Int
}
instance JSON Memory where
showJSON (Memory max) =
object [
"max_pages" .= max
]
readJSON obj =
let max = obj .? "max_pages" in
Ok (Memory max)
-- | HTTP request
data HTTPRequest = HTTPRequest
{
url :: String
, headers :: Nullable [(String, String)]
, method :: Nullable String
}
makeKV x =
object [(k, showJSON v) | (k, v) <- x]
requestObj (HTTPRequest url headers method) =
[
"url" .= url,
"headers" .= mapNullable makeKV headers,
"method" .= method
]
instance JSON HTTPRequest where
showJSON req = object $ requestObj req
readJSON x =
let url = x .? "url" in
let headers = x .? "headers" in
let method = x .? "method" in
case url of
Null -> Error "Missing 'url' field"
NotNull url -> Ok (HTTPRequest url headers method)
-- | WASM from file
data WasmFile = WasmFile
{
filePath :: String
, fileName :: Nullable String
, fileHash :: Nullable String
}
instance JSON WasmFile where
showJSON (WasmFile path name hash) =
object [
"path" .= path,
"name" .= name,
"hash" .= hash
]
readJSON x =
let path = x .? "url" in
let name = x .? "name" in
let hash = x .? "hash" in
case path of
Null -> Error "Missing 'path' field"
NotNull path -> Ok (WasmFile path name hash)
-- | WASM from raw bytes
data WasmData = WasmData
{
dataBytes :: Base64
, dataName :: Nullable String
, dataHash :: Nullable String
}
instance JSON WasmData where
showJSON (WasmData bytes name hash) =
object [
"data" .= bytes,
"name" .= name,
"hash" .= hash
]
readJSON x =
let d = x .? "data" in
let name = x .? "name" in
let hash = x .? "hash" in
case d of
Null -> Error "Missing 'path' field"
NotNull d ->
case readJSON d of
Error msg -> Error msg
Ok d' -> Ok (WasmData d' name hash)
-- | WASM from a URL
data WasmURL = WasmURL
{
req :: HTTPRequest
, urlName :: Nullable String
, urlHash :: Nullable String
}
instance JSON WasmURL where
showJSON (WasmURL req name hash) =
object (
"name" .= name :
"hash" .= hash :
requestObj req)
readJSON x =
let req = x .? "req" in
let name = x .? "name" in
let hash = x .? "hash" in
case fromNullable req of
Nothing -> Error "Missing 'req' field"
Just req -> Ok (WasmURL req name hash)
-- | Specifies where to get WASM module data
data Wasm = File WasmFile | Data WasmData | URL WasmURL
instance JSON Wasm where
showJSON x =
case x of
File f -> showJSON f
Data d -> showJSON d
URL u -> showJSON u
readJSON x =
let file = (readJSON x :: Result WasmFile) in
case file of
Ok x -> Ok (File x)
Error _ ->
let data' = (readJSON x :: Result WasmData) in
case data' of
Ok x -> Ok (Data x)
Error _ ->
let url = (readJSON x :: Result WasmURL) in
case url of
Ok x -> Ok (URL x)
Error _ -> Error "JSON does not match any of the Wasm types"
wasmFile :: String -> Wasm
wasmFile path =
File WasmFile { filePath = path, fileName = null', fileHash = null'}
wasmURL :: String -> String -> Wasm
wasmURL method url =
let r = HTTPRequest { url = url, headers = null', method = nonNull method } in
URL WasmURL { req = r, urlName = null', urlHash = null' }
wasmData :: B.ByteString -> Wasm
wasmData d =
Data WasmData { dataBytes = Base64 d, dataName = null', dataHash = null' }
withName :: Wasm -> String -> Wasm
withName (Data d) name = Data d { dataName = nonNull name }
withName (URL url) name = URL url { urlName = nonNull name }
withName (File f) name = File f { fileName = nonNull name }
withHash :: Wasm -> String -> Wasm
withHash (Data d) hash = Data d { dataHash = nonNull hash }
withHash (URL url) hash = URL url { urlHash = nonNull hash }
withHash (File f) hash = File f { fileHash = nonNull hash }
-- | The 'Manifest' type is used to provide WASM data and configuration to the
-- | Extism runtime
data Manifest = Manifest
{
wasm :: [Wasm]
, memory :: Nullable Memory
, config :: Nullable [(String, String)]
, allowedHosts :: Nullable [String]
, allowedPaths :: Nullable [(String, String)]
, timeout :: Nullable Int
}
instance JSON Manifest where
showJSON (Manifest wasm memory config hosts paths timeout) =
let w = makeArray wasm in
object [
"wasm" .= w,
"memory" .= memory,
"config" .= mapNullable makeKV config,
"allowed_hosts" .= hosts,
"allowed_paths" .= mapNullable makeKV paths,
"timeout_ms" .= timeout
]
readJSON x =
let wasm = x .? "wasm" in
let memory = x .? "memory" in
let config = x .? "config" in
let hosts = x .? "allowed_hosts" in
let paths = x .? "allowed_paths" in
let timeout = x .? "timeout_ms" in
case fromNullable wasm of
Nothing -> Error "Missing 'wasm' field"
Just wasm -> Ok (Manifest wasm memory config hosts paths timeout)
-- | Create a new 'Manifest' from a list of 'Wasm'
manifest :: [Wasm] -> Manifest
manifest wasm =
Manifest {
wasm = wasm,
memory = null',
config = null',
allowedHosts = null',
allowedPaths = null',
timeout = null'
}
-- | Update the config values
withConfig :: Manifest -> [(String, String)] -> Manifest
withConfig m config =
m { config = nonNull config }
-- | Update allowed hosts for `extism_http_request`
withHosts :: Manifest -> [String] -> Manifest
withHosts m hosts =
m { allowedHosts = nonNull hosts }
-- | Update allowed paths
withPaths :: Manifest -> [(String, String)] -> Manifest
withPaths m p =
m { allowedPaths = nonNull p }
-- | Update plugin timeout (in milliseconds)
withTimeout :: Manifest -> Int -> Manifest
withTimeout m t =
m { timeout = nonNull t }
toString :: (JSON a) => a -> String
toString v =
encode (showJSON v)