use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct TaxLine {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rate: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub price_set: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct DiscountApplication {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub discount_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allocation_method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_selection: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct DiscountAllocation {
#[serde(skip_serializing_if = "Option::is_none")]
pub amount: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub discount_application_index: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub amount_set: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct LineItemProperty {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct LineItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub variant_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub product_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quantity: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sku: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub variant_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vendor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub grams: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub taxable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gift_card: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub requires_shipping: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<Vec<LineItemProperty>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tax_lines: Option<Vec<TaxLine>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub discount_allocations: Option<Vec<DiscountAllocation>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_discount: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fulfillment_service: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fulfillment_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub product_exists: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fulfillable_quantity: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub duties: Option<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub price_set: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_discount_set: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub admin_graphql_api_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct ShippingLine {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub carrier_identifier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tax_lines: Option<Vec<TaxLine>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub price_set: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub discount_allocations: Option<Vec<DiscountAllocation>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub discounted_price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub discounted_price_set: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct NoteAttribute {
pub name: String,
pub value: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tax_line_serialization() {
let tax_line = TaxLine {
title: Some("State Tax".to_string()),
price: Some("8.50".to_string()),
rate: Some(0.085),
price_set: None,
};
let json = serde_json::to_string(&tax_line).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["title"], "State Tax");
assert_eq!(parsed["price"], "8.50");
assert_eq!(parsed["rate"], 0.085);
assert!(parsed.get("price_set").is_none());
}
#[test]
fn test_discount_application_with_type_rename() {
let discount = DiscountApplication {
discount_type: Some("discount_code".to_string()),
value: Some("10.00".to_string()),
value_type: Some("fixed_amount".to_string()),
allocation_method: Some("across".to_string()),
target_selection: Some("all".to_string()),
target_type: Some("line_item".to_string()),
code: Some("SAVE10".to_string()),
title: Some("$10 Off".to_string()),
description: Some("Save $10 on your order".to_string()),
};
let json = serde_json::to_string(&discount).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["type"], "discount_code");
assert_eq!(parsed["value"], "10.00");
assert_eq!(parsed["code"], "SAVE10");
let json_with_type = r#"{
"type": "manual",
"value": "5.00",
"value_type": "percentage"
}"#;
let deserialized: DiscountApplication = serde_json::from_str(json_with_type).unwrap();
assert_eq!(deserialized.discount_type, Some("manual".to_string()));
}
#[test]
fn test_line_item_with_nested_tax_lines() {
let line_item = LineItem {
id: Some(1234567890),
variant_id: Some(9876543210),
product_id: Some(5555555555),
title: Some("Awesome Product".to_string()),
quantity: Some(3),
price: Some("49.99".to_string()),
sku: Some("AWESOME-001".to_string()),
variant_title: Some("Large / Blue".to_string()),
vendor: Some("Cool Vendor".to_string()),
grams: Some(500),
taxable: Some(true),
gift_card: Some(false),
requires_shipping: Some(true),
name: Some("Awesome Product - Large / Blue".to_string()),
tax_lines: Some(vec![
TaxLine {
title: Some("State Tax".to_string()),
price: Some("4.00".to_string()),
rate: Some(0.08),
price_set: None,
},
TaxLine {
title: Some("City Tax".to_string()),
price: Some("1.25".to_string()),
rate: Some(0.025),
price_set: None,
},
]),
discount_allocations: Some(vec![DiscountAllocation {
amount: Some("5.00".to_string()),
discount_application_index: Some(0),
amount_set: None,
}]),
total_discount: Some("5.00".to_string()),
..Default::default()
};
let json = serde_json::to_string(&line_item).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["id"], 1234567890u64);
assert_eq!(parsed["title"], "Awesome Product");
assert_eq!(parsed["quantity"], 3);
assert_eq!(parsed["price"], "49.99");
assert!(parsed["taxable"].as_bool().unwrap());
assert!(!parsed["gift_card"].as_bool().unwrap());
let tax_lines = parsed["tax_lines"].as_array().unwrap();
assert_eq!(tax_lines.len(), 2);
assert_eq!(tax_lines[0]["title"], "State Tax");
assert_eq!(tax_lines[1]["title"], "City Tax");
let discount_allocations = parsed["discount_allocations"].as_array().unwrap();
assert_eq!(discount_allocations.len(), 1);
assert_eq!(discount_allocations[0]["amount"], "5.00");
}
#[test]
fn test_line_item_deserialization() {
let json = r#"{
"id": 11111,
"variant_id": 22222,
"product_id": 33333,
"title": "Test Product",
"quantity": 1,
"price": "19.99",
"taxable": true,
"tax_lines": [
{
"title": "GST",
"price": "1.00",
"rate": 0.05
}
]
}"#;
let line_item: LineItem = serde_json::from_str(json).unwrap();
assert_eq!(line_item.id, Some(11111));
assert_eq!(line_item.title, Some("Test Product".to_string()));
assert_eq!(line_item.quantity, Some(1));
assert_eq!(line_item.price, Some("19.99".to_string()));
let tax_lines = line_item.tax_lines.unwrap();
assert_eq!(tax_lines.len(), 1);
assert_eq!(tax_lines[0].title, Some("GST".to_string()));
assert_eq!(tax_lines[0].rate, Some(0.05));
}
#[test]
fn test_shipping_line_serialization() {
let shipping = ShippingLine {
id: Some(9999),
title: Some("Standard Shipping".to_string()),
price: Some("5.99".to_string()),
code: Some("STANDARD".to_string()),
source: Some("shopify".to_string()),
carrier_identifier: Some("ups".to_string()),
tax_lines: Some(vec![TaxLine {
title: Some("Shipping Tax".to_string()),
price: Some("0.48".to_string()),
rate: Some(0.08),
price_set: None,
}]),
..Default::default()
};
let json = serde_json::to_string(&shipping).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["title"], "Standard Shipping");
assert_eq!(parsed["price"], "5.99");
assert_eq!(parsed["tax_lines"][0]["title"], "Shipping Tax");
}
#[test]
fn test_note_attribute_serialization() {
let note = NoteAttribute {
name: "gift_message".to_string(),
value: "Happy Birthday!".to_string(),
};
let json = serde_json::to_string(¬e).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["name"], "gift_message");
assert_eq!(parsed["value"], "Happy Birthday!");
}
#[test]
fn test_line_item_property_serialization() {
let property = LineItemProperty {
name: "engraving".to_string(),
value: "J + M".to_string(),
};
let json = serde_json::to_string(&property).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["name"], "engraving");
assert_eq!(parsed["value"], "J + M");
}
}