use crate::error::BittensorError;
use crate::types::Balance;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExtrinsicStatus {
Success,
Failed,
Pending,
Dropped,
}
impl std::fmt::Display for ExtrinsicStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Success => write!(f, "Success"),
Self::Failed => write!(f, "Failed"),
Self::Pending => write!(f, "Pending"),
Self::Dropped => write!(f, "Dropped"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtrinsicResponse<T = ()> {
pub status: ExtrinsicStatus,
pub message: String,
pub block_hash: Option<String>,
pub extrinsic_hash: Option<String>,
pub block_number: Option<u64>,
pub fee: Option<Balance>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<T>,
#[serde(skip)]
pub error: Option<BittensorError>,
}
impl<T> ExtrinsicResponse<T> {
pub fn success() -> Self {
Self {
status: ExtrinsicStatus::Success,
message: "Extrinsic succeeded".to_string(),
block_hash: None,
extrinsic_hash: None,
block_number: None,
fee: None,
data: None,
error: None,
}
}
pub fn failed(message: &str) -> Self {
Self {
status: ExtrinsicStatus::Failed,
message: message.to_string(),
block_hash: None,
extrinsic_hash: None,
block_number: None,
fee: None,
data: None,
error: None,
}
}
pub fn from_error(error: BittensorError) -> Self {
Self {
status: ExtrinsicStatus::Failed,
message: error.to_string(),
block_hash: None,
extrinsic_hash: None,
block_number: None,
fee: None,
data: None,
error: Some(error),
}
}
pub fn is_success(&self) -> bool {
self.status == ExtrinsicStatus::Success
}
pub fn is_failed(&self) -> bool {
self.status == ExtrinsicStatus::Failed
}
pub fn with_message(mut self, message: &str) -> Self {
self.message = message.to_string();
self
}
pub fn with_block_hash(mut self, hash: &str) -> Self {
self.block_hash = Some(hash.to_string());
self
}
pub fn with_extrinsic_hash(mut self, hash: &str) -> Self {
self.extrinsic_hash = Some(hash.to_string());
self
}
pub fn with_block_number(mut self, number: u64) -> Self {
self.block_number = Some(number);
self
}
pub fn with_fee(mut self, fee: Balance) -> Self {
self.fee = Some(fee);
self
}
pub fn with_data(mut self, data: T) -> Self {
self.data = Some(data);
self
}
pub fn with_error(mut self, error: BittensorError) -> Self {
self.error = Some(error);
self
}
pub fn into_result(self) -> Result<T, BittensorError>
where
T: Default,
{
if self.is_success() {
Ok(self.data.unwrap_or_default())
} else {
Err(self.error.unwrap_or(BittensorError::ChainError {
message: self.message,
}))
}
}
pub fn map<U, F>(self, f: F) -> ExtrinsicResponse<U>
where
F: FnOnce(T) -> U,
{
ExtrinsicResponse {
status: self.status,
message: self.message,
block_hash: self.block_hash,
extrinsic_hash: self.extrinsic_hash,
block_number: self.block_number,
fee: self.fee,
data: self.data.map(f),
error: self.error,
}
}
pub fn discard_data(self) -> ExtrinsicResponse<()> {
ExtrinsicResponse {
status: self.status,
message: self.message,
block_hash: self.block_hash,
extrinsic_hash: self.extrinsic_hash,
block_number: self.block_number,
fee: self.fee,
data: Some(()),
error: self.error,
}
}
}
impl<T> Default for ExtrinsicResponse<T> {
fn default() -> Self {
Self::success()
}
}
impl<T: std::fmt::Display> std::fmt::Display for ExtrinsicResponse<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {}", self.status, self.message)?;
if let Some(ref hash) = self.block_hash {
write!(f, " (block: {})", hash)?;
}
if let Some(ref fee) = self.fee {
write!(f, " (fee: {})", fee)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_success_response() {
let response: ExtrinsicResponse<()> = ExtrinsicResponse::success();
assert!(response.is_success());
assert!(!response.is_failed());
assert_eq!(response.status, ExtrinsicStatus::Success);
}
#[test]
fn test_failed_response() {
let response: ExtrinsicResponse<()> = ExtrinsicResponse::failed("test error");
assert!(!response.is_success());
assert!(response.is_failed());
assert_eq!(response.message, "test error");
}
#[test]
fn test_builder_pattern() {
let response = ExtrinsicResponse::<u64>::success()
.with_message("Transfer completed")
.with_block_hash("0x1234")
.with_extrinsic_hash("0x5678")
.with_block_number(100)
.with_fee(Balance::from_tao(0.001))
.with_data(42);
assert!(response.is_success());
assert_eq!(response.message, "Transfer completed");
assert_eq!(response.block_hash, Some("0x1234".to_string()));
assert_eq!(response.extrinsic_hash, Some("0x5678".to_string()));
assert_eq!(response.block_number, Some(100));
assert!(response.fee.is_some());
assert_eq!(response.data, Some(42));
}
#[test]
fn test_from_error() {
let error = BittensorError::ChainError {
message: "test".to_string(),
};
let response: ExtrinsicResponse<()> = ExtrinsicResponse::from_error(error);
assert!(response.is_failed());
assert!(response.error.is_some());
}
#[test]
fn test_into_result_success() {
let response = ExtrinsicResponse::<u64>::success().with_data(42);
let result = response.into_result();
assert_eq!(result.unwrap(), 42);
}
#[test]
fn test_into_result_failure() {
let response: ExtrinsicResponse<u64> = ExtrinsicResponse::failed("test error");
let result = response.into_result();
assert!(result.is_err());
}
#[test]
fn test_map() {
let response = ExtrinsicResponse::<u64>::success()
.with_data(42)
.map(|x| x.to_string());
assert_eq!(response.data, Some("42".to_string()));
}
#[test]
fn test_discard_data() {
let response = ExtrinsicResponse::<u64>::success()
.with_data(42)
.discard_data();
assert_eq!(response.data, Some(()));
assert!(response.is_success());
}
#[test]
fn test_display() {
let response = ExtrinsicResponse::<String>::success()
.with_message("Done")
.with_block_hash("0x1234");
let display = format!("{}", response);
assert!(display.contains("Success"));
assert!(display.contains("Done"));
assert!(display.contains("0x1234"));
}
#[test]
fn test_serialization() {
let response = ExtrinsicResponse::<u64>::success()
.with_message("test")
.with_data(42);
let json = serde_json::to_string(&response).unwrap();
assert!(json.contains("\"status\":\"Success\""));
assert!(json.contains("\"data\":42"));
}
}