bunq-rs/src/lib.rs
Jacob Kiers c3fb162732 Update all dependencies to the latest versions
Signed-off-by: Jacob Kiers <code@kiers.eu>
2024-03-01 07:09:28 +01:00

104 lines
2.8 KiB
Rust

use anyhow::Result;
use rsa::pkcs1v15::SigningKey;
use rsa::RsaPrivateKey;
use rsa::sha2::Sha256;
use rsa::signature::RandomizedSigner;
use serde::{de::DeserializeOwned, Deserialize};
pub use config::BunqConfig;
pub use monetary_account::MonetaryAccount;
pub use payment::Payment;
mod config;
mod monetary_account;
mod payment;
const BASE_URL: &str = "https://api.bunq.com";
pub struct BunqClient {
token: String,
#[allow(dead_code)] // Required for signing bodies. Not used yet.
keypair: RsaPrivateKey,
user_id: i64,
}
impl BunqClient {
pub fn monetary_accounts(&self) -> Result<Vec<MonetaryAccount>> {
monetary_account::get(self)
}
pub fn payments(&self, acc: &MonetaryAccount) -> Result<Vec<Payment>> {
self.payments_from_to(acc, None, None)
}
pub fn payments_from_to(
&self,
acc: &MonetaryAccount,
from: Option<i64>,
to: Option<i64>,
) -> anyhow::Result<Vec<Payment>> {
payment::get_from_to(self, acc, from, to)
}
}
#[derive(Deserialize, Debug)]
struct Pagination {
#[allow(dead_code)] // Used for refresh. Not implemented yet.
future_url: Option<String>,
#[allow(dead_code)] // Used for finding newer items. Not necessary yet.
newer_url: Option<String>,
older_url: Option<String>,
}
struct Response<T> {
response: T,
pagination: Option<Pagination>,
}
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
struct RawResponse {
response: Vec<serde_json::Value>,
pagination: Option<Pagination>,
}
impl RawResponse {
fn decode_retarded<T: DeserializeOwned>(self) -> Result<Response<T>> {
let mut map = serde_json::Map::new();
for e in self.response {
if let serde_json::Value::Object(e) = e {
let (k, v) = e
.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("malformed response"))?;
map.insert(k, v);
}
}
Ok(Response {
response: serde_json::from_value(map.into())?,
pagination: self.pagination,
})
}
}
fn deserialize_retarded_response<T: DeserializeOwned>(r: &str) -> Result<Response<T>> {
let r: RawResponse = serde_json::from_str(r)?;
r.decode_retarded()
}
fn deserialize_normal_response<T: DeserializeOwned>(r: &str) -> Result<Response<T>> {
let r: RawResponse = serde_json::from_str(r)?;
Ok(Response {
response: serde_json::from_value(r.response.into())?,
pagination: r.pagination,
})
}
fn sign(body: &str, key: &RsaPrivateKey) -> Result<String> {
use base64::prelude::{BASE64_STANDARD, Engine};
let signing_key = SigningKey::<Sha256>::new(key.clone());
let mut rng = rand::thread_rng();
let signature = signing_key.sign_with_rng(&mut rng, body.as_bytes());
Ok(BASE64_STANDARD.encode(signature.to_string()))
}