substrate_stellar_sdk/horizon/
fetch.rs

1use core::num::{ParseFloatError, ParseIntError};
2use sp_io::offchain::timestamp;
3use sp_runtime::offchain::{
4    http::{Error, Method, Request},
5    Duration, HttpError,
6};
7use sp_std::{str, vec, vec::Vec};
8
9use core::convert::TryInto;
10
11use crate::{AccountId, IntoAccountId, StellarSdkError};
12
13use super::{
14    api_response_types::FeeStats, json_response_types, Horizon, HTTP_HEADER_CLIENT_NAME, HTTP_HEADER_CLIENT_VERSION,
15};
16
17impl From<ParseIntError> for FetchError {
18    fn from(error: ParseIntError) -> Self {
19        FetchError::ParseIntError(error)
20    }
21}
22
23impl From<ParseFloatError> for FetchError {
24    fn from(error: ParseFloatError) -> Self {
25        FetchError::ParseFloatError(error)
26    }
27}
28
29#[derive(Debug, Clone, Eq, PartialEq)]
30pub enum FetchError {
31    DeadlineReached,
32    IoError,
33    Invalid,
34    Unknown,
35    UnexpectedResponseStatus { status: u16, body: Vec<u8> },
36    JsonParseError,
37    InvalidSequenceNumber,
38    ParseIntError(ParseIntError),
39    ParseFloatError(ParseFloatError),
40    AccountRequiredMemo(AccountId),
41}
42
43impl From<Error> for FetchError {
44    fn from(error: Error) -> Self {
45        match error {
46            Error::DeadlineReached => FetchError::DeadlineReached,
47            Error::IoError => FetchError::IoError,
48            Error::Unknown => FetchError::Unknown,
49        }
50    }
51}
52
53impl From<FetchError> for StellarSdkError {
54    fn from(error: FetchError) -> Self {
55        StellarSdkError::FetchError(error)
56    }
57}
58
59impl From<HttpError> for FetchError {
60    fn from(error: HttpError) -> Self {
61        match error {
62            HttpError::DeadlineReached => FetchError::DeadlineReached,
63            HttpError::IoError => FetchError::IoError,
64            HttpError::Invalid => FetchError::Invalid,
65        }
66    }
67}
68
69impl From<serde_json::Error> for FetchError {
70    fn from(_error: serde_json::Error) -> Self {
71        FetchError::JsonParseError
72    }
73}
74
75impl Horizon {
76    pub fn request(&self, path: Vec<&[u8]>, method: Method, timeout_milliseconds: u64) -> Result<Vec<u8>, FetchError> {
77        let mut url = self.base_url.clone();
78        for path_segment in path {
79            url.extend_from_slice(path_segment);
80        }
81
82        let request = Request::<Vec<&'static [u8]>>::new(str::from_utf8(&url).unwrap()).method(method);
83        let timeout = timestamp().add(Duration::from_millis(timeout_milliseconds));
84        let pending = request
85            .add_header("X-Client-Name", HTTP_HEADER_CLIENT_NAME)
86            .add_header("X-Client-Version", HTTP_HEADER_CLIENT_VERSION)
87            .deadline(timeout)
88            .send()?;
89
90        let response = pending.try_wait(timeout).map_err(|_| FetchError::DeadlineReached)?;
91        let response = response?;
92
93        if response.code != 200 {
94            return Err(FetchError::UnexpectedResponseStatus { status: response.code, body: response.body().collect() })
95        }
96
97        Ok(response.body().collect())
98    }
99
100    /// Fetch the sequence number of an account
101    ///
102    /// The sequence number is defined to be of type [i64](https://github.com/stellar/stellar-core/blob/master/src/xdr/Stellar-ledger-entries.x)
103    pub fn fetch_fee_stats(&self, timeout_milliseconds: u64) -> Result<FeeStats, FetchError> {
104        let json = self.request(vec![b"/fee_stats"], Method::Get, timeout_milliseconds)?;
105
106        let response: json_response_types::FeeStats = serde_json::from_slice(&json)?;
107
108        response.try_into()
109    }
110
111    /// Fetch the sequence number of an account
112    ///
113    /// The sequence number is defined to be of type [i64](https://github.com/stellar/stellar-core/blob/master/src/xdr/Stellar-ledger-entries.x)
114    pub fn fetch_account<T: IntoAccountId>(
115        &self,
116        account_id: T,
117        timeout_milliseconds: u64,
118    ) -> Result<json_response_types::AccountResponse, FetchError> {
119        let json = self.request(
120            vec![b"/accounts/", account_id.into_encoding().as_slice()],
121            Method::Get,
122            timeout_milliseconds,
123        )?;
124
125        let account_response: json_response_types::AccountResponse = serde_json::from_slice(&json)?;
126
127        Ok(account_response)
128    }
129
130    /// Fetch the sequence number of an account
131    ///
132    /// The sequence number is defined to be of type [i64](https://github.com/stellar/stellar-core/blob/master/src/xdr/Stellar-ledger-entries.x)
133    pub fn fetch_next_sequence_number<T: IntoAccountId>(
134        &self,
135        account_id: T,
136        timeout_milliseconds: u64,
137    ) -> Result<i64, FetchError> {
138        let account_response = self.fetch_account(account_id, timeout_milliseconds)?;
139
140        let sequence_number: i64 = match account_response.sequence.parse() {
141            Ok(n) => n,
142            Err(_) => return Err(FetchError::InvalidSequenceNumber),
143        };
144        let next_sequence_number = sequence_number + 1;
145        Ok(next_sequence_number)
146    }
147}