-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
198 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
use crate::alby::helpers::{make_request, RequestArgs, RequestError}; | ||
|
||
/// Alby API functions related to the account. | ||
/// | ||
/// Read more at | ||
/// <https://guides.getalby.com/developer-guide/v/alby-wallet-api/reference/api-reference/account>. | ||
pub mod account { | ||
use super::*; | ||
|
||
/// Response body for a successful [get_balance] request. | ||
#[derive(Debug, serde::Deserialize)] | ||
pub struct GetBalanceResponse { | ||
/// The balance amount. | ||
pub balance: u64, | ||
/// The currency of the balance. | ||
pub currency: String, | ||
/// The unit of the balance (e.g., "sat" for satoshis). | ||
pub unit: String, | ||
} | ||
|
||
/// Arguments for [get_balance]. | ||
pub struct GetBalanceArgs<'a> { | ||
/// User agent string. | ||
pub user_agent: &'a str, | ||
/// Bearer token for authentication. | ||
pub token: &'a str, | ||
} | ||
|
||
/// Get Alby account balance. | ||
pub async fn get_balance(args: GetBalanceArgs<'_>) -> Result<GetBalanceResponse, RequestError> { | ||
let request_args = RequestArgs { | ||
user_agent: args.user_agent, | ||
method: reqwest::Method::GET, | ||
url: "https://api.getalby.com/balance", | ||
token: args.token, | ||
body: None, | ||
}; | ||
|
||
make_request(request_args).await | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
use serde::de::DeserializeOwned; | ||
use std::fmt; | ||
|
||
/// Alby error response from 400 and 500 status codes. | ||
#[derive(Debug, serde::Deserialize)] | ||
pub struct ErrorResponse { | ||
/// Alby error code. | ||
pub code: u32, | ||
/// Indicates if it is an error. | ||
pub error: bool, | ||
/// Alby error message. | ||
pub message: String, | ||
} | ||
|
||
/// Alby API request error. | ||
#[derive(Debug)] | ||
pub enum RequestError { | ||
/// Failed to create auth header. | ||
AuthHeaderCreation(reqwest::header::InvalidHeaderValue), | ||
/// Failed to create reqwest client. | ||
ClientCreation(reqwest::Error), | ||
/// Failed to send request. | ||
RequestSend(reqwest::Error), | ||
/// Failed to read response body. | ||
ResponseBodyRead(reqwest::Error), | ||
/// Failed to parse response body. | ||
ResponseParse(serde_json::Error), | ||
/// Bad request (400). | ||
BadRequest(ErrorResponse), | ||
/// Internal server error (500). | ||
InternalServerError(ErrorResponse), | ||
/// Unexpected status code. | ||
UnexpectedStatus { | ||
/// Status code. | ||
status: reqwest::StatusCode, | ||
/// Response body. | ||
body: String, | ||
}, | ||
} | ||
|
||
impl fmt::Display for RequestError { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
match self { | ||
RequestError::AuthHeaderCreation(e) => write!(f, "Failed to create auth header: {}", e), | ||
RequestError::ClientCreation(e) => write!(f, "Failed to create reqwest client: {}", e), | ||
RequestError::RequestSend(e) => write!(f, "Failed to send request: {}", e), | ||
RequestError::ResponseBodyRead(e) => write!(f, "Failed to read response body: {}", e), | ||
RequestError::ResponseParse(e) => write!(f, "Failed to parse response body: {}", e), | ||
RequestError::BadRequest(e) => { | ||
write!(f, "Bad request (400): {} (code: {})", e.message, e.code) | ||
} | ||
RequestError::InternalServerError(e) => write!( | ||
f, | ||
"Internal server error (500): {} (code: {})", | ||
e.message, e.code | ||
), | ||
RequestError::UnexpectedStatus { status, body } => { | ||
write!(f, "Unexpected status code: {}. Body: {}", status, body) | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl std::error::Error for RequestError { | ||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { | ||
match self { | ||
RequestError::AuthHeaderCreation(e) => Some(e), | ||
RequestError::ClientCreation(e) => Some(e), | ||
RequestError::RequestSend(e) => Some(e), | ||
RequestError::ResponseBodyRead(e) => Some(e), | ||
RequestError::ResponseParse(e) => Some(e), | ||
RequestError::BadRequest(_) | ||
| RequestError::InternalServerError(_) | ||
| RequestError::UnexpectedStatus { .. } => None, | ||
} | ||
} | ||
} | ||
|
||
impl From<reqwest::header::InvalidHeaderValue> for RequestError { | ||
fn from(error: reqwest::header::InvalidHeaderValue) -> Self { | ||
RequestError::AuthHeaderCreation(error) | ||
} | ||
} | ||
|
||
impl From<reqwest::Error> for RequestError { | ||
fn from(error: reqwest::Error) -> Self { | ||
if error.is_builder() { | ||
RequestError::ClientCreation(error) | ||
} else if error.is_request() { | ||
RequestError::RequestSend(error) | ||
} else { | ||
RequestError::ResponseBodyRead(error) | ||
} | ||
} | ||
} | ||
|
||
impl From<serde_json::Error> for RequestError { | ||
fn from(error: serde_json::Error) -> Self { | ||
RequestError::ResponseParse(error) | ||
} | ||
} | ||
|
||
/// Arguments for making a request. | ||
pub struct RequestArgs<'a> { | ||
/// User agent string. | ||
pub user_agent: &'a str, | ||
/// HTTP method. | ||
pub method: reqwest::Method, | ||
/// URL to send the request to. | ||
pub url: &'a str, | ||
/// Bearer token for authentication. | ||
pub token: &'a str, | ||
/// Optional request body. | ||
pub body: Option<&'a str>, | ||
} | ||
|
||
pub async fn make_request<T: DeserializeOwned>(args: RequestArgs<'_>) -> Result<T, RequestError> { | ||
let mut headers = reqwest::header::HeaderMap::new(); | ||
headers.insert( | ||
reqwest::header::AUTHORIZATION, | ||
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", args.token))?, | ||
); | ||
|
||
let client = reqwest::Client::builder() | ||
.default_headers(headers) | ||
.user_agent(args.user_agent) | ||
.timeout(std::time::Duration::from_secs(10)) | ||
.build()?; | ||
|
||
let response = client | ||
.request(args.method, args.url) | ||
.body(args.body.unwrap_or_default().to_string()) | ||
.send() | ||
.await?; | ||
|
||
let status = response.status(); | ||
let body = response.text().await?; | ||
|
||
match status.as_u16() { | ||
200 => Ok(serde_json::from_str(&body)?), | ||
400 => { | ||
let error_response: ErrorResponse = serde_json::from_str(&body)?; | ||
Err(RequestError::BadRequest(error_response)) | ||
} | ||
500 => { | ||
let error_response: ErrorResponse = serde_json::from_str(&body)?; | ||
Err(RequestError::InternalServerError(error_response)) | ||
} | ||
_ => Err(RequestError::UnexpectedStatus { status, body }), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters