Skip to main content

rialo_types/
output.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! # Output Module
5//!
6//! Defines the output format for REX results.
7//!
8//! This module provides the [`RexOutput`] enum which represents all possible
9//! outcomes from a REX execution. The output is designed to be:
10//! - Self-describing with status tags
11//! - Easily serializable to JSON
12//! - Type-safe with exhaustive matching
13//!
14//! ## Output Types
15//!
16//! - `Success` - The REX execution completed successfully and returned data
17//! - `RexError` - The REX execution encountered an error
18//! - `UnserializableResponse` - The REX execution produced output that couldn't be serialized
19//!
20//! ## Serialization Format
21//!
22//! All outputs serialize to a consistent JSON structure:
23//! ```json
24//! {
25//!   "status": "success|rex-error|unserializable-response",
26//!   "contents": <actual data or error details>
27//! }
28//! ```
29
30use std::{fmt, fmt::Debug};
31
32use borsh::{BorshDeserialize, BorshSerialize};
33use chrono::SecondsFormat;
34use rialo_cli_representable::Representable;
35use serde::{Deserialize, Serialize};
36
37use crate::RexError;
38
39/// Result for filtering operations.
40#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
41pub enum FilterResult {
42    Success(Vec<u8>),
43    Error(String),
44}
45
46impl FilterResult {
47    /// Returns true if the filter result is a success.
48    pub fn is_success(&self) -> bool {
49        matches!(self, FilterResult::Success(_))
50    }
51
52    /// Returns true if the filter result is an error.
53    pub fn is_error(&self) -> bool {
54        matches!(self, FilterResult::Error(_))
55    }
56
57    /// Returns the contained bytes if the filter result is a success, otherwise returns `None`.
58    pub fn as_success(&self) -> Option<&Vec<u8>> {
59        if let FilterResult::Success(ref bytes) = self {
60            Some(bytes)
61        } else {
62            None
63        }
64    }
65
66    /// Returns the contained error message if the filter result is an error, otherwise returns `None`.
67    pub fn as_error(&self) -> Option<&String> {
68        if let FilterResult::Error(ref err) = self {
69            Some(err)
70        } else {
71            None
72        }
73    }
74}
75
76impl fmt::Display for FilterResult {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        match self {
79            FilterResult::Success(bytes) => write!(f, "Success: {:?}", bytes),
80            FilterResult::Error(err) => write!(f, "Error: {}", err),
81        }
82    }
83}
84
85impl From<Result<Vec<u8>, String>> for FilterResult {
86    fn from(result: Result<Vec<u8>, String>) -> Self {
87        match result {
88            Ok(bytes) => FilterResult::Success(bytes),
89            Err(err) => FilterResult::Error(err),
90        }
91    }
92}
93
94/// Represents the data returned by REX.
95/// This is a vector of bytes that can contain any data returned by the REX execution.
96#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
97pub enum RexData {
98    /// The raw bytes returned by the REX execution.
99    Raw(Vec<u8>),
100    /// The filtered bytes returned by the REX execution.
101    Filtered(Vec<FilterResult>),
102}
103
104impl RexData {
105    /// Returns true if the REX data is raw.
106    pub fn is_raw(&self) -> bool {
107        matches!(self, RexData::Raw(_))
108    }
109
110    /// Returns true if the REX data is filtered.
111    pub fn is_filtered(&self) -> bool {
112        matches!(self, RexData::Filtered(_))
113    }
114
115    /// Returns the contained raw bytes if the REX data is raw, otherwise returns `None`.
116    pub fn as_raw(&self) -> Option<&Vec<u8>> {
117        if let RexData::Raw(ref data) = self {
118            Some(data)
119        } else {
120            None
121        }
122    }
123
124    /// Returns the contained filtered results if the REX data is filtered, otherwise returns `None`.
125    pub fn as_filtered(&self) -> Option<&Vec<FilterResult>> {
126        if let RexData::Filtered(ref results) = self {
127            Some(results)
128        } else {
129            None
130        }
131    }
132
133    /// Returns the length of the contained data in bytes.
134    pub fn len(&self) -> usize {
135        match self {
136            RexData::Raw(data) => data.len(),
137            RexData::Filtered(results) => results
138                .iter()
139                .map(|r| match r {
140                    FilterResult::Success(bytes) => bytes.len(),
141                    FilterResult::Error(err) => err.len(),
142                })
143                .sum(),
144        }
145    }
146
147    /// Returns true if the contained data is empty.
148    pub fn is_empty(&self) -> bool {
149        self.len() == 0
150    }
151}
152
153/// The response received from REX.
154///
155/// This data structure is used to represent the successful output of a REX request.
156#[derive(
157    Clone,
158    Debug,
159    Eq,
160    PartialEq,
161    Serialize,
162    Deserialize,
163    BorshSerialize,
164    BorshDeserialize,
165    Representable,
166)]
167#[representable(human_readable = "rex_response_human_readable")]
168pub struct RexResponse {
169    /// The raw response data from the REX execution, encoded as bytes.
170    pub response: RexData,
171    /// The timestamp when the REX response was generated.
172    pub timestamp: String,
173}
174
175fn rex_response_human_readable(response: &RexResponse) -> String {
176    let mut out = String::new();
177    out.push_str(&format!(
178        "Response: {:?}, Timestamp (from TEE): {}",
179        response.response, response.timestamp
180    ));
181    out
182}
183
184impl RexResponse {
185    /// Creates a new `RexResponse` with the given response data and timestamp.
186    pub fn new(response: RexData) -> Self {
187        Self {
188            response,
189            // Always uses 'Z' suffix and 6 decimal places (microseconds)
190            // Produces exactly 30 bytes: "2025-08-24T10:59:40.123456Z"
191            timestamp: chrono::Utc::now().to_rfc3339_opts(SecondsFormat::Micros, true),
192        }
193    }
194
195    /// Convert to successful response data. In case of `RexData::Raw` returns a vector of one
196    /// element containing the raw bytes. In case of `RexData::Filtered` returns a vector of all
197    /// successful filtered bytes.
198    ///
199    /// If there are no successful responses, returns an empty vector.
200    pub fn into_successful_responses(self) -> Vec<Vec<u8>> {
201        match self.response {
202            RexData::Raw(data) => vec![data],
203            RexData::Filtered(results) => results
204                .into_iter()
205                .filter_map(|r| {
206                    if let FilterResult::Success(bytes) = r {
207                        Some(bytes)
208                    } else {
209                        None
210                    }
211                })
212                .collect(),
213        }
214    }
215}
216
217/// The output from REX.
218///
219/// This enum represents the possible outcomes of a REX execution.
220#[derive(
221    Clone,
222    Debug,
223    Eq,
224    PartialEq,
225    Serialize,
226    Deserialize,
227    BorshSerialize,
228    BorshDeserialize,
229    Representable,
230)]
231#[serde(tag = "status", content = "contents")]
232#[serde(rename_all = "kebab-case")]
233#[non_exhaustive]
234#[representable(human_readable = "rex_output_human_readable")]
235pub enum RexOutput {
236    /// The response of a successfully handled REX request.
237    Success(RexResponse),
238    /// The details of an error reported by a REX execution.
239    RexError(RexError),
240    /// The REX execution produced an unserializable response.
241    UnserializableResponse(String),
242}
243
244fn rex_output_human_readable(output: &RexOutput) -> String {
245    match output {
246        RexOutput::Success(resp) => {
247            let len = resp.response.len();
248            format!("Success: {} bytes @ {}", len, resp.timestamp)
249        }
250        RexOutput::RexError(err) => format!("RexError: {}", err),
251        RexOutput::UnserializableResponse(msg) => {
252            format!("UnserializableResponse: {}", msg)
253        }
254    }
255}
256
257impl RexOutput {
258    /// Returns true if the output is a success.
259    pub fn is_success(&self) -> bool {
260        matches!(self, RexOutput::Success(_))
261    }
262
263    /// Returns true if the output is a REX error.
264    pub fn is_rex_error(&self) -> bool {
265        matches!(self, RexOutput::RexError(_))
266    }
267
268    /// Returns true if the output is an unserializable response.
269    pub fn is_unserializable_response(&self) -> bool {
270        matches!(self, RexOutput::UnserializableResponse(_))
271    }
272
273    /// Returns the contained `RexResponse` if the output is a success, otherwise returns `None`.
274    pub fn as_success(&self) -> Option<&RexResponse> {
275        if let RexOutput::Success(ref response) = self {
276            Some(response)
277        } else {
278            None
279        }
280    }
281
282    /// Returns the contained `RexError` if the output is a REX error, otherwise returns `None`.
283    pub fn as_rex_error(&self) -> Option<&RexError> {
284        if let RexOutput::RexError(ref error) = self {
285            Some(error)
286        } else {
287            None
288        }
289    }
290
291    /// Converts to successful response data if the output is a success, and we have successful `RexData`.
292    /// Otherwise, returns `None`.
293    pub fn into_success_payload(self) -> Option<Vec<Vec<u8>>> {
294        match self {
295            RexOutput::Success(response) => Some(response.into_successful_responses()),
296            _ => None,
297        }
298    }
299}
300
301impl From<Result<RexResponse, RexError>> for RexOutput {
302    fn from(result: Result<RexResponse, RexError>) -> Self {
303        match result {
304            Ok(response) => RexOutput::Success(response),
305            Err(error) => RexOutput::RexError(error),
306        }
307    }
308}
309
310impl From<RexError> for RexOutput {
311    fn from(error: RexError) -> Self {
312        RexOutput::RexError(error)
313    }
314}
315
316#[cfg(test)]
317mod test {
318    use serde_json::json;
319
320    use super::*;
321
322    #[test]
323    fn test_rex_result_success_serialization() {
324        let rex_data = RexResponse::new(RexData::Raw(
325            serde_json::to_vec(&json!({"data": 42, "message": "test"})).unwrap(),
326        ));
327        let result = RexOutput::Success(rex_data.clone());
328        let json = serde_json::to_value(&result).unwrap();
329
330        assert_eq!(
331            json,
332            json!({
333                "status": "success",
334                "contents": rex_data
335            })
336        );
337    }
338
339    #[test]
340    fn test_rex_result_error_serialization() {
341        let error = RexError::HttpStatusError {
342            status: 404,
343            reason: String::from("HTTP Status Code 404 Not Found"),
344        };
345
346        let result = RexOutput::RexError(error.clone());
347        let json = serde_json::to_value(&result).unwrap();
348
349        assert_eq!(
350            json,
351            json!({
352                "status": "rex-error",
353                "contents": error
354            })
355        );
356    }
357
358    #[test]
359    fn test_rex_result_unserializable_serialization() {
360        let result = RexOutput::UnserializableResponse("Failed to parse response".to_string());
361        let json = serde_json::to_value(&result).unwrap();
362
363        assert_eq!(
364            json,
365            json!({
366                "status": "unserializable-response",
367                "contents": "Failed to parse response"
368            })
369        );
370    }
371
372    #[test]
373    fn test_rex_result_deserialization() {
374        // Test deserializing success
375        let expected = RexResponse::new(RexData::Raw(
376            serde_json::to_vec(&json!({"data": 123 })).unwrap(),
377        ));
378        let expected_clone = expected.clone();
379        let json = json!({
380            "status": "success",
381            "contents": expected_clone
382        });
383        let result: RexOutput = serde_json::from_value(json).unwrap();
384        assert_eq!(result, RexOutput::Success(expected_clone));
385
386        // Test deserializing error
387        let error = RexError::HttpStatusError {
388            status: 500,
389            reason: "Internal Server Error".to_string(),
390        };
391        let json = json!({
392            "status": "rex-error",
393            "contents": error.clone()
394        });
395        let result: RexOutput = serde_json::from_value(json).unwrap();
396        assert_eq!(result, RexOutput::RexError(error));
397
398        // Test deserializing unserializable response
399        let json = json!({
400            "status": "unserializable-response",
401            "contents": "Parse error"
402        });
403        let result: RexOutput = serde_json::from_value(json).unwrap();
404        assert_eq!(
405            result,
406            RexOutput::UnserializableResponse("Parse error".to_string())
407        );
408    }
409
410    #[test]
411    fn test_rex_result_roundtrip() {
412        let rex_response = RexResponse::new(RexData::Raw(
413            serde_json::to_vec(&json!({"key": "value", "number": 42})).unwrap(),
414        ));
415        let error = RexError::HttpStatusError {
416            status: 403,
417            reason: "Forbidden".to_string(),
418        };
419        let test_cases = vec![
420            RexOutput::Success(rex_response),
421            RexOutput::RexError(error),
422            RexOutput::UnserializableResponse("Invalid JSON".to_string()),
423        ];
424
425        for original in test_cases {
426            let serialized = serde_json::to_string(&original).unwrap();
427            let deserialized: RexOutput = serde_json::from_str(&serialized).unwrap();
428            assert_eq!(original, deserialized);
429        }
430    }
431
432    #[test]
433    fn test_rex_result_complex_nested_values() {
434        let complex_value = json!({
435            "nested": {
436                "array": [1, 2, 3],
437                "object": {"key": "value"},
438                "null": null,
439                "bool": true
440            }
441        });
442        let rex_response =
443            RexResponse::new(RexData::Raw(serde_json::to_vec(&complex_value).unwrap()));
444
445        let result = RexOutput::Success(rex_response.clone());
446        let json = serde_json::to_value(&result).unwrap();
447
448        assert_eq!(json["status"], "success");
449        assert_eq!(json["contents"], json!(rex_response));
450    }
451
452    #[test]
453    fn test_rex_result_empty_values() {
454        // Test with empty object
455        let rex_response = RexResponse::new(RexData::Raw(serde_json::to_vec(&json!({})).unwrap()));
456        let result = RexOutput::Success(rex_response.clone());
457        let json = serde_json::to_value(&result).unwrap();
458        assert_eq!(
459            json,
460            json!({
461                "status": "success",
462                "contents": rex_response
463            })
464        );
465
466        // Test with empty string
467        let result = RexOutput::UnserializableResponse("".to_string());
468        let json = serde_json::to_value(&result).unwrap();
469        assert_eq!(
470            json,
471            json!({
472                "status": "unserializable-response",
473                "contents": ""
474            })
475        );
476    }
477}