Skip to content

Commit 469de76

Browse files
committed
support shopify function wasm api
1 parent 62aedb3 commit 469de76

File tree

6 files changed

+166
-25
lines changed

6 files changed

+166
-25
lines changed

build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ fn main() {
44
println!("cargo:rerun-if-changed=providers/javy_quickjs_provider_v3.wasm");
55
println!("cargo:rerun-if-changed=providers/shopify_functions_javy_v1.wasm");
66
println!("cargo:rerun-if-changed=providers/shopify_functions_javy_v2.wasm");
7+
println!("cargo:rerun-if-changed=providers/shopify_function_v0.0.1.wasm");
78
}
116 KB
Binary file not shown.

src/engine.rs

Lines changed: 134 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ pub struct ProfileOpts {
2020
#[folder = "providers/"]
2121
struct StandardProviders;
2222

23+
pub fn uses_msgpack_provider(module: &Module) -> bool {
24+
module.imports().map(|i| i.module()).any(|module| {
25+
module.starts_with("shopify_function_v") || module == "shopify_functions_javy_v2"
26+
})
27+
}
28+
2329
fn import_modules<T>(
2430
module: &Module,
2531
engine: &Engine,
@@ -28,8 +34,10 @@ fn import_modules<T>(
2834
) {
2935
let imported_modules: HashSet<String> =
3036
module.imports().map(|i| i.module().to_string()).collect();
37+
3138
imported_modules.iter().for_each(|module_name| {
32-
let imported_module_bytes = StandardProviders::get(&format!("{module_name}.wasm"));
39+
let provider_path = format!("{module_name}.wasm");
40+
let imported_module_bytes = StandardProviders::get(&provider_path);
3341

3442
if let Some(bytes) = imported_module_bytes {
3543
let imported_module = Module::from_binary(engine, &bytes.data)
@@ -38,20 +46,22 @@ fn import_modules<T>(
3846
let imported_module_instance = linker
3947
.instantiate(&mut store, &imported_module)
4048
.expect("Failed to instantiate imported instance");
49+
4150
linker
4251
.instance(&mut store, module_name, imported_module_instance)
4352
.expect("Failed to import module");
4453
}
4554
});
4655
}
4756

48-
#[derive(Default)]
4957
pub struct FunctionRunParams<'a> {
5058
pub function_path: PathBuf,
5159
pub input: BytesContainer,
5260
pub export: &'a str,
5361
pub profile_opts: Option<&'a ProfileOpts>,
5462
pub scale_factor: f64,
63+
pub module: Module,
64+
pub engine: Engine,
5565
}
5666

5767
const STARTING_FUEL: u64 = u64::MAX;
@@ -114,18 +124,10 @@ pub fn run(params: FunctionRunParams) -> Result<FunctionRunResult> {
114124
export,
115125
profile_opts,
116126
scale_factor,
127+
engine,
128+
module,
117129
} = params;
118130

119-
let engine = Engine::new(
120-
Config::new()
121-
.wasm_multi_memory(true)
122-
.wasm_threads(false)
123-
.consume_fuel(true)
124-
.epoch_interruption(true),
125-
)?;
126-
let module = Module::from_file(&engine, &function_path)
127-
.map_err(|e| anyhow!("Couldn't load the Function {:?}: {}", &function_path, e))?;
128-
129131
let input_stream = MemoryInputPipe::new(input.raw.clone());
130132
let output_stream = MemoryOutputPipe::new(usize::MAX);
131133
let error_stream = MemoryOutputPipe::new(usize::MAX);
@@ -201,11 +203,15 @@ pub fn run(params: FunctionRunParams) -> Result<FunctionRunResult> {
201203

202204
logs.extend_from_slice(error_logs.as_bytes());
203205

206+
let output_codec = input.codec;
204207
let raw_output = output_stream
205208
.try_into_inner()
206209
.expect("Output stream reference still exists");
207-
208-
let output = BytesContainer::new(BytesContainerType::Output, input.codec, raw_output.to_vec())?;
210+
let output = BytesContainer::new(
211+
BytesContainerType::Output,
212+
output_codec,
213+
raw_output.to_vec(),
214+
)?;
209215

210216
let name = function_path.file_name().unwrap().to_str().unwrap();
211217
let size = function_path.metadata()?.len() / 1024;
@@ -226,6 +232,21 @@ pub fn run(params: FunctionRunParams) -> Result<FunctionRunResult> {
226232
Ok(function_run_result)
227233
}
228234

235+
/// Creates a new Engine with our standard configuration.
236+
/// We use a dedicated function instead of making this the default configuration because:
237+
/// 1. It's more explicit about what configuration we're using
238+
/// 2. It keeps the door open for different configurations in the future without breaking changes
239+
/// 3. It makes it easier to find all places where we create an Engine
240+
pub fn new_engine() -> Result<Engine> {
241+
Engine::new(
242+
Config::new()
243+
.wasm_multi_memory(true)
244+
.wasm_threads(false)
245+
.consume_fuel(true)
246+
.epoch_interruption(true),
247+
)
248+
}
249+
229250
#[cfg(test)]
230251
mod tests {
231252
use colored::Colorize;
@@ -244,6 +265,9 @@ mod tests {
244265

245266
#[test]
246267
fn test_js_function() -> Result<()> {
268+
let engine = new_engine()?;
269+
let module =
270+
Module::from_file(&engine, Path::new("tests/fixtures/build/js_function.wasm"))?;
247271
let input = json_input(include_bytes!(
248272
"../tests/fixtures/input/js_function_input.json"
249273
))?;
@@ -252,7 +276,10 @@ mod tests {
252276
function_path: Path::new("tests/fixtures/build/js_function.wasm").to_path_buf(),
253277
input,
254278
export: DEFAULT_EXPORT,
255-
..Default::default()
279+
module,
280+
engine,
281+
scale_factor: 1.0,
282+
profile_opts: None,
256283
})?;
257284

258285
assert_eq!(function_run_result.memory_usage, 1280);
@@ -262,14 +289,22 @@ mod tests {
262289

263290
#[test]
264291
fn test_js_v2_function() -> Result<()> {
292+
let engine = new_engine()?;
293+
let module = Module::from_file(
294+
&engine,
295+
Path::new("tests/fixtures/build/js_function_v2.wasm"),
296+
)?;
265297
let input = json_input(include_bytes!(
266298
"../tests/fixtures/input/js_function_input.json"
267299
))?;
268300
let function_run_result = run(FunctionRunParams {
269301
function_path: Path::new("tests/fixtures/build/js_function_v2.wasm").to_path_buf(),
270302
input,
271303
export: DEFAULT_EXPORT,
272-
..Default::default()
304+
module,
305+
engine,
306+
scale_factor: 1.0,
307+
profile_opts: None,
273308
})?;
274309

275310
assert_eq!(function_run_result.memory_usage, 1344);
@@ -278,6 +313,11 @@ mod tests {
278313

279314
#[test]
280315
fn test_js_v3_function() -> Result<()> {
316+
let engine = new_engine()?;
317+
let module = Module::from_file(
318+
&engine,
319+
Path::new("tests/fixtures/build/js_function_v3.wasm"),
320+
)?;
281321
let input = json_input(include_bytes!(
282322
"../tests/fixtures/input/js_function_input.json"
283323
))?;
@@ -286,7 +326,10 @@ mod tests {
286326
function_path: Path::new("tests/fixtures/build/js_function_v3.wasm").to_path_buf(),
287327
input,
288328
export: DEFAULT_EXPORT,
289-
..Default::default()
329+
module,
330+
engine,
331+
scale_factor: 1.0,
332+
profile_opts: None,
290333
})?;
291334

292335
assert_eq!(function_run_result.memory_usage, 1344);
@@ -295,6 +338,11 @@ mod tests {
295338

296339
#[test]
297340
fn test_js_functions_javy_v1() -> Result<()> {
341+
let engine = new_engine()?;
342+
let module = Module::from_file(
343+
&engine,
344+
Path::new("tests/fixtures/build/js_functions_javy_v1.wasm"),
345+
)?;
298346
let input = json_input(include_bytes!(
299347
"../tests/fixtures/input/js_function_input.json"
300348
))?;
@@ -304,7 +352,10 @@ mod tests {
304352
.to_path_buf(),
305353
input,
306354
export: DEFAULT_EXPORT,
307-
..Default::default()
355+
module,
356+
engine,
357+
scale_factor: 1.0,
358+
profile_opts: None,
308359
})?;
309360

310361
assert_eq!(function_run_result.memory_usage, 1344);
@@ -313,11 +364,16 @@ mod tests {
313364

314365
#[test]
315366
fn test_exit_code_zero() -> Result<()> {
367+
let engine = new_engine()?;
368+
let module = Module::from_file(&engine, Path::new("tests/fixtures/build/exit_code.wasm"))?;
316369
let function_run_result = run(FunctionRunParams {
317370
function_path: Path::new("tests/fixtures/build/exit_code.wasm").to_path_buf(),
318371
input: json_input(&serde_json::to_vec(&json!({ "code": 0 }))?)?,
319372
export: DEFAULT_EXPORT,
320-
..Default::default()
373+
module,
374+
engine,
375+
scale_factor: 1.0,
376+
profile_opts: None,
321377
})?;
322378

323379
assert_eq!(function_run_result.logs, "");
@@ -326,11 +382,16 @@ mod tests {
326382

327383
#[test]
328384
fn test_exit_code_one() -> Result<()> {
385+
let engine = new_engine()?;
386+
let module = Module::from_file(&engine, Path::new("tests/fixtures/build/exit_code.wasm"))?;
329387
let function_run_result = run(FunctionRunParams {
330388
function_path: Path::new("tests/fixtures/build/exit_code.wasm").to_path_buf(),
331389
input: json_input(&serde_json::to_vec(&json!({ "code": 1 }))?)?,
332390
export: DEFAULT_EXPORT,
333-
..Default::default()
391+
module,
392+
engine,
393+
scale_factor: 1.0,
394+
profile_opts: None,
334395
})?;
335396

336397
assert_eq!(function_run_result.logs, "module exited with code: 1");
@@ -339,11 +400,19 @@ mod tests {
339400

340401
#[test]
341402
fn test_linear_memory_usage_in_kb() -> Result<()> {
403+
let engine = new_engine()?;
404+
let module = Module::from_file(
405+
&engine,
406+
Path::new("tests/fixtures/build/linear_memory.wasm"),
407+
)?;
342408
let function_run_result = run(FunctionRunParams {
343409
function_path: Path::new("tests/fixtures/build/linear_memory.wasm").to_path_buf(),
344410
input: json_input(&serde_json::to_vec(&json!({}))?)?,
345411
export: DEFAULT_EXPORT,
346-
..Default::default()
412+
module,
413+
engine,
414+
scale_factor: 1.0,
415+
profile_opts: None,
347416
})?;
348417

349418
assert_eq!(function_run_result.memory_usage, 12800); // 200 * 64KiB pages
@@ -352,12 +421,20 @@ mod tests {
352421

353422
#[test]
354423
fn test_logs_truncation() -> Result<()> {
424+
let engine = new_engine()?;
425+
let module = Module::from_file(
426+
&engine,
427+
Path::new("tests/fixtures/build/log_truncation_function.wasm"),
428+
)?;
355429
let function_run_result = run(FunctionRunParams {
356430
input: json_input("{}".as_bytes())?,
357431
function_path: Path::new("tests/fixtures/build/log_truncation_function.wasm")
358432
.to_path_buf(),
359433
export: DEFAULT_EXPORT,
360-
..Default::default()
434+
module,
435+
engine,
436+
scale_factor: 1.0,
437+
profile_opts: None,
361438
})?;
362439

363440
assert!(
@@ -374,12 +451,16 @@ mod tests {
374451
#[test]
375452
fn test_file_size_in_kb() -> Result<()> {
376453
let file_path = Path::new("tests/fixtures/build/exit_code.wasm");
377-
454+
let engine = new_engine()?;
455+
let module = Module::from_file(&engine, file_path)?;
378456
let function_run_result = run(FunctionRunParams {
379457
function_path: file_path.to_path_buf(),
380458
input: json_input(&serde_json::to_vec(&json!({ "code": 0 }))?)?,
381459
export: DEFAULT_EXPORT,
382-
..Default::default()
460+
module,
461+
engine,
462+
scale_factor: 1.0,
463+
profile_opts: None,
383464
})?;
384465

385466
assert_eq!(
@@ -388,4 +469,33 @@ mod tests {
388469
);
389470
Ok(())
390471
}
472+
473+
#[test]
474+
fn test_wasm_api_function() -> Result<()> {
475+
let engine = new_engine()?;
476+
let module = Module::from_file(
477+
&engine,
478+
Path::new("tests/fixtures/build/echo.trampolined.wasm"),
479+
)?;
480+
let expected_input_value = json!({"foo": "echo", "bar": "test"});
481+
let input = serde_json::to_vec(&expected_input_value).unwrap();
482+
let input_bytes = BytesContainer::new(BytesContainerType::Input, Codec::Json, input);
483+
let function_run_result = run(FunctionRunParams {
484+
function_path: Path::new("tests/fixtures/build/echo.trampolined.wasm").to_path_buf(),
485+
input: input_bytes.unwrap(),
486+
export: DEFAULT_EXPORT,
487+
module,
488+
engine,
489+
scale_factor: 1.0,
490+
profile_opts: None,
491+
});
492+
493+
assert!(function_run_result.is_ok());
494+
let result = function_run_result.unwrap();
495+
assert_eq!(
496+
serde_json::from_slice::<serde_json::Value>(&result.input.raw).unwrap(),
497+
expected_input_value
498+
);
499+
Ok(())
500+
}
391501
}

src/main.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use function_runner::{BytesContainer, BytesContainerType, Codec};
2+
use wasmtime::Module;
23

34
use std::{
45
fs::File,
@@ -135,7 +136,18 @@ fn main() -> Result<()> {
135136

136137
let query_string = opts.read_query_to_string().transpose()?;
137138

138-
let input = BytesContainer::new(BytesContainerType::Input, opts.codec, buffer)?;
139+
let engine = function_runner::engine::new_engine()?;
140+
let module = Module::from_file(&engine, &opts.function)
141+
.map_err(|e| anyhow!("Couldn't load the Function {:?}: {}", &opts.function, e))?;
142+
143+
// Infer codec from the module if it uses msgpack, otherwise use the codec from the command line
144+
let inferred_codec = if function_runner::engine::uses_msgpack_provider(&module) {
145+
Codec::Messagepack
146+
} else {
147+
opts.codec
148+
};
149+
150+
let input = BytesContainer::new(BytesContainerType::Input, inferred_codec, buffer)?;
139151
let scale_factor = if let (Some(schema_string), Some(query_string), Some(json_value)) =
140152
(schema_string, query_string, input.json_value.clone())
141153
{
@@ -158,6 +170,8 @@ fn main() -> Result<()> {
158170
export: opts.export.as_ref(),
159171
profile_opts: profile_opts.as_ref(),
160172
scale_factor,
173+
module,
174+
engine,
161175
})?;
162176

163177
if opts.json {
86.1 KB
Binary file not shown.

tests/integration_tests.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,4 +455,20 @@ mod tests {
455455
cmd.assert().stdout(contains("discountApplicationStrategy"));
456456
Ok(())
457457
}
458+
459+
#[test]
460+
fn run_wasm_api_function() -> Result<(), Box<dyn std::error::Error>> {
461+
let mut cmd = Command::cargo_bin("function-runner")?;
462+
let input_file = temp_input(json!({
463+
"test": "echo"
464+
}))?;
465+
let file = File::open(input_file.path())?;
466+
467+
cmd.args(["--function", "tests/fixtures/build/echo.trampolined.wasm"])
468+
.stdin(Stdio::from(file));
469+
470+
cmd.assert().success();
471+
472+
Ok(())
473+
}
458474
}

0 commit comments

Comments
 (0)