use std::{fmt, fmt::Debug};
use borsh::{BorshDeserialize, BorshSerialize};
use chrono::SecondsFormat;
use rialo_cli_representable::Representable;
use serde::{Deserialize, Serialize};
use crate::RexError;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub enum FilterResult {
Success(Vec<u8>),
Error(String),
}
impl FilterResult {
pub fn is_success(&self) -> bool {
matches!(self, FilterResult::Success(_))
}
pub fn is_error(&self) -> bool {
matches!(self, FilterResult::Error(_))
}
pub fn as_success(&self) -> Option<&Vec<u8>> {
if let FilterResult::Success(ref bytes) = self {
Some(bytes)
} else {
None
}
}
pub fn as_error(&self) -> Option<&String> {
if let FilterResult::Error(ref err) = self {
Some(err)
} else {
None
}
}
}
impl fmt::Display for FilterResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FilterResult::Success(bytes) => write!(f, "Success: {:?}", bytes),
FilterResult::Error(err) => write!(f, "Error: {}", err),
}
}
}
impl From<Result<Vec<u8>, String>> for FilterResult {
fn from(result: Result<Vec<u8>, String>) -> Self {
match result {
Ok(bytes) => FilterResult::Success(bytes),
Err(err) => FilterResult::Error(err),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub enum RexData {
Raw(Vec<u8>),
Filtered(Vec<FilterResult>),
}
impl RexData {
pub fn is_raw(&self) -> bool {
matches!(self, RexData::Raw(_))
}
pub fn is_filtered(&self) -> bool {
matches!(self, RexData::Filtered(_))
}
pub fn as_raw(&self) -> Option<&Vec<u8>> {
if let RexData::Raw(ref data) = self {
Some(data)
} else {
None
}
}
pub fn as_filtered(&self) -> Option<&Vec<FilterResult>> {
if let RexData::Filtered(ref results) = self {
Some(results)
} else {
None
}
}
pub fn len(&self) -> usize {
match self {
RexData::Raw(data) => data.len(),
RexData::Filtered(results) => results
.iter()
.map(|r| match r {
FilterResult::Success(bytes) => bytes.len(),
FilterResult::Error(err) => err.len(),
})
.sum(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
Representable,
)]
#[representable(human_readable = "rex_response_human_readable")]
pub struct RexResponse {
pub response: RexData,
pub timestamp: String,
}
fn rex_response_human_readable(response: &RexResponse) -> String {
let mut out = String::new();
out.push_str(&format!(
"Response: {:?}, Timestamp (from TEE): {}",
response.response, response.timestamp
));
out
}
impl RexResponse {
pub fn new(response: RexData) -> Self {
Self {
response,
timestamp: chrono::Utc::now().to_rfc3339_opts(SecondsFormat::Micros, true),
}
}
pub fn into_successful_responses(self) -> Vec<Vec<u8>> {
match self.response {
RexData::Raw(data) => vec![data],
RexData::Filtered(results) => results
.into_iter()
.filter_map(|r| {
if let FilterResult::Success(bytes) = r {
Some(bytes)
} else {
None
}
})
.collect(),
}
}
}
#[derive(
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
Representable,
)]
#[serde(tag = "status", content = "contents")]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
#[representable(human_readable = "rex_output_human_readable")]
pub enum RexOutput {
Success(RexResponse),
RexError(RexError),
UnserializableResponse(String),
}
fn rex_output_human_readable(output: &RexOutput) -> String {
match output {
RexOutput::Success(resp) => {
let len = resp.response.len();
format!("Success: {} bytes @ {}", len, resp.timestamp)
}
RexOutput::RexError(err) => format!("RexError: {}", err),
RexOutput::UnserializableResponse(msg) => {
format!("UnserializableResponse: {}", msg)
}
}
}
impl RexOutput {
pub fn is_success(&self) -> bool {
matches!(self, RexOutput::Success(_))
}
pub fn is_rex_error(&self) -> bool {
matches!(self, RexOutput::RexError(_))
}
pub fn is_unserializable_response(&self) -> bool {
matches!(self, RexOutput::UnserializableResponse(_))
}
pub fn as_success(&self) -> Option<&RexResponse> {
if let RexOutput::Success(ref response) = self {
Some(response)
} else {
None
}
}
pub fn as_rex_error(&self) -> Option<&RexError> {
if let RexOutput::RexError(ref error) = self {
Some(error)
} else {
None
}
}
pub fn into_success_payload(self) -> Option<Vec<Vec<u8>>> {
match self {
RexOutput::Success(response) => Some(response.into_successful_responses()),
_ => None,
}
}
}
impl From<Result<RexResponse, RexError>> for RexOutput {
fn from(result: Result<RexResponse, RexError>) -> Self {
match result {
Ok(response) => RexOutput::Success(response),
Err(error) => RexOutput::RexError(error),
}
}
}
impl From<RexError> for RexOutput {
fn from(error: RexError) -> Self {
RexOutput::RexError(error)
}
}
#[cfg(test)]
mod test {
use serde_json::json;
use super::*;
#[test]
fn test_rex_result_success_serialization() {
let rex_data = RexResponse::new(RexData::Raw(
serde_json::to_vec(&json!({"data": 42, "message": "test"})).unwrap(),
));
let result = RexOutput::Success(rex_data.clone());
let json = serde_json::to_value(&result).unwrap();
assert_eq!(
json,
json!({
"status": "success",
"contents": rex_data
})
);
}
#[test]
fn test_rex_result_error_serialization() {
let error = RexError::HttpStatusError {
status: 404,
reason: String::from("HTTP Status Code 404 Not Found"),
};
let result = RexOutput::RexError(error.clone());
let json = serde_json::to_value(&result).unwrap();
assert_eq!(
json,
json!({
"status": "rex-error",
"contents": error
})
);
}
#[test]
fn test_rex_result_unserializable_serialization() {
let result = RexOutput::UnserializableResponse("Failed to parse response".to_string());
let json = serde_json::to_value(&result).unwrap();
assert_eq!(
json,
json!({
"status": "unserializable-response",
"contents": "Failed to parse response"
})
);
}
#[test]
fn test_rex_result_deserialization() {
let expected = RexResponse::new(RexData::Raw(
serde_json::to_vec(&json!({"data": 123 })).unwrap(),
));
let expected_clone = expected.clone();
let json = json!({
"status": "success",
"contents": expected_clone
});
let result: RexOutput = serde_json::from_value(json).unwrap();
assert_eq!(result, RexOutput::Success(expected_clone));
let error = RexError::HttpStatusError {
status: 500,
reason: "Internal Server Error".to_string(),
};
let json = json!({
"status": "rex-error",
"contents": error.clone()
});
let result: RexOutput = serde_json::from_value(json).unwrap();
assert_eq!(result, RexOutput::RexError(error));
let json = json!({
"status": "unserializable-response",
"contents": "Parse error"
});
let result: RexOutput = serde_json::from_value(json).unwrap();
assert_eq!(
result,
RexOutput::UnserializableResponse("Parse error".to_string())
);
}
#[test]
fn test_rex_result_roundtrip() {
let rex_response = RexResponse::new(RexData::Raw(
serde_json::to_vec(&json!({"key": "value", "number": 42})).unwrap(),
));
let error = RexError::HttpStatusError {
status: 403,
reason: "Forbidden".to_string(),
};
let test_cases = vec![
RexOutput::Success(rex_response),
RexOutput::RexError(error),
RexOutput::UnserializableResponse("Invalid JSON".to_string()),
];
for original in test_cases {
let serialized = serde_json::to_string(&original).unwrap();
let deserialized: RexOutput = serde_json::from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
}
#[test]
fn test_rex_result_complex_nested_values() {
let complex_value = json!({
"nested": {
"array": [1, 2, 3],
"object": {"key": "value"},
"null": null,
"bool": true
}
});
let rex_response =
RexResponse::new(RexData::Raw(serde_json::to_vec(&complex_value).unwrap()));
let result = RexOutput::Success(rex_response.clone());
let json = serde_json::to_value(&result).unwrap();
assert_eq!(json["status"], "success");
assert_eq!(json["contents"], json!(rex_response));
}
#[test]
fn test_rex_result_empty_values() {
let rex_response = RexResponse::new(RexData::Raw(serde_json::to_vec(&json!({})).unwrap()));
let result = RexOutput::Success(rex_response.clone());
let json = serde_json::to_value(&result).unwrap();
assert_eq!(
json,
json!({
"status": "success",
"contents": rex_response
})
);
let result = RexOutput::UnserializableResponse("".to_string());
let json = serde_json::to_value(&result).unwrap();
assert_eq!(
json,
json!({
"status": "unserializable-response",
"contents": ""
})
);
}
}