Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions example_with_targets/cart.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
query Input {
cartLines {
id
quantity
title
}
}
27 changes: 27 additions & 0 deletions example_with_targets/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ A void type that can be used to return a null value from a mutation.
"""
scalar Void

"""
A cart line representing an item in the cart.
"""
type CartLine {
id: ID!
quantity: Int!
title: String!
}

"""
The input object for the function.
"""
Expand All @@ -77,6 +86,7 @@ type Input {
optionalArray: [String!]
optionalArrayOfArrays: [[String!]!]
optionalArrayOfOptionalArrays: [[String!]]
cartLines: [CartLine!] @restrictTarget(only: ["test.target-cart"])
}

"""
Expand All @@ -102,6 +112,16 @@ type MutationRoot {
"""
result: FunctionTargetBResult!
): Void!

"""
The function for API target Cart.
"""
targetCart(
"""
The result of calling the function for API target Cart.
"""
result: FunctionTargetCartResult!
): Void!
}

"""
Expand All @@ -119,6 +139,13 @@ input FunctionTargetBResult {
operations: [Operation!]!
}

"""
The result of API target Cart.
"""
input FunctionTargetCartResult {
totalQuantity: Int!
}

input Operation @oneOf {
doThis: This
doThat: That
Expand Down
17 changes: 17 additions & 0 deletions example_with_targets/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ mod schema {

#[query("./b.graphql")]
pub mod target_b {}

#[query("./cart.graphql")]
pub mod target_cart {}
}

#[shopify_function]
Expand Down Expand Up @@ -45,6 +48,20 @@ fn target_panic(_input: schema::target_a::Input) -> Result<schema::FunctionTarge
panic!("Something went wrong");
}

#[shopify_function]
fn target_cart(input: schema::target_cart::Input) -> Result<schema::FunctionTargetCartResult> {
// Iterate over cart lines and sum quantities - this accesses the `quantity` property
// multiple times, which should benefit from interned string caching
let total_quantity: i32 = input
.cart_lines()
.unwrap_or(&[])
.iter()
.map(|line| *line.quantity())
.sum();

Ok(schema::FunctionTargetCartResult { total_quantity })
}

fn main() {
log!("Invoke a named export");
process::abort()
Expand Down
33 changes: 31 additions & 2 deletions integration_tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ use std::{
sync::LazyLock,
};

/// Result from running a Shopify Function
#[derive(Debug)]
pub struct RunResult {
/// The JSON output from the function
pub output: serde_json::Value,
/// The logs from the function
pub logs: String,
/// The number of instructions executed
pub instructions: u64,
/// The memory usage in bytes
pub memory_usage: u64,
}

const FUNCTION_RUNNER_VERSION: &str = "9.1.0";
const TRAMPOLINE_VERSION: &str = "2.0.0";

Expand Down Expand Up @@ -152,7 +165,7 @@ pub fn run_example(
path: PathBuf,
export: &str,
input: serde_json::Value,
) -> Result<(serde_json::Value, String)> {
) -> Result<RunResult> {
let function_runner_path = FUNCTION_RUNNER_PATH
.as_ref()
.map_err(|e| anyhow::anyhow!("Failed to download function runner: {}", e))?;
Expand Down Expand Up @@ -198,6 +211,16 @@ pub fn run_example(
.ok_or_else(|| anyhow::anyhow!("Logs are not a string"))?
.to_string();

let instructions = output
.get("instructions")
.and_then(|v| v.as_u64())
.ok_or_else(|| anyhow::anyhow!("No instructions count"))?;

let memory_usage = output
.get("memory_usage")
.and_then(|v| v.as_u64())
.ok_or_else(|| anyhow::anyhow!("No memory_usage"))?;

if !status.success() {
anyhow::bail!(
"Function runner returned non-zero exit code: {}, logs: {}",
Expand All @@ -210,5 +233,11 @@ pub fn run_example(
.get_mut("output")
.ok_or_else(|| anyhow::anyhow!("No output"))?
.take();
Ok((output_json, logs))

Ok(RunResult {
output: output_json,
logs,
instructions,
memory_usage,
})
}
55 changes: 48 additions & 7 deletions integration_tests/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ fn test_example_with_targets_target_a() -> Result<()> {
"name": "test",
"country": "CA"
});
let (output, logs) = run_example(path.clone(), "target_a", input)?;
let result = run_example(path.clone(), "target_a", input)?;
assert_eq!(
output,
result.output,
serde_json::json!({
"status": 200
})
);
assert_eq!(logs, "In target_a\nWith var: 42\nWith var: 42\n");
assert_eq!(result.logs, "In target_a\nWith var: 42\nWith var: 42\n");
eprintln!(
"target_a: instructions={}, memory_usage={}",
result.instructions, result.memory_usage
);
Ok(())
}

Expand All @@ -36,9 +40,9 @@ fn test_example_with_targets_target_b() -> Result<()> {
"id": "gid://shopify/Order/1234567890",
"targetAResult": 200
});
let (output, logs) = run_example(path.clone(), "target_b", input)?;
let result = run_example(path.clone(), "target_b", input)?;
assert_eq!(
output,
result.output,
serde_json::json!({
"name": "new name: \"gid://shopify/Order/1234567890\"",
"operations": [
Expand All @@ -55,7 +59,11 @@ fn test_example_with_targets_target_b() -> Result<()> {
]
})
);
assert_eq!(logs, "In target_b\n");
assert_eq!(result.logs, "In target_b\n");
eprintln!(
"target_b: instructions={}, memory_usage={}",
result.instructions, result.memory_usage
);
Ok(())
}

Expand All @@ -72,10 +80,43 @@ fn test_example_with_panic() -> Result<()> {
.unwrap_err()
.to_string();
let expected_err =
"Function runner returned non-zero exit code: exit status: 1, logs: panicked at example_with_targets/src/main.rs:45:5:\nSomething went wrong\nerror while executing at wasm backtrace:";
"Function runner returned non-zero exit code: exit status: 1, logs: panicked at example_with_targets/src/main.rs:48:5:\nSomething went wrong\nerror while executing at wasm backtrace:";
assert!(
err.contains(expected_err),
"Expected error message to contain:\n`{expected_err}`\nbut was:\n`{err}`"
);
Ok(())
}

#[test]
fn test_example_with_targets_target_cart() -> Result<()> {
let path = EXAMPLE_WITH_TARGETS_RESULT
.as_ref()
.map_err(|e| anyhow::anyhow!("Failed to prepare example: {}", e))?;
// Create input with 100 cart lines to demonstrate interned string benefits
let cart_lines: Vec<_> = (0..100)
.map(|i| {
serde_json::json!({
"id": format!("gid://shopify/CartLine/{i}"),
"quantity": i + 1,
"title": format!("Product {i}")
})
})
.collect();
let input = serde_json::json!({
"cartLines": cart_lines
});
let result = run_example(path.clone(), "target_cart", input)?;
// Sum of 1 + 2 + ... + 100 = 5050
assert_eq!(
result.output,
serde_json::json!({
"totalQuantity": 5050
})
);
eprintln!(
"target_cart (100 lines): instructions={}, memory_usage={}",
result.instructions, result.memory_usage
);
Ok(())
}
5 changes: 1 addition & 4 deletions shopify_function_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,8 @@ impl CodeGenerator for ShopifyFunctionCodeGenerator {
parse_quote! {
#description
pub fn #field_name_ident(&self) -> #field_type {
static INTERNED_FIELD_NAME: shopify_function::wasm_api::CachedInternedStringId = shopify_function::wasm_api::CachedInternedStringId::new(#field_name_lit_str, );
let interned_string_id = INTERNED_FIELD_NAME.load();

let value = self.#field_name_ident.get_or_init(|| {
let value = self.__wasm_value.get_interned_obj_prop(interned_string_id);
let value = self.__wasm_value.get_obj_prop(#field_name_lit_str);
shopify_function::wasm_api::Deserialize::deserialize(&value).unwrap()
});
let value_ref = &value;
Expand Down
Loading