use serde::{Deserialize, Serialize};
use crate::error::CoreError;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SpendIntent {
pub delegation_id: String,
pub nonce: u64,
pub amount_cents: u64,
pub merchant_id: String,
pub category: String,
pub memo: String,
}
impl SpendIntent {
pub fn new(
delegation_id: impl Into<String>,
nonce: u64,
amount_cents: u64,
merchant_id: impl Into<String>,
category: impl Into<String>,
memo: impl Into<String>,
) -> Self {
Self {
delegation_id: delegation_id.into(),
nonce,
amount_cents,
merchant_id: merchant_id.into(),
category: category.into(),
memo: memo.into(),
}
}
pub fn validate(&self) -> Result<(), CoreError> {
if self.delegation_id.trim().is_empty() {
return Err(CoreError::InvalidIntent("delegation_id 不能为空".into()));
}
if self.merchant_id.trim().is_empty() {
return Err(CoreError::InvalidIntent("merchant_id 不能为空".into()));
}
if self.amount_cents == 0 {
return Err(CoreError::InvalidIntent(
"amount_cents 不能为 0(单位是分)".into(),
));
}
if self.nonce == 0 {
return Err(CoreError::InvalidIntent(
"nonce 不能为 0(防重放 nonce 从 1 起,对齐 mist-core 断言 7)".into(),
));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> SpendIntent {
SpendIntent::new("d1", 1, 500, "jd:shop-1", "grocery", "早饭")
}
#[test]
fn validate_accepts_well_formed_intent() {
assert_eq!(sample().validate(), Ok(()));
}
#[test]
fn validate_rejects_zero_amount() {
let i = SpendIntent {
amount_cents: 0,
..sample()
};
assert!(matches!(i.validate(), Err(CoreError::InvalidIntent(_))));
}
#[test]
fn validate_rejects_zero_nonce() {
let i = SpendIntent {
nonce: 0,
..sample()
};
assert!(matches!(i.validate(), Err(CoreError::InvalidIntent(_))));
}
#[test]
fn validate_rejects_empty_fields() {
for bad in [
SpendIntent {
delegation_id: "".into(),
..sample()
},
SpendIntent {
merchant_id: " ".into(),
..sample()
},
] {
assert!(
matches!(bad.validate(), Err(CoreError::InvalidIntent(_))),
"应拒收空字段: {bad:?}"
);
}
}
#[test]
fn serde_roundtrip() {
let i = sample();
let json = serde_json::to_string(&i).expect("序列化");
let back: SpendIntent = serde_json::from_str(&json).expect("反序列化");
assert_eq!(back, i);
assert!(json.contains("\"amount_cents\":500"));
assert!(json.contains("\"nonce\":1"));
}
}