Skip to content

Commit a7ab04c

Browse files
author
Ryan Workman
authored
Merge pull request #157 from Shopify/add-cart-transform-example
Add cart-transform template
2 parents b8ee35a + 0672a1f commit a7ab04c

File tree

8 files changed

+355
-1
lines changed

8 files changed

+355
-1
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"rust-analyzer.linkedProjects": [
33
"checkout/rust/delivery-customization/default/Cargo.toml",
44
"checkout/rust/payment-customization/default/Cargo.toml",
5+
"checkout/rust/cart-transform/bundles/Cargo.toml",
56
"discounts/rust/order-discounts/default/Cargo.toml",
67
"discounts/rust/order-discounts/fixed-amount/Cargo.toml",
78
"discounts/rust/product-discounts/default/Cargo.toml",
@@ -10,4 +11,4 @@
1011
"discounts/rust/shipping-discounts/fixed-amount/Cargo.toml",
1112
"sample-apps/discounts-tutorial/extensions/volume/Cargo.toml"
1213
]
13-
}
14+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/target
2+
Cargo.lock
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "bundle_cart_transform"
3+
version = "1.0.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
serde_json = "1.0"
8+
serde = { version = "1.0.13", features = ["derive"] }
9+
10+
[profile.release]
11+
lto = true
12+
opt-level = 's'
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
query Input {
2+
cart {
3+
lines {
4+
id
5+
quantity
6+
merchandise {
7+
... on ProductVariant {
8+
id
9+
component_parents: metafield(
10+
namespace: "custom"
11+
key: "component_parents"
12+
) {
13+
value
14+
}
15+
component_reference: metafield(
16+
namespace: "custom"
17+
key: "component_reference"
18+
) {
19+
value
20+
}
21+
component_quantities: metafield(
22+
namespace: "custom"
23+
key: "component_quantities"
24+
) {
25+
value
26+
}
27+
}
28+
}
29+
}
30+
}
31+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "schemaVersions": { "cart_transform": { "major": 1, "minor": 0 } } }
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name = "bundle cart transform"
2+
type = "cart_transform"
3+
title = "bundle cart transform"
4+
api_version = "unstable"
5+
6+
[build]
7+
command = "cargo wasi build --release"
8+
path = "target/wasm32-wasi/release/bundle_cart_transform.wasm"
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#![allow(non_camel_case_types, non_snake_case)]
2+
use serde::{Deserialize, Serialize};
3+
4+
// Common types
5+
6+
pub type ID = String;
7+
8+
// Input types
9+
10+
#[derive(Clone, Debug, Deserialize)]
11+
#[serde(rename_all = "camelCase")]
12+
pub struct Input {
13+
pub cart: Cart,
14+
}
15+
16+
#[derive(Clone, Debug, Deserialize)]
17+
pub struct Cart {
18+
pub lines: Vec<CartLine>,
19+
}
20+
21+
#[derive(Clone, Debug, Deserialize)]
22+
pub struct CartLine {
23+
pub id: ID,
24+
pub quantity: i32,
25+
pub merchandise: Option<ProductVariant>,
26+
}
27+
28+
#[derive(Clone, Debug, Deserialize)]
29+
pub struct ProductVariant {
30+
pub id: ID,
31+
pub component_parents: Option<Metafield>,
32+
pub component_reference: Option<Metafield>,
33+
pub component_quantities: Option<Metafield>,
34+
}
35+
36+
#[derive(Clone, Debug, Deserialize)]
37+
pub struct Metafield {
38+
pub value: String,
39+
}
40+
41+
#[derive(Clone, Debug, Deserialize)]
42+
pub struct ComponentParentMetafield {
43+
pub id: ID,
44+
pub component_reference: ComponentParentMetafieldReference,
45+
pub component_quantities: ComponentParentMetafieldQuantities,
46+
}
47+
48+
#[derive(Clone, Debug, Deserialize)]
49+
pub struct ComponentParentMetafieldReference {
50+
pub value: Vec<String>,
51+
}
52+
53+
#[derive(Clone, Debug, Deserialize)]
54+
pub struct ComponentParentMetafieldQuantities {
55+
pub value: Vec<i32>,
56+
}
57+
58+
#[derive(Clone, Debug, Deserialize, PartialEq)]
59+
pub struct ComponentParent {
60+
pub id: ID,
61+
pub component_reference: Vec<ID>,
62+
pub component_quantities: Vec<i32>,
63+
}
64+
// Output types
65+
66+
#[derive(Clone, Debug, Serialize)]
67+
pub struct FunctionResult {
68+
pub operations: Vec<CartOperation>,
69+
}
70+
71+
#[derive(Clone, Debug, Serialize)]
72+
pub enum CartOperation {
73+
merge(MergeOperation),
74+
expand(ExpandOperation),
75+
}
76+
77+
#[derive(Clone, Debug, Serialize)]
78+
pub struct ExpandOperation {
79+
pub cartLineId: ID,
80+
pub expandedCartItems: Vec<ExpandRelationship>,
81+
pub price: Option<PriceAdjustment>,
82+
}
83+
84+
#[derive(Clone, Debug, Serialize)]
85+
pub struct ExpandRelationship {
86+
pub merchandiseId: ID,
87+
pub quantity: i32,
88+
}
89+
90+
#[derive(Clone, Debug, Serialize)]
91+
pub struct MergeOperation {
92+
pub parentVariantId: ID,
93+
pub title: Option<String>,
94+
pub cartLines: Vec<CartLineMergeOperation>,
95+
pub price: Option<PriceAdjustment>,
96+
pub quantity: i32,
97+
pub image: Option<ImageInput>,
98+
}
99+
100+
#[derive(Clone, Debug, Serialize)]
101+
pub struct CartLineMergeOperation {
102+
pub cartLineId: ID,
103+
pub quantity: i32,
104+
}
105+
106+
#[derive(Clone, Debug, Serialize)]
107+
pub struct PriceAdjustment {
108+
pub percentageDecrease: Option<PriceAdjustmentValue>,
109+
}
110+
111+
#[derive(Clone, Debug, Serialize)]
112+
pub struct PriceAdjustmentValue {
113+
pub value: f64,
114+
}
115+
116+
#[derive(Clone, Debug, Serialize)]
117+
pub struct ImageInput {
118+
pub url: String,
119+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use serde::Serialize;
2+
3+
mod api;
4+
use api::*;
5+
6+
#[allow(unused_must_use)]
7+
fn main() -> Result<(), Box<dyn std::error::Error>> {
8+
let input: Input = serde_json::from_reader(std::io::BufReader::new(std::io::stdin()))?;
9+
10+
let mut out = std::io::stdout();
11+
let mut serializer = serde_json::Serializer::new(&mut out);
12+
13+
cart_transform(&input.cart).serialize(&mut serializer);
14+
15+
Ok(())
16+
}
17+
18+
fn cart_transform(cart: &Cart) -> FunctionResult {
19+
let mut merge_cart_operations: Vec<CartOperation> = get_merge_cart_operations(cart);
20+
let expand_cart_operations: Vec<CartOperation> = get_expand_cart_operations(cart);
21+
22+
merge_cart_operations.extend(expand_cart_operations);
23+
24+
return FunctionResult {
25+
operations: merge_cart_operations,
26+
};
27+
}
28+
29+
// merge operation logic
30+
31+
fn get_merge_cart_operations(cart: &Cart) -> Vec<CartOperation> {
32+
let merge_parent_defintions: Vec<ComponentParent> = get_merge_parent_definitions(cart);
33+
let mut result: Vec<MergeOperation> = Vec::new();
34+
35+
for definition in merge_parent_defintions.iter() {
36+
let (components_in_cart, parent_variant_quantity) =
37+
get_components_in_cart(cart, definition);
38+
if components_in_cart.len() == definition.component_reference.len() {
39+
let cart_lines: Vec<CartLineMergeOperation> = components_in_cart
40+
.iter()
41+
.map(|component| CartLineMergeOperation {
42+
cartLineId: component.cartLineId.clone(),
43+
quantity: parent_variant_quantity * component.quantity,
44+
})
45+
.collect();
46+
47+
let merge_operation: MergeOperation = MergeOperation {
48+
parentVariantId: definition.id.clone(),
49+
title: None,
50+
cartLines: cart_lines.clone(),
51+
quantity: parent_variant_quantity.clone(),
52+
image: None,
53+
price: None,
54+
};
55+
56+
result.push(merge_operation);
57+
}
58+
}
59+
60+
return result.iter().map(|op| CartOperation::merge(op.clone())).collect();
61+
}
62+
63+
fn get_components_in_cart(
64+
cart: &Cart,
65+
definition: &ComponentParent,
66+
) -> (Vec<CartLineMergeOperation>, i32) {
67+
let mut line_results: Vec<CartLineMergeOperation> = Vec::new();
68+
let mut maximum_available_component: Vec<i32> = Vec::new();
69+
for (reference, quantity) in definition
70+
.component_reference
71+
.iter()
72+
.zip(definition.component_quantities.iter())
73+
{
74+
for line in cart.lines.iter() {
75+
if let Some(merchandise) = &line.merchandise {
76+
if reference == &merchandise.id && &line.quantity >= quantity {
77+
line_results.push(CartLineMergeOperation {
78+
cartLineId: line.id.clone(),
79+
quantity: quantity.clone(),
80+
});
81+
let maximum_available = if quantity > &0 {
82+
line.quantity / quantity
83+
} else {
84+
0
85+
};
86+
maximum_available_component.push(maximum_available)
87+
}
88+
}
89+
}
90+
}
91+
let parent_variant_quantity: i32 = match maximum_available_component.iter().min() {
92+
Some(available) => available.clone(),
93+
None => 0,
94+
};
95+
96+
return (line_results, parent_variant_quantity);
97+
}
98+
99+
fn get_merge_parent_definitions(cart: &Cart) -> Vec<ComponentParent> {
100+
let mut merge_parent_defintions: Vec<ComponentParent> = Vec::new();
101+
102+
for line in cart.lines.iter() {
103+
if let Some(merchandise) = &line.merchandise {
104+
merge_parent_defintions.append(&mut get_component_parents(&merchandise));
105+
}
106+
}
107+
merge_parent_defintions.dedup_by(|a, b| a.id == b.id);
108+
return merge_parent_defintions;
109+
}
110+
111+
fn get_component_parents(variant: &ProductVariant) -> Vec<ComponentParent> {
112+
let mut component_parents: Vec<ComponentParent> = Vec::new();
113+
if let Some(component_parents_metafield) = &variant.component_parents {
114+
let value: Vec<ComponentParentMetafield> =
115+
serde_json::from_str(&component_parents_metafield.value).unwrap();
116+
for parent_definition in value.iter() {
117+
component_parents.push(ComponentParent {
118+
id: parent_definition.id.clone(),
119+
component_reference: parent_definition.component_reference.value.clone(),
120+
component_quantities: parent_definition.component_quantities.value.clone(),
121+
});
122+
}
123+
}
124+
125+
return component_parents;
126+
}
127+
128+
// expand operation logic
129+
130+
fn get_expand_cart_operations(cart: &Cart) -> Vec<CartOperation> {
131+
let mut result: Vec<ExpandOperation> = Vec::new();
132+
133+
for line in cart.lines.iter() {
134+
if let Some(merchandise) = &line.merchandise {
135+
let component_references: Vec<ID> = get_component_references(&merchandise);
136+
let component_quantities: Vec<i32> = get_component_quantities(&merchandise);
137+
138+
if component_references.is_empty() || component_references.len() != component_quantities.len() {
139+
continue;
140+
}
141+
142+
let mut expand_relationships: Vec<ExpandRelationship> = Vec::new();
143+
144+
for (reference, quantity) in component_references.iter().zip(component_quantities.iter()) {
145+
let expand_relationship: ExpandRelationship = ExpandRelationship {
146+
merchandiseId: reference.clone(),
147+
quantity: quantity.clone(),
148+
};
149+
150+
expand_relationships.push(expand_relationship);
151+
}
152+
153+
let expand_operation: ExpandOperation = ExpandOperation{
154+
cartLineId: line.id.clone(),
155+
expandedCartItems: expand_relationships,
156+
price: None,
157+
};
158+
159+
result.push(expand_operation);
160+
}
161+
}
162+
163+
return result.iter().map(|op| CartOperation::expand(op.clone())).collect();
164+
}
165+
166+
fn get_component_quantities(variant: &ProductVariant) -> Vec<i32> {
167+
if let Some(component_quantities_metafield) = &variant.component_quantities {
168+
return serde_json::from_str(&component_quantities_metafield.value).unwrap();
169+
}
170+
171+
return Vec::new();
172+
}
173+
174+
fn get_component_references(variant: &ProductVariant) -> Vec<ID> {
175+
if let Some(component_reference_metafield) = &variant.component_reference {
176+
return serde_json::from_str(&component_reference_metafield.value).unwrap();
177+
}
178+
179+
return Vec::new();
180+
}

0 commit comments

Comments
 (0)