Skip to content

Commit

Permalink
feat: create a manifest class
Browse files Browse the repository at this point in the history
  • Loading branch information
bhelx committed Sep 29, 2023
1 parent e27c730 commit 06aacbc
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 87 deletions.
16 changes: 5 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,12 @@ The primary concept in Extism is the plug-in. You can think of a plug-in as a co
You'll generally load the plug-in from disk, but for simplicity let's load a pre-built demo plug-in from the web:

```ruby
manifest = {
wasm: [
{ url: "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm" }
]
}
url = "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"
manifest = Extism::Manifest.from_url
plugin = Extism::Plugin.new(manifest)
```

> **Note**: The schema for this manifest can be found here: [https://extism.org/docs/concepts/manifest/](https://extism.org/docs/concepts/manifest/)
> **Note**: See [the Manifest docs](https://extism.github.io/ruby-sdk/Extism/Manifest.html) as it has a rich schema and a lot of options.
### Calling A Plug-in's Exports

Expand Down Expand Up @@ -115,11 +112,8 @@ When a [charge.succeeded](https://stripe.com/docs/api/events/types#event_types-c
First let's create the manifest for our plug-in like usual but load up the `store_credit` plug-in:

```ruby
manifest = {
wasm: [
{ url: "https://github.com/extism/plugins/releases/latest/download/store_credit.wasm" }
]
}
url = "https://github.com/extism/plugins/releases/latest/download/store_credit.wasm"
manifest = Extism::Manifest.from_url(url)
```

But, unlike our `count_vowels` plug-in, this plug-in expects you to provide host functions that satisfy our plug-in's imports.
Expand Down
97 changes: 49 additions & 48 deletions lib/extism.rb
Original file line number Diff line number Diff line change
@@ -1,48 +1,49 @@
require 'ffi'
require 'json'
require_relative './extism/version'
require_relative './extism/plugin'
require_relative './extism/current_plugin'
require_relative './extism/libextism'
require_relative './extism/wasm'
require_relative './extism/host_environment'

module Extism
class Error < StandardError
end

# Return the version of Extism
#
# @return [String] The version string of the Extism runtime
def self.extism_version
LibExtism.extism_version
end

# Set log file and level, this is a global configuration
# @param name [String] The path to the logfile
# @param level [String] The log level. One of {"debug", "error", "info", "trace" }
def self.set_log_file(name, level = nil)
LibExtism.extism_log_file(name, level)
end

$PLUGINS = {}
$FREE_PLUGIN = proc { |ptr|
x = $PLUGINS[ptr]
unless x.nil?
LibExtism.extism_plugin_free(x[:plugin])
$PLUGINS.delete(ptr)
end
}

# Represents a "block" of memory in Extism.
# This memory is in the communication buffer b/w the
# guest in the host and technically lives in host memory.
class Memory
attr_reader :offset, :len

def initialize(offset, len)
@offset = offset
@len = len
end
end
end
require 'ffi'
require 'json'
require_relative './extism/manifest'
require_relative './extism/version'
require_relative './extism/plugin'
require_relative './extism/current_plugin'
require_relative './extism/libextism'
require_relative './extism/wasm'
require_relative './extism/host_environment'

module Extism
class Error < StandardError
end

# Return the version of Extism
#
# @return [String] The version string of the Extism runtime
def self.extism_version
LibExtism.extism_version
end

# Set log file and level, this is a global configuration
# @param name [String] The path to the logfile
# @param level [String] The log level. One of {"debug", "error", "info", "trace" }
def self.set_log_file(name, level = nil)
LibExtism.extism_log_file(name, level)
end

$PLUGINS = {}
$FREE_PLUGIN = proc { |ptr|
x = $PLUGINS[ptr]
unless x.nil?
LibExtism.extism_plugin_free(x[:plugin])
$PLUGINS.delete(ptr)
end
}

# Represents a "block" of memory in Extism.
# This memory is in the communication buffer b/w the
# guest in the host and technically lives in host memory.
class Memory
attr_reader :offset, :len

def initialize(offset, len)
@offset = offset
@len = len
end
end
end
63 changes: 63 additions & 0 deletions lib/extism/manifest.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module Extism
class Manifest
attr_reader :manifest_data

# Create a manifest of a single wasm from url.
# Look at {Manifest#new} for an interface with more control
#
# @see Manifest::new
# @param [String] url The url to the wasm module
# @param [String | nil] url An optional sha256 integrity hash. Defaults to nil
# @param [String | nil] An optional name. Defaults to nil
# @returns [Extism::Manifest]
def self.from_url(url, hash: nil, name: nil)
wasm = { url: url }
wasm[:hash] = hash unless hash.nil?
wasm[:name] = name unless hash.nil?

Manifest.new({ wasm: [wasm] })
end

# Create a manifest of a single wasm from file path.
# Look at {Manifest#new} for an interface with more control
#
# @see Manifest::new
# @param [String] path The path to the wasm module on disk
# @param [String | nil] url An optional sha256 integrity hash. Defaults to nil
# @param [String | nil] An optional name. Defaults to nil
# @returns [Extism::Manifest]
def self.from_path(path, hash: nil, name: nil)
wasm = { path: path }
wasm[:hash] = hash unless hash.nil?
wasm[:name] = name unless hash.nil?

Manifest.new({ wasm: [wasm] })
end

# Create a manifest of a single wasm module with raw binary data.
# Look at {Manifest#new} for an interface with more control
# Consider using a file path instead of the raw wasm binary in memory.
# The performance is often better letting the runtime load the binary itself.
#
# @see Manifest::new
# @param [String] The binary data of the wasm module
# @param [String | nil] hash An optional sha256 integrity hash. Defaults to nil
# @param [String | nil] name An optional name. Defaults to nil
# @returns [Extism::Manifest]
def self.from_bytes(data, hash: nil, name: nil)
wasm = { data: data }
wasm[:hash] = hash unless hash.nil?
wasm[:name] = name unless hash.nil?

Manifest.new({ wasm: [wasm] })
end

# Initialize a manifest
# See https://extism.org/docs/concepts/manifest for schema
#
# @param
def initialize(hash)
@manifest_data = hash
end
end
end
18 changes: 11 additions & 7 deletions lib/extism/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ class Plugin
# Intialize a plugin
#
# @example Initialize a plugin from a url
# manifest = {
# wasm: [
# { url: "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm" }
# ]
# }
# manifest = Extism::Manifest.from_url "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"
# plugin = Extism::Plugin.new(manifest)
#
# @example Pass a config object to configure the plug-in
Expand All @@ -18,11 +14,19 @@ class Plugin
# @example Initalize a plug-in that needs WASI
# plugin = Extism::Plugin.new(manifest, wasi: true)
#
# @param wasm [Hash, String] The manifest as a Hash or WASM binary as a String. See https://extism.org/docs/concepts/manifest/.
# @param wasm [Hash, String, Manifest] The manifest as a Hash or WASM binary as a String. See https://extism.org/docs/concepts/manifest/.
# @param wasi [Boolean] Enable WASI support
# @param config [Hash] The plugin config
def initialize(wasm, environment: nil, functions: [], wasi: false, config: nil)
wasm = JSON.generate(wasm) if wasm.instance_of?(Hash)
wasm = case wasm
when Hash
JSON.generate(wasm)
when Manifest
JSON.generate(wasm.manifest_data)
else
wasm
end

code = FFI::MemoryPointer.new(:char, wasm.bytesize)
errmsg = FFI::MemoryPointer.new(:pointer)
code.put_bytes(0, wasm)
Expand Down
25 changes: 4 additions & 21 deletions test/test_extism.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
amount_in_cents: 0
}
}

class Environment
include Extism::HostEnvironment

Expand Down Expand Up @@ -107,32 +108,14 @@ def test_environment
private

def vowels_manifest
{
wasm: [
{
path: File.join(__dir__, '../wasm/count_vowels.wasm')
}
]
}
Extism::Manifest.from_path File.join(__dir__, '../wasm/count_vowels.wasm')
end

def reflect_manifest
{
wasm: [
{
path: File.join(__dir__, '../wasm/reflect.wasm')
}
]
}
Extism::Manifest.from_path File.join(__dir__, '../wasm/reflect.wasm')
end

def store_credit_manifest
{
wasm: [
{
path: File.join(__dir__, '../wasm/store_credit.wasm')
}
]
}
Extism::Manifest.from_path File.join(__dir__, '../wasm/store_credit.wasm')
end
end

0 comments on commit 06aacbc

Please sign in to comment.