Compare commits

..

8 Commits

Author SHA1 Message Date
zach
014686e6e4 docs: remove version from readme 2025-07-14 11:58:07 -07:00
zach
52c160b9ec v1.12.0 2025-07-14 11:04:16 -07:00
dependabot[bot]
f68a548df4 chore(deps): Update toml requirement from 0.8 to 0.9 (#874)
Updates the requirements on [toml](https://github.com/toml-rs/toml) to
permit the latest version.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c28f9ac30f"><code>c28f9ac</code></a>
chore: Release</li>
<li><a
href="f3a2299148"><code>f3a2299</code></a>
docs: Update changelog</li>
<li><a
href="69f09d3093"><code>69f09d3</code></a>
fix(lex): Don't loop over ')' for forever (<a
href="https://redirect.github.com/toml-rs/toml/issues/1003">#1003</a>)</li>
<li><a
href="cc68ae4f42"><code>cc68ae4</code></a>
fix(lex): Don't loop over ')' for forever</li>
<li><a
href="8c8ef44ea1"><code>8c8ef44</code></a>
chore: Release</li>
<li><a
href="b60ac5bfe9"><code>b60ac5b</code></a>
fix(toml): Correct minimal version for indexmap (<a
href="https://redirect.github.com/toml-rs/toml/issues/998">#998</a>)</li>
<li><a
href="966bd40511"><code>966bd40</code></a>
fix(toml): Correct minimal version for indexmap</li>
<li><a
href="2ed2af6519"><code>2ed2af6</code></a>
docs(readme): Mention additional crates</li>
<li><a
href="c7d93e5524"><code>c7d93e5</code></a>
chore: Release</li>
<li><a
href="1ac3aa136c"><code>1ac3aa1</code></a>
chore: Release</li>
<li>Additional commits viewable in <a
href="https://github.com/toml-rs/toml/compare/toml-v0.8.0...toml-v0.9.2">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-14 09:44:00 -07:00
zach
3d15c76115 fix: set ureq http_status_as_error to false to allow access to headers/body of non-200 responses (#873)
Fixes #872
2025-07-10 12:39:31 -07:00
Nutomic
0f4c32e68d Disable unused wasmtime features (#858)
This PR makes it possible to disable all default dependencies for wasmtime

---------

Co-authored-by: zach <zachshipko@gmail.com>
Co-authored-by: zach <zach@dylibso.com>
2025-07-10 12:38:58 -07:00
Nutomic
9e5729b103 Add Pool.function_exists with caching (#869)
This wrapper caches the result of `plugin.function_exists`, to avoid
having to load a plugin from the pool every single time just to find out
if the given function exists. It can improve performance if there are
many plugin hooks without corresponding plugin functions.

---------

Co-authored-by: zach <zach@dylibso.com>
2025-07-08 09:32:36 -07:00
Binlogo
04cf39e751 docs: fix runtime with_config_key usage in runtime/README.md (#870)
Update the code to fit `with_config_key` function calling.
2025-07-08 10:09:01 -04:00
dependabot[bot]
7133dfc4e0 chore(deps): Update prost requirement from 0.13.1 to 0.14.1 (#865)
Updates the requirements on [prost](https://github.com/tokio-rs/prost)
to permit the latest version.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/prost/blob/master/CHANGELOG.md">prost's
changelog</a>.</em></p>
<blockquote>
<h1>Prost version 0.14.1</h1>
<p><em>PROST!</em> is a <a
href="https://developers.google.com/protocol-buffers/">Protocol
Buffers</a> implementation for the <a
href="https://www.rust-lang.org/">Rust Language</a>. <code>prost</code>
generates simple, idiomatic Rust code from <code>proto2</code> and
<code>proto3</code> files.</p>
<h2>⚠️ Revert emission of <code>rerun</code> commands</h2>
<p>Version 0.14.1 reverts the emission of <code>rerun</code> commands.
Other than this change, it is identical to 0.14.0.</p>
<p>In version 0.14.0, <code>prost-build</code> began emitting
<code>rerun</code> commands. While intended to improve build
correctness, this change caused regressions for some users—for example,
those generating <code>protos</code> from an <code>includes</code>
directory. These edge cases are difficult to address reliably, so the
change has been rolled back in 0.14.1.</p>
<p>For more details, see [issue <a
href="https://redirect.github.com/tokio-rs/prost/issues/1296">#1296</a>](<a
href="https://redirect.github.com/tokio-rs/prost/issues/1296">tokio-rs/prost#1296</a>).</p>
<h2>Breaking changes</h2>
<ul>
<li>
<p>prost: Relax Message Debug trait bound (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1147">#1147</a>)</p>
<p>BREAKING CHANGE: <code>trait Debug</code> was a supertrait of
<code>trait Message</code>. This is no longer required by
<code>prost</code>. If your code relies on <code>trait Debug</code>
being implemented for every <code>impl Message</code>, you must now
explicitly state that you require both Debug and Message. For example:
<code>where M: Debug + Message</code></p>
</li>
<li>
<p>prost: Remove prost-derive feature (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1247">#1247</a>)</p>
<p>BREAKING CHANGE: Feature flag <code>prost-derive</code> is renamed to
<code>derive</code>. Please rename any usage of
<code>prost-derive</code> feature in your <code>Cargo.toml</code>.</p>
</li>
<li>
<p>prost-build: Prevent repeated fields to be boxed (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1237">#1237</a>)</p>
<p>BREAKING CHANGE: A repeated field that is manually marked as boxed
was typed as <code>Vec&lt;Box&lt;T&gt;&gt;</code>. Those fields are now
simply typed as <code>Vec&lt;T&gt;</code> to prevent double indirection.
The <code>boxed</code> configuration is effectively ignored for repeated
fields.</p>
</li>
<li>
<p>prost-build: Make <code>type_name_domain</code> cumulative (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1228">#1228</a>)</p>
<p>BREAKING CHANGE: The configuration for domain names of messages is
now cumulative. All calls to
<code>prost_build::Config::type_name_domain</code> are now concatenated.
The previous behavior was that only the arguments of the last call were
used. If you do multiple calls to type_name_domain, you need to remove
all but the last call to maintain the same behavior.</p>
</li>
<li>
<p>prost-build: Derive Eq and Hash trait for messages where possible (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1175">#1175</a>)</p>
<p>BREAKING CHANGE: <code>prost-build</code> will automatically derive
<code>trait Eq</code> and <code>trait Hash</code> for types where all
field support those as well. If you manually <code>impl Eq</code> and/or
<code>impl Hash</code> for generated types, then you need to remove the
manual implementation. If you use <code>type_attribute</code> to
<code>derive(Eq)</code> and/or <code>derive(Hash)</code>, then you need
to remove those.</p>
</li>
</ul>
<h2>Features</h2>
<ul>
<li>prost-types: Implement conversion <code>Duration</code> to/from
<code>chrono::TimeDelta</code> (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1236">#1236</a>)</li>
<li>prost-build: Prepare for 2024 keyword <code>gen</code> (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1257">#1257</a>)</li>
</ul>
<h2>Dependencies</h2>
<ul>
<li><em>(deps)</em> Update pulldown-cmark to 0.13 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1259">#1259</a>)</li>
<li><em>(deps)</em> update criterion requirement from 0.5 to 0.6 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1280">#1280</a>)</li>
</ul>
<h2>Documentation</h2>
<ul>
<li>Update dead link LICENSE in <code>prost-types/README.md</code> (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1262">#1262</a>)</li>
</ul>
<h2>Styling</h2>
<ul>
<li>Use DoubleEndedIterator::next_back (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1255">#1255</a>)</li>
<li>Fix typo (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1260">#1260</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9965a988a7"><code>9965a98</code></a>
chore: Release version 0.14.1 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1299">#1299</a>)</li>
<li><a
href="0caca2977d"><code>0caca29</code></a>
Revert &quot;feat(prost-build): emit <code>rerun</code> commands (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1140">#1140</a>)&quot;
(<a
href="https://redirect.github.com/tokio-rs/prost/issues/1297">#1297</a>)</li>
<li><a
href="3543eb8001"><code>3543eb8</code></a>
chore: Release version 0.14.0 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1276">#1276</a>)</li>
<li><a
href="1e93f5612d"><code>1e93f56</code></a>
build(deps): update criterion requirement from 0.5 to 0.6 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1280">#1280</a>)</li>
<li><a
href="bdd03fcb8d"><code>bdd03fc</code></a>
Update config.rs (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1270">#1270</a>)</li>
<li><a
href="fcf610edf5"><code>fcf610e</code></a>
ci: Run clippy with edition 2024 enabled (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1256">#1256</a>)</li>
<li><a
href="8a3d42e5a3"><code>8a3d42e</code></a>
docs: update dead link LICENSE in <code>prost-types/README.md</code> (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1262">#1262</a>)</li>
<li><a
href="97d5841c3b"><code>97d5841</code></a>
chore: fix typo (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1260">#1260</a>)</li>
<li><a
href="5c97cf88e7"><code>5c97cf8</code></a>
build(deps): Update pulldown-cmark to 0.13 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1259">#1259</a>)</li>
<li><a
href="db871b4e71"><code>db871b4</code></a>
harden <code>ref mut</code> according to edition 2024 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1248">#1248</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tokio-rs/prost/compare/v0.13.1...v0.14.1">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-25 11:21:24 -07:00
19 changed files with 123 additions and 192 deletions

View File

@@ -13,7 +13,7 @@ description = "Traits to make Rust types usable with Extism"
anyhow = "1.0.75"
base64 = "~0.22"
bytemuck = {version = "1.14.0", optional = true }
prost = { version = "0.13.1", optional = true }
prost = { version = "0.14.1", optional = true }
protobuf = { version = "3.2.0", optional = true }
rmp-serde = { version = "1.1.2", optional = true }
serde = "1.0.186"

View File

@@ -1,10 +1,6 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
mod local_path;
pub use local_path::LocalPath;
#[deprecated]
pub type ManifestMemory = MemoryOptions;
@@ -283,7 +279,7 @@ pub struct Manifest {
/// the path on disk to the path it should be available inside the plugin.
/// For example, `".": "/tmp"` would mount the current directory as `/tmp` inside the module
#[serde(default)]
pub allowed_paths: Option<BTreeMap<PathBuf, LocalPath>>,
pub allowed_paths: Option<BTreeMap<String, PathBuf>>,
/// The plugin timeout in milliseconds
#[serde(default)]
@@ -341,15 +337,15 @@ impl Manifest {
}
/// Add a path to `allowed_paths`
pub fn with_allowed_path(mut self, src: impl Into<LocalPath>, dest: impl AsRef<Path>) -> Self {
pub fn with_allowed_path(mut self, src: String, dest: impl AsRef<Path>) -> Self {
let dest = dest.as_ref().to_path_buf();
match &mut self.allowed_paths {
Some(p) => {
p.insert(dest, src.into());
p.insert(src, dest);
}
None => {
let mut p = BTreeMap::new();
p.insert(dest, src.into());
p.insert(src, dest);
self.allowed_paths = Some(p);
}
}
@@ -358,8 +354,8 @@ impl Manifest {
}
/// Set `allowed_paths`
pub fn with_allowed_paths(mut self, paths: impl Iterator<Item = (LocalPath, PathBuf)>) -> Self {
self.allowed_paths = Some(paths.map(|(local, wasm)| (wasm, local)).collect());
pub fn with_allowed_paths(mut self, paths: impl Iterator<Item = (String, PathBuf)>) -> Self {
self.allowed_paths = Some(paths.collect());
self
}

View File

@@ -1,119 +0,0 @@
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
pub enum LocalPath {
ReadOnly(PathBuf),
ReadWrite(PathBuf),
}
impl LocalPath {
pub fn as_path(&self) -> &Path {
match self {
LocalPath::ReadOnly(p) => p.as_path(),
LocalPath::ReadWrite(p) => p.as_path(),
}
}
}
impl From<&str> for LocalPath {
fn from(value: &str) -> Self {
if let Some(s) = value.strip_prefix("ro:") {
LocalPath::ReadOnly(PathBuf::from(s))
} else {
LocalPath::ReadWrite(PathBuf::from(value))
}
}
}
impl From<String> for LocalPath {
fn from(value: String) -> Self {
LocalPath::from(value.as_str())
}
}
impl From<PathBuf> for LocalPath {
fn from(value: PathBuf) -> Self {
LocalPath::ReadWrite(value)
}
}
impl From<&Path> for LocalPath {
fn from(value: &Path) -> Self {
LocalPath::ReadWrite(value.to_path_buf())
}
}
impl serde::Serialize for LocalPath {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
LocalPath::ReadOnly(path) => {
let s = match path.to_str() {
Some(s) => s,
None => {
return Err(serde::ser::Error::custom(
"Path contains invalid UTF-8 characters",
))
}
};
format!("ro:{s}").serialize(serializer)
}
LocalPath::ReadWrite(path) => path.serialize(serializer),
}
}
}
struct LocalPathVisitor;
impl serde::de::Visitor<'_> for LocalPathVisitor {
type Value = LocalPath;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("path string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(From::from(v))
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(From::from(v))
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
std::str::from_utf8(v)
.map(From::from)
.map_err(|_| serde::de::Error::invalid_value(serde::de::Unexpected::Bytes(v), &self))
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
String::from_utf8(v).map(From::from).map_err(|e| {
serde::de::Error::invalid_value(serde::de::Unexpected::Bytes(&e.into_bytes()), &self)
})
}
}
impl<'de> serde::Deserialize<'de> for LocalPath {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_string(LocalPathVisitor)
}
}

View File

@@ -9,29 +9,46 @@ repository.workspace = true
version.workspace = true
[dependencies]
wasmtime = {version = ">= 27.0.0, < 31.0.0"}
wasi-common = {version = ">= 27.0.0, < 31.0.0"}
wiggle = {version = ">= 27.0.0, < 31.0.0"}
wasmtime = { version = ">= 27.0.0, < 31.0.0", default-features = false, features = [
'cache',
'gc',
'gc-drc',
'cranelift',
'coredump',
'wat',
'parallel-compilation',
'pooling-allocator',
'demangle',
] }
wasi-common = { version = ">= 27.0.0, < 31.0.0" }
wiggle = { version = ">= 27.0.0, < 31.0.0" }
anyhow = "1"
serde = {version = "1", features = ["derive"]}
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
toml = "0.9"
sha2 = "0.10"
tracing = "0.1"
tracing-subscriber = {version = "0.3.18", features = ["std", "env-filter", "fmt"]}
tracing-subscriber = { version = "0.3.18", features = [
"std",
"env-filter",
"fmt",
] }
url = "2"
glob = "0.3"
ureq = {version = "3.0", optional=true}
ureq = { version = "3.0", optional = true }
extism-manifest = { workspace = true }
extism-convert = { workspace = true, features = ["extism-path"] }
uuid = { version = "1", features = ["v4"] }
libc = "0.2"
[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
default = ["http", "register-http", "register-filesystem", "wasmtime-default-features"]
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
wasmtime-default-features = [
'wasmtime/default',
]
[build-dependencies]
cbindgen = { version = "0.29", default-features = false }

View File

@@ -12,7 +12,7 @@ To use the `extism` crate, you can add it to your Cargo file:
```toml
[dependencies]
extism = "1.4.1"
extism = "*"
```
## Environment variables
@@ -112,7 +112,8 @@ let mut plugin = Plugin::new(&manifest, [], true);
let res = plugin.call::<&str, &str>("count_vowels", "Yellow, world!").unwrap();
println!("{}", res);
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
let mut plugin = Plugin::new(&manifest, [], true).with_config_key("vowels", "aeiouyAEIOUY");
let manifest = Manifest::new([url]).with_config_key("vowels", "aeiouyAEIOUY");
let mut plugin = Plugin::new(&manifest, [], true).unwrap();
let res = plugin.call::<&str, &str>("count_vowels", "Yellow, world!").unwrap();
println!("{}", res);
# => {"count": 4, "total": 4, "vowels": "aeiouyAEIOUY"}

View File

@@ -16,7 +16,7 @@ fn main() {
let res = plugin.call::<&str, &str>("try_read", "").unwrap();
println!("{:?}", res);
println!("{res:?}");
println!("-----------------------------------------------------");
@@ -30,7 +30,7 @@ fn main() {
);
let res2 = plugin.call::<&str, &str>("try_write", &line).unwrap();
println!("{:?}", res2);
println!("{res2:?}");
println!("done!");
}

View File

@@ -30,6 +30,6 @@ fn main() {
let res = plugin
.call::<&str, &str>("reflect", "Hello, world!")
.unwrap();
println!("{}", res);
println!("{res}");
}
}

View File

@@ -25,6 +25,6 @@ fn main() {
println!("Dumping logs");
for line in LOGS.lock().unwrap().iter() {
print!("{}", line);
print!("{line}");
}
}

View File

@@ -52,6 +52,6 @@ fn main() {
let res = plugin
.call::<&str, &str>("count_vowels", "Hello, world!")
.unwrap();
println!("{}", res);
println!("{res}");
}
}

View File

@@ -352,9 +352,9 @@ impl CurrentPlugin {
if let Some(a) = &manifest.allowed_paths {
for (k, v) in a.iter() {
let readonly = matches!(v, extism_manifest::LocalPath::ReadOnly(_));
let readonly = k.starts_with("ro:");
let dir_path = v.as_path();
let dir_path = if readonly { &k[3..] } else { k };
let dir = wasi_common::sync::dir::Dir::from_cap_std(
wasi_common::sync::Dir::open_ambient_dir(dir_path, auth)?,
@@ -366,7 +366,7 @@ impl CurrentPlugin {
Box::new(dir)
};
ctx.push_preopened_dir(file, k)?;
ctx.push_preopened_dir(file, v)?;
}
}

View File

@@ -98,7 +98,7 @@ pub fn set_log_callback<F: 'static + Clone + Fn(&str)>(
let x = tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing::Level::ERROR.into());
if is_level {
x.parse_lossy(format!("extism={}", filter))
x.parse_lossy(format!("extism={filter}"))
} else {
x.parse_lossy(filter)
}

View File

@@ -10,7 +10,7 @@ use crate::*;
fn hex(data: &[u8]) -> String {
let mut s = String::new();
for &byte in data {
write!(&mut s, "{:02x}", byte).unwrap();
write!(&mut s, "{byte:02x}").unwrap();
}
s
}

View File

@@ -258,12 +258,16 @@ pub(crate) fn http_request(
};
let buf: &[u8] = data.memory_bytes(handle)?;
let agent = ureq::agent();
let config = agent.configure_request(r.body(buf)?);
let config = agent
.configure_request(r.body(buf)?)
.http_status_as_error(false);
let req = config.timeout_global(timeout).build();
ureq::run(req)
} else {
let agent = ureq::agent();
let config = agent.configure_request(r.body(())?);
let config = agent
.configure_request(r.body(())?)
.http_status_as_error(false);
let req = config.timeout_global(timeout).build();
ureq::run(req)
};

View File

@@ -963,8 +963,7 @@ impl Plugin {
}
Err(msg) => {
res = Err(Error::msg(format!(
"unable to load error message from memory: {}",
msg,
"unable to load error message from memory: {msg}",
)));
}
}

View File

@@ -1,4 +1,7 @@
use crate::{Error, FromBytesOwned, Plugin, ToBytes};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
// `PoolBuilder` is used to configure and create `Pool`s
#[derive(Debug, Clone)]
@@ -79,7 +82,8 @@ unsafe impl Sync for PoolInner {}
#[derive(Clone)]
pub struct Pool {
config: PoolBuilder,
inner: std::sync::Arc<std::sync::Mutex<PoolInner>>,
inner: Arc<std::sync::Mutex<PoolInner>>,
existing_functions: Arc<RwLock<HashMap<String, bool>>>,
}
unsafe impl Send for Pool {}
@@ -88,13 +92,7 @@ unsafe impl Sync for Pool {}
impl Pool {
/// Create a new pool with the default configuration
pub fn new<F: 'static + Fn() -> Result<Plugin, Error>>(source: F) -> Self {
Pool {
config: Default::default(),
inner: std::sync::Arc::new(std::sync::Mutex::new(PoolInner {
plugin_source: Box::new(source),
instances: Default::default(),
})),
}
Self::new_from_builder(Box::new(source), PoolBuilder::default())
}
/// Create a new pool configured using a `PoolBuilder`
@@ -104,10 +102,11 @@ impl Pool {
) -> Self {
Pool {
config: builder,
inner: std::sync::Arc::new(std::sync::Mutex::new(PoolInner {
inner: Arc::new(std::sync::Mutex::new(PoolInner {
plugin_source: Box::new(source),
instances: Default::default(),
})),
existing_functions: RwLock::new(HashMap::default()).into(),
}
}
@@ -171,4 +170,26 @@ impl Pool {
}
Ok(None)
}
/// Returns `true` if the given function exists, otherwise `false`. Results are cached
/// after the first call.
pub fn function_exists(&self, name: &str, timeout: std::time::Duration) -> Result<bool, Error> {
// read current value if any
let read = self.existing_functions.read().unwrap();
let exists_opt = read.get(name).cloned();
drop(read);
if let Some(exists) = exists_opt {
Ok(exists)
} else {
// load plugin and call function_exists
let plugin = self.get(timeout)?;
let exists = plugin.unwrap().0.borrow().function_exists(name);
// write result to hashmap
let mut write = self.existing_functions.write().unwrap();
write.insert(name.to_string(), exists);
Ok(exists)
}
}
}

View File

@@ -871,7 +871,7 @@ fn set_log_file(log_file: impl Into<std::path::PathBuf>, filter: &str) -> Result
let x = tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing::Level::ERROR.into());
if is_level {
x.parse_lossy(format!("extism={}", filter))
x.parse_lossy(format!("extism={filter}"))
} else {
x.parse_lossy(filter)
}
@@ -926,7 +926,7 @@ unsafe fn set_log_buffer(filter: &str) -> Result<(), Error> {
let x = tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing::Level::ERROR.into());
if is_level {
x.parse_lossy(format!("extism={}", filter))
x.parse_lossy(format!("extism={filter}"))
} else {
x.parse_lossy(filter)
}

View File

@@ -16,7 +16,7 @@ fn test_issue_620() {
// Call test method, this does not work
let p = plugin.call::<(), String>("test", ()).unwrap();
println!("{}", p);
println!("{p}");
}
// https://github.com/extism/extism/issues/619
@@ -53,5 +53,5 @@ fn test_issue_775() {
Ok(code) => Err(code),
}
.unwrap();
println!("{}", p);
println!("{p}");
}

View File

@@ -1,29 +1,33 @@
use crate::*;
use std::time::Duration;
fn run_thread(p: Pool, i: u64) -> std::thread::JoinHandle<()> {
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(i));
std::thread::sleep(Duration::from_millis(i));
let s: String = p
.get(std::time::Duration::from_secs(1))
.get(Duration::from_secs(1))
.unwrap()
.unwrap()
.call("count_vowels", "abc")
.unwrap();
println!("{}", s);
println!("{s}");
})
}
fn init(max_instances: usize) -> Pool {
let data = include_bytes!("../../../wasm/code.wasm");
let plugin_builder =
extism::PluginBuilder::new(extism::Manifest::new([extism::Wasm::data(data)]))
.with_wasi(true);
PoolBuilder::new()
.with_max_instances(max_instances)
.build(move || plugin_builder.clone().build())
}
#[test]
fn test_threads() {
for i in 1..=3 {
let data = include_bytes!("../../../wasm/code.wasm");
let plugin_builder =
extism::PluginBuilder::new(extism::Manifest::new([extism::Wasm::data(data)]))
.with_wasi(true);
let pool: Pool = PoolBuilder::new()
.with_max_instances(i)
.build(move || plugin_builder.clone().build());
let pool = init(i);
let threads = vec![
run_thread(pool.clone(), 1000),
run_thread(pool.clone(), 1000),
@@ -46,3 +50,14 @@ fn test_threads() {
assert!(pool.count() <= i);
}
}
#[test]
fn test_exists() -> Result<(), Error> {
let pool = init(1);
let timeout = Duration::from_secs(1);
assert!(pool.function_exists("count_vowels", timeout)?);
assert!(pool.function_exists("count_vowels", timeout)?);
assert!(!pool.function_exists("not_existing", timeout)?);
assert!(!pool.function_exists("not_existing", timeout)?);
Ok(())
}

View File

@@ -133,10 +133,7 @@ fn it_works() {
.unwrap();
let native_avg: std::time::Duration = native_sum / native_num_tests as u32;
println!(
"native function call (avg, N = {}): {:?}",
native_num_tests, native_avg
);
println!("native function call (avg, N = {native_num_tests}): {native_avg:?}");
let num_tests = test_times.len();
let sum: std::time::Duration = test_times
@@ -145,7 +142,7 @@ fn it_works() {
.unwrap();
let avg: std::time::Duration = sum / num_tests as u32;
println!("wasm function call (avg, N = {}): {:?}", num_tests, avg);
println!("wasm function call (avg, N = {num_tests}): {avg:?}");
// Check that log file was written to
if log {
@@ -212,7 +209,7 @@ fn test_cancel() {
let _output: Result<&[u8], Error> = plugin.call("loop_forever", "abc123");
let end = std::time::Instant::now();
let time = end - start;
println!("Cancelled plugin ran for {:?}", time);
println!("Cancelled plugin ran for {time:?}");
}
}
@@ -271,7 +268,7 @@ fn test_fuel_consumption() {
assert!(output.is_err());
let fuel_consumed = plugin.fuel_consumed().unwrap();
println!("Fuel consumed: {}", fuel_consumed);
println!("Fuel consumed: {fuel_consumed}");
assert!(fuel_consumed > 0);
}
@@ -440,7 +437,7 @@ fn test_memory_max() {
assert!(output.is_err());
let err = output.unwrap_err().root_cause().to_string();
println!("{:?}", err);
println!("{err:?}");
assert_eq!(err, "oom");
// Should pass with memory.max set to a large enough number
@@ -503,7 +500,7 @@ fn test_extism_error() {
let mut plugin = Plugin::new(&manifest, [f], true).unwrap();
let output: Result<String, Error> = plugin.call("count_vowels", "a".repeat(1024));
assert!(output.is_err());
println!("{:?}", output);
println!("{output:?}");
assert_eq!(output.unwrap_err().root_cause().to_string(), "TEST");
}
@@ -823,7 +820,7 @@ fn test_http_response_headers() {
.unwrap();
let req = HttpRequest::new("https://extism.org");
let Json(res): Json<HashMap<String, String>> = plugin.call("http_get", Json(req)).unwrap();
println!("{:?}", res);
println!("{res:?}");
assert_eq!(res["content-type"], "text/html; charset=utf-8");
}
@@ -838,6 +835,6 @@ fn test_http_response_headers_disabled() {
.unwrap();
let req = HttpRequest::new("https://extism.org");
let Json(res): Json<HashMap<String, String>> = plugin.call("http_get", Json(req)).unwrap();
println!("{:?}", res);
println!("{res:?}");
assert!(res.is_empty());
}