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