From 06aacbcb9ed52b1c4184194aca16a1c2709e7593 Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Fri, 29 Sep 2023 18:46:10 -0500 Subject: [PATCH] feat: create a manifest class --- README.md | 16 +++---- lib/extism.rb | 97 +++++++++++++++++++++--------------------- lib/extism/manifest.rb | 63 +++++++++++++++++++++++++++ lib/extism/plugin.rb | 18 +++++--- test/test_extism.rb | 25 ++--------- 5 files changed, 132 insertions(+), 87 deletions(-) create mode 100644 lib/extism/manifest.rb diff --git a/README.md b/README.md index 9dfe6d8..9851e90 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. diff --git a/lib/extism.rb b/lib/extism.rb index 777879d..0852069 100644 --- a/lib/extism.rb +++ b/lib/extism.rb @@ -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 diff --git a/lib/extism/manifest.rb b/lib/extism/manifest.rb new file mode 100644 index 0000000..b24b16b --- /dev/null +++ b/lib/extism/manifest.rb @@ -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 diff --git a/lib/extism/plugin.rb b/lib/extism/plugin.rb index 3bac185..6158e1a 100644 --- a/lib/extism/plugin.rb +++ b/lib/extism/plugin.rb @@ -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 @@ -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) diff --git a/test/test_extism.rb b/test/test_extism.rb index a50297b..f62f7f3 100644 --- a/test/test_extism.rb +++ b/test/test_extism.rb @@ -14,6 +14,7 @@ amount_in_cents: 0 } } + class Environment include Extism::HostEnvironment @@ -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