use std::{fmt, fmt::Debug};
use borsh::{BorshDeserialize, BorshSerialize};
use chrono::SecondsFormat;
use rialo_cli_representable::Representable;
use serde::{Deserialize, Serialize};
use crate::OracleError;
#[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 OracleData {
Raw(Vec<u8>),
Filtered(Vec<FilterResult>),
}
impl OracleData {
pub fn is_raw(&self) -> bool {
matches!(self, OracleData::Raw(_))
}
pub fn is_filtered(&self) -> bool {
matches!(self, OracleData::Filtered(_))
}
pub fn as_raw(&self) -> Option<&Vec<u8>> {
if let OracleData::Raw(ref data) = self {
Some(data)
} else {
None
}
}
pub fn as_filtered(&self) -> Option<&Vec<FilterResult>> {
if let OracleData::Filtered(ref results) = self {
Some(results)
} else {
None
}
}
pub fn len(&self) -> usize {
match self {
OracleData::Raw(data) => data.len(),
OracleData::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 = "oracle_response_human_readable")]
pub struct OracleResponse {
pub response: OracleData,
pub timestamp: String,
}
fn oracle_response_human_readable(response: &OracleResponse) -> String {
let mut out = String::new();
out.push_str(&format!(
"Response: {:?}, Timestamp (from TEE): {}",
response.response, response.timestamp
));
out
}
impl OracleResponse {
pub fn new(response: OracleData) -> 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 {
OracleData::Raw(data) => vec![data],
OracleData::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 = "oracle_output_human_readable")]
pub enum OracleOutput {
Success(OracleResponse),
OracleError(OracleError),
UnserializableResponse(String),
}
fn oracle_output_human_readable(output: &OracleOutput) -> String {
match output {
OracleOutput::Success(resp) => {
let len = resp.response.len();
format!("Success: {} bytes @ {}", len, resp.timestamp)
}
OracleOutput::OracleError(err) => format!("OracleError: {}", err),
OracleOutput::UnserializableResponse(msg) => {
format!("UnserializableResponse: {}", msg)
}
}
}
impl OracleOutput {
pub fn is_success(&self) -> bool {
matches!(self, OracleOutput::Success(_))
}
pub fn is_oracle_error(&self) -> bool {
matches!(self, OracleOutput::OracleError(_))
}
pub fn is_unserializable_response(&self) -> bool {
matches!(self, OracleOutput::UnserializableResponse(_))
}
pub fn as_success(&self) -> Option<&OracleResponse> {
if let OracleOutput::Success(ref response) = self {
Some(response)
} else {
None
}
}
pub fn as_oracle_error(&self) -> Option<&OracleError> {
if let OracleOutput::OracleError(ref error) = self {
Some(error)
} else {
None
}
}
pub fn into_success_payload(self) -> Option<Vec<Vec<u8>>> {
match self {
OracleOutput::Success(response) => Some(response.into_successful_responses()),
_ => None,
}
}
}
impl From<Result<OracleResponse, OracleError>> for OracleOutput {
fn from(result: Result<OracleResponse, OracleError>) -> Self {
match result {
Ok(response) => OracleOutput::Success(response),
Err(error) => OracleOutput::OracleError(error),
}
}
}
impl From<OracleError> for OracleOutput {
fn from(error: OracleError) -> Self {
OracleOutput::OracleError(error)
}
}
#[cfg(test)]
mod test {
use serde_json::json;
use super::*;
#[test]
fn test_oracle_result_success_serialization() {
let oracle_data = OracleResponse::new(OracleData::Raw(
serde_json::to_vec(&json!({"data": 42, "message": "test"})).unwrap(),
));
let result = OracleOutput::Success(oracle_data.clone());
let json = serde_json::to_value(&result).unwrap();
assert_eq!(
json,
json!({
"status": "success",
"contents": oracle_data
})
);
}
#[test]
fn test_oracle_result_error_serialization() {
let error = OracleError::HttpStatusError {
status: 404,
reason: String::from("HTTP Status Code 404 Not Found"),
};
let result = OracleOutput::OracleError(error.clone());
let json = serde_json::to_value(&result).unwrap();
assert_eq!(
json,
json!({
"status": "oracle-error",
"contents": error
})
);
}
#[test]
fn test_oracle_result_unserializable_serialization() {
let result = OracleOutput::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_oracle_result_deserialization() {
let expected = OracleResponse::new(OracleData::Raw(
serde_json::to_vec(&json!({"data": 123 })).unwrap(),
));
let expected_clone = expected.clone();
let json = json!({
"status": "success",
"contents": expected_clone
});
let result: OracleOutput = serde_json::from_value(json).unwrap();
assert_eq!(result, OracleOutput::Success(expected_clone));
let error = OracleError::HttpStatusError {
status: 500,
reason: "Internal Server Error".to_string(),
};
let json = json!({
"status": "oracle-error",
"contents": error.clone()
});
let result: OracleOutput = serde_json::from_value(json).unwrap();
assert_eq!(result, OracleOutput::OracleError(error));
let json = json!({
"status": "unserializable-response",
"contents": "Parse error"
});
let result: OracleOutput = serde_json::from_value(json).unwrap();
assert_eq!(
result,
OracleOutput::UnserializableResponse("Parse error".to_string())
);
}
#[test]
fn test_oracle_result_roundtrip() {
let oracle_response = OracleResponse::new(OracleData::Raw(
serde_json::to_vec(&json!({"key": "value", "number": 42})).unwrap(),
));
let error = OracleError::HttpStatusError {
status: 403,
reason: "Forbidden".to_string(),
};
let test_cases = vec![
OracleOutput::Success(oracle_response),
OracleOutput::OracleError(error),
OracleOutput::UnserializableResponse("Invalid JSON".to_string()),
];
for original in test_cases {
let serialized = serde_json::to_string(&original).unwrap();
let deserialized: OracleOutput = serde_json::from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
}
#[test]
fn test_oracle_result_complex_nested_values() {
let complex_value = json!({
"nested": {
"array": [1, 2, 3],
"object": {"key": "value"},
"null": null,
"bool": true
}
});
let oracle_response =
OracleResponse::new(OracleData::Raw(serde_json::to_vec(&complex_value).unwrap()));
let result = OracleOutput::Success(oracle_response.clone());
let json = serde_json::to_value(&result).unwrap();
assert_eq!(json["status"], "success");
assert_eq!(json["contents"], json!(oracle_response));
}
#[test]
fn test_oracle_result_empty_values() {
let oracle_response =
OracleResponse::new(OracleData::Raw(serde_json::to_vec(&json!({})).unwrap()));
let result = OracleOutput::Success(oracle_response.clone());
let json = serde_json::to_value(&result).unwrap();
assert_eq!(
json,
json!({
"status": "success",
"contents": oracle_response
})
);
let result = OracleOutput::UnserializableResponse("".to_string());
let json = serde_json::to_value(&result).unwrap();
assert_eq!(
json,
json!({
"status": "unserializable-response",
"contents": ""
})
);
}
}