substrate_stellar_sdk/horizon/
fetch.rs1use 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 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 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 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}