Skip to content

Commit

Permalink
feat(withdraw): add client/server and a test for withdraw (lud03)
Browse files Browse the repository at this point in the history
  • Loading branch information
lsunsi committed Dec 4, 2023
1 parent a8017f9 commit 6cbaff3
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 66 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ perf = "deny"
style = "deny"
suspicious = "deny"

[[test]]
name = "lud03"
required-features = ["client", "server"]

[[test]]
name = "lud06"
required-features = ["client", "server"]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This library works as a toolkit so you can serve and make your LNURL requests wi

- [LUD-01](https://github.com/lnurl/luds/blob/luds/01.md): ✅ core ✅ client ✅ server ⚠️ tests
- [LUD-02](https://github.com/lnurl/luds/blob/luds/02.md): ✅ core ⚠️ client 🆘 server 🆘 tests
- [LUD-03](https://github.com/lnurl/luds/blob/luds/03.md): ✅ core ⚠️ client 🆘 server 🆘 tests
- [LUD-03](https://github.com/lnurl/luds/blob/luds/03.md): ✅ core client server ⚠️ tests
- [LUD-04](https://github.com/lnurl/luds/blob/luds/04.md): 🆘 core 🆘 client 🆘 server 🆘 tests
- [LUD-05](https://github.com/lnurl/luds/blob/luds/05.md): 🆘 core 🆘 client 🆘 server 🆘 tests
- [LUD-06](https://github.com/lnurl/luds/blob/luds/06.md): ✅ core ✅ client ✅ server ✅ tests
Expand Down
82 changes: 43 additions & 39 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::core;

#[derive(Clone, Default)]
pub struct Client(reqwest::Client);

Expand All @@ -6,83 +8,72 @@ impl Client {
///
/// Returns errors on network or deserialization failures.
pub async fn query(&self, s: &str) -> Result<Query, &'static str> {
let url = crate::core::resolve(s)?;
let url = core::resolve(s)?;

let client = &self.0;
let response = client.get(url).send().await.map_err(|_| "request failed")?;
let text = response.text().await.map_err(|_| "body failed")?;

text.parse::<crate::core::Query>()
text.parse::<core::Query>()
.map_err(|_| "parse failed")
.map(|query| match query {
crate::core::Query::PayRequest(core) => {
Query::PayRequest(PayRequest { client, core })
}
crate::core::Query::ChannelRequest(core) => {
core::Query::ChannelRequest(core) => {
Query::ChannelRequest(ChannelRequest { client, core })
}
crate::core::Query::WithdrawRequest(core) => {
core::Query::WithdrawRequest(core) => {
Query::WithdrawRequest(WithdrawRequest { client, core })
}
core::Query::PayRequest(core) => Query::PayRequest(PayRequest { client, core }),
})
}
}

#[derive(Clone, Debug)]
pub enum Query<'a> {
PayRequest(PayRequest<'a>),
ChannelRequest(ChannelRequest<'a>),
WithdrawRequest(WithdrawRequest<'a>),
PayRequest(PayRequest<'a>),
}

#[derive(Clone, Debug)]
pub struct PayRequest<'a> {
pub struct ChannelRequest<'a> {
client: &'a reqwest::Client,
pub core: crate::core::pay_request::PayRequest,
pub core: core::channel_request::ChannelRequest,
}

#[derive(Clone, Debug)]
pub struct ChannelRequest<'a> {
pub struct WithdrawRequest<'a> {
client: &'a reqwest::Client,
pub core: crate::core::channel_request::ChannelRequest,
pub core: core::withdraw_request::WithdrawRequest,
}

#[derive(Clone, Debug)]
pub struct WithdrawRequest<'a> {
pub struct PayRequest<'a> {
client: &'a reqwest::Client,
pub core: crate::core::withdraw_request::WithdrawRequest,
pub core: core::pay_request::PayRequest,
}

impl PayRequest<'_> {
impl ChannelRequest<'_> {
/// # Errors
///
/// Returns errors on network or deserialization failures.
pub async fn callback(
self,
comment: &str,
millisatoshis: u64,
) -> Result<crate::core::pay_request::CallbackResponse, &'static str> {
let callback = self.core.callback(comment, millisatoshis);
pub async fn callback_accept(self, remoteid: &str, private: bool) -> Result<(), &'static str> {
let callback = self.core.callback_accept(remoteid, private);

let response = self
.client
self.client
.get(callback)
.send()
.await
.map_err(|_| "request failed")?;

let text = response.text().await.map_err(|_| "body failed")?;

text.parse().map_err(|_| "parse failed")
Ok(())
}
}

impl ChannelRequest<'_> {
/// # Errors
///
/// Returns errors on network or deserialization failures.
pub async fn callback_accept(self, remoteid: &str, private: bool) -> Result<(), &'static str> {
let callback = self.core.callback_accept(remoteid, private);
pub async fn callback_cancel(self, remoteid: &str) -> Result<(), &'static str> {
let callback = self.core.callback_cancel(remoteid);

self.client
.get(callback)
Expand All @@ -92,36 +83,49 @@ impl ChannelRequest<'_> {

Ok(())
}
}

impl WithdrawRequest<'_> {
/// # Errors
///
/// Returns errors on network or deserialization failures.
pub async fn callback_cancel(self, remoteid: &str) -> Result<(), &'static str> {
let callback = self.core.callback_cancel(remoteid);
pub async fn callback(
self,
pr: &str,
) -> Result<core::withdraw_request::CallbackResponse, &'static str> {
let callback = self.core.callback(pr);

self.client
let response = self
.client
.get(callback)
.send()
.await
.map_err(|_| "request failed")?;

Ok(())
let text = response.text().await.map_err(|_| "body failed")?;
text.parse().map_err(|_| "parse failed")
}
}

impl WithdrawRequest<'_> {
impl PayRequest<'_> {
/// # Errors
///
/// Returns errors on network or deserialization failures.
pub async fn callback(self, pr: &str) -> Result<(), &'static str> {
let callback = self.core.callback(pr);
pub async fn callback(
self,
comment: &str,
millisatoshis: u64,
) -> Result<core::pay_request::CallbackResponse, &'static str> {
let callback = self.core.callback(comment, millisatoshis);

self.client
let response = self
.client
.get(callback)
.send()
.await
.map_err(|_| "request failed")?;

Ok(())
let text = response.text().await.map_err(|_| "body failed")?;
text.parse().map_err(|_| "parse failed")
}
}
80 changes: 76 additions & 4 deletions src/core/withdraw_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ pub const TAG: &str = "withdrawRequest";

#[derive(Clone, Debug)]
pub struct WithdrawRequest {
k1: String,
callback: url::Url,
pub k1: String,
pub callback: url::Url,
pub description: String,
pub min: u64,
pub max: u64,
Expand All @@ -13,7 +13,7 @@ impl std::str::FromStr for WithdrawRequest {
type Err = &'static str;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let d: serde::QueryResponse =
let d: de::QueryResponse =
miniserde::json::from_str(s).map_err(|_| "deserialize failed")?;

Ok(WithdrawRequest {
Expand All @@ -26,6 +26,19 @@ impl std::str::FromStr for WithdrawRequest {
}
}

impl std::fmt::Display for WithdrawRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&miniserde::json::to_string(&ser::QueryResponse {
tag: TAG,
callback: crate::serde::Url(self.callback.clone()),
default_description: &self.description,
min_withdrawable: self.min,
max_withdrawable: self.max,
k1: &self.k1,
}))
}
}

impl WithdrawRequest {
/// # Errors
///
Expand All @@ -40,7 +53,66 @@ impl WithdrawRequest {
}
}

mod serde {
#[derive(Debug)]
pub enum CallbackResponse {
Error(String),
Ok,
}

impl std::str::FromStr for CallbackResponse {
type Err = &'static str;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let map = miniserde::json::from_str::<std::collections::BTreeMap<String, String>>(s)
.map_err(|_| "bad json")?;

match map.get("status").map(|s| s as &str) {
Some("OK") => Ok(CallbackResponse::Ok),
Some("ERROR") => Ok(CallbackResponse::Error(
map.get("reason").cloned().unwrap_or_default(),
)),
_ => Err("bad status field"),
}
}
}

impl std::fmt::Display for CallbackResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut map = std::collections::BTreeMap::new();

match self {
CallbackResponse::Error(reason) => {
map.insert("status", "ERROR");
map.insert("reason", reason);
}
CallbackResponse::Ok => {
map.insert("status", "OK");
}
}

f.write_str(&miniserde::json::to_string(&map))
}
}

mod ser {
use crate::serde::Url;
use miniserde::Serialize;

#[derive(Serialize)]
pub(super) struct QueryResponse<'a> {
pub tag: &'static str,
pub k1: &'a str,
pub callback: Url,
#[serde(rename = "defaultDescription")]
pub default_description: &'a str,
#[serde(rename = "minWithdrawable")]
pub min_withdrawable: u64,
#[serde(rename = "maxWithdrawable")]
pub max_withdrawable: u64,
}
}

mod de {
use crate::serde::Url;
use miniserde::Deserialize;

Expand Down
Loading

0 comments on commit 6cbaff3

Please sign in to comment.