1use crate::error::ScriptError;
10use ferriskey::Value;
11
12#[derive(Debug)]
14pub struct FcallResult {
15 pub success: bool,
17 pub status: String,
19 pub fields: Vec<Value>,
21}
22
23impl FcallResult {
24 pub fn parse(raw: &Value) -> Result<Self, ScriptError> {
26 let items = match raw {
27 Value::Array(arr) => arr,
28 _ => {
32 return Err(ScriptError::Parse(format!(
33 "expected Array, got {:?}",
34 value_type_name(raw)
35 )));
36 }
37 };
38
39 if items.is_empty() {
40 return Err(ScriptError::Parse("empty FCALL result array".into()));
41 }
42
43 let status_code = match items.first() {
45 Some(Ok(Value::Int(n))) => *n,
46 other => {
47 return Err(ScriptError::Parse(format!(
48 "expected Int at index 0, got {:?}",
49 other
50 )));
51 }
52 };
53
54 let status = if items.len() > 1 {
56 value_to_string(items[1].as_ref().ok())
57 } else {
58 String::new()
59 };
60
61 let fields: Vec<Value> = items
63 .iter()
64 .skip(2)
65 .filter_map(|r| r.as_ref().ok().cloned())
66 .collect();
67
68 Ok(FcallResult {
69 success: status_code == 1,
70 status,
71 fields,
72 })
73 }
74
75 pub fn into_success(self) -> Result<Self, ScriptError> {
84 if self.success {
85 Ok(self)
86 } else {
87 let detail = value_to_string(self.fields.first());
88 let err = ScriptError::from_code_with_detail(&self.status, &detail)
89 .unwrap_or_else(|| {
90 ScriptError::Parse(format!("unknown error code: {}", self.status))
91 });
92 Err(err)
93 }
94 }
95
96 pub fn field_str(&self, index: usize) -> String {
98 self.fields
99 .get(index)
100 .map(|v| value_to_string(Some(v)))
101 .unwrap_or_default()
102 }
103}
104
105pub trait FromFcallResult: Sized {
110 fn from_fcall_result(raw: &Value) -> Result<Self, ScriptError>;
111}
112
113fn value_to_string(v: Option<&Value>) -> String {
115 match v {
116 Some(Value::BulkString(b)) => String::from_utf8_lossy(b).into_owned(),
117 Some(Value::SimpleString(s)) => s.clone(),
118 Some(Value::Int(n)) => n.to_string(),
119 Some(Value::Okay) => "OK".into(),
120 _ => String::new(),
121 }
122}
123
124fn value_type_name(v: &Value) -> &'static str {
126 match v {
127 Value::Nil => "Nil",
128 Value::Int(_) => "Int",
129 Value::BulkString(_) => "BulkString",
130 Value::Array(_) => "Array",
131 Value::SimpleString(_) => "SimpleString",
132 Value::Okay => "Okay",
133 Value::Map(_) => "Map",
134 Value::Set(_) => "Set",
135 Value::Double(_) => "Double",
136 Value::Boolean(_) => "Boolean",
137 Value::BigNumber(_) => "BigNumber",
138 Value::VerbatimString { .. } => "VerbatimString",
139 Value::Attribute { .. } => "Attribute",
140 Value::Push { .. } => "Push",
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 fn ok_value(fields: Vec<Value>) -> Value {
149 let mut arr: Vec<Result<Value, ferriskey::Error>> = vec![
150 Ok(Value::Int(1)),
151 Ok(Value::BulkString("OK".into())),
152 ];
153 for f in fields {
154 arr.push(Ok(f));
155 }
156 Value::Array(arr)
157 }
158
159 fn err_value(code: &str) -> Value {
160 Value::Array(vec![
161 Ok(Value::Int(0)),
162 Ok(Value::BulkString(Vec::from(code.as_bytes()).into())),
163 ])
164 }
165
166 #[test]
167 fn parse_success() {
168 let raw = ok_value(vec![Value::BulkString("hello".into())]);
169 let result = FcallResult::parse(&raw).unwrap();
170 assert!(result.success);
171 assert_eq!(result.status, "OK");
172 assert_eq!(result.fields.len(), 1);
173 assert_eq!(result.field_str(0), "hello");
174 }
175
176 #[test]
177 fn parse_error() {
178 let raw = err_value("stale_lease");
179 let result = FcallResult::parse(&raw).unwrap();
180 assert!(!result.success);
181 assert_eq!(result.status, "stale_lease");
182 }
183
184 #[test]
185 fn into_success_ok() {
186 let raw = ok_value(vec![]);
187 let result = FcallResult::parse(&raw).unwrap().into_success();
188 assert!(result.is_ok());
189 }
190
191 #[test]
192 fn into_success_err() {
193 let raw = err_value("lease_expired");
194 let result = FcallResult::parse(&raw).unwrap().into_success();
195 assert!(result.is_err());
196 assert!(matches!(result.unwrap_err(), ScriptError::LeaseExpired));
197 }
198
199 #[test]
200 fn parse_non_array_fails() {
201 let raw = Value::SimpleString("hello".into());
202 let result = FcallResult::parse(&raw);
203 assert!(result.is_err());
204 }
205}