1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use crate::Error;
use async_trait::async_trait;
use std::any::Any;
use std::fmt;

/// BlockchainConnector trait is used to connect to a blockchain and send and receive information to and from the blockchain.
#[async_trait]
pub trait BlockchainConnector {
    /// ErrorType is the type of error that is returned by the BlockchainConnector
    type ErrorType: std::error::Error + fmt::Display + Send + Sync + 'static;

    /// new creates a new BlockchainConnector with a given url
    fn new(url: &str) -> Result<Self, Self::ErrorType>
    where
        Self: Sized;

    /// url returns the url of the BlockchainConnector
    fn url(&self) -> &str;

    // TODO(AS): return the fee estimates.. also consider adding functions to get common blockchain functions

    /// Returns the builder that can be used to build a BlockchainConnector with custom options
    fn builder() -> BlockchainConnectorBuilder<Self>
    where
        Self: Sized + Clone + BlockchainConnectorGeneral,
    {
        BlockchainConnectorBuilder::new()
    }
}

/// BlockchainConnectorGeneral is a general trait that can work with any struct that implements the BlockchainConnector trait
pub trait BlockchainConnectorGeneral {
    /// Returns a dyn Any reference to the BlockchainConnector
    fn as_any(&self) -> &dyn Any;

    /// Returns a clone in a box type
    fn box_clone(&self) -> Box<dyn BlockchainConnectorGeneral>;
}

/// ConnectorType is an enum that represents the type of connector that is being used, the different enum variants are meant to bue used with different cryptocurrency types and the generic type T is meant to be a specific struct that implements the BlockchainConnector trait
#[derive(Debug, Clone, Copy)]
pub enum ConnectorType<T>
where
    T: BlockchainConnector + Clone,
{
    /// BTC is a variant that represents a connector that is used to connect to a Bitcoin blockchain
    BTC(T),
    /// ETH is a variant that represents a connector that is used to connect to an Ethereum blockchain
    ETH(T),
}

/// BlockchainConnectorBuilder is a builder that can be used to build a BlockchainConnector with custom options
#[derive(Debug, Clone, Default)]
pub struct BlockchainConnectorBuilder<T>
where
    T: BlockchainConnector + Clone,
{
    url: Option<String>,
    connector_type: Option<ConnectorType<T>>,
}

impl<T> BlockchainConnectorBuilder<T>
where
    T: BlockchainConnector + BlockchainConnectorGeneral + Clone,
{
    /// new creates a new BlockchainConnectorBuilder with default options (no url and no connector type specified)
    pub fn new() -> Self {
        Self {
            url: None,
            connector_type: None,
        }
    }

    /// This function sets the url of the BlockchainConnectorBuilder
    pub fn set_url(&mut self, url: String) -> Self {
        self.url = Some(url);
        self.clone()
    }

    /// This function sets the connector type of the BlockchainConnectorBuilder, this requires the associated BlockchainConnector struct to be fully defined with data
    pub fn set_connector(&mut self, connector_type: ConnectorType<T>) -> Self {
        self.connector_type = Some(connector_type);
        self.clone()
    }

    /// This function builds the BlockchainConnectorBuilder using the options provided in the builder
    ///
    /// It returns a [Box< dyn BlockchainConnectorGeneral >] that can be used to connect to a blockchain.
    /// The result of that build later can be downcasted to a specific [BlockchainConnector] struct - any compatible struct that implements the [BlockchainConnector] trait.
    pub fn build(&mut self) -> Result<Box<dyn BlockchainConnectorGeneral>, Error> {
        match &self.connector_type {
            Some(ConnectorType::BTC(connector) | ConnectorType::ETH(connector)) => {
                Ok(connector.box_clone())
            }
            None => match self.url {
                Some(ref url) => {
                    let client = T::new(url)
                        .map_err(|e| Error::BlockchainConnectorBuilder(e.to_string()))?;
                    let client_gen = client.box_clone();
                    Ok(client_gen)
                }
                None => Err(Error::BlockchainConnectorBuilder("url not set".into())),
            },
        }
    }
}