diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7f32fd5..3774d51 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,5 +17,5 @@ jobs: steps: - uses: actions/checkout@v3 - uses: mozilla-actions/sccache-action@v0.0.3 - - run: cargo clippy - - run: cargo test + - run: cargo clippy --all-features + - run: cargo test --all-features diff --git a/Cargo.toml b/Cargo.toml index 63a2505..5f6ec5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,12 @@ license = "GPL-3.0-only" readme = "README.md" [dependencies] -base64 = { version = "0.21.5", features = ["std"], default-features = false } +base64 = { version = "0.21.0", features = ["std"], default-features = false } bech32 = { version = "0.9.0", default-features = false } miniserde = { version = "0.1.0", default-features = false } url = { version = "2.5.0", default-features = false } +axum = { version = "0.7.0", default-features = false, optional = true } reqwest = { version = "0.11.0", default-features = false, optional = true } [dev-dependencies] @@ -26,7 +27,7 @@ tokio = { version = "1.0.0", features = ["rt", "macros"], default-features = fal [features] client = ["dep:reqwest"] -server = [] +server = ["dep:axum"] [lints.rust] warnings = "deny" diff --git a/src/core/pay_request.rs b/src/core/pay_request.rs index 8dc2c42..c40555e 100644 --- a/src/core/pay_request.rs +++ b/src/core/pay_request.rs @@ -26,7 +26,7 @@ impl std::str::FromStr for PayRequest { use base64::{prelude::BASE64_STANDARD, Engine}; use miniserde::json::Value; - let p: serde::QueryResponse = + let p: de::QueryResponse = miniserde::json::from_str(s).map_err(|_| "deserialize failed")?; let comment_size = p.comment_allowed.unwrap_or(0); @@ -91,6 +91,55 @@ impl std::str::FromStr for PayRequest { } } +impl std::fmt::Display for PayRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use base64::{prelude::BASE64_STANDARD, Engine}; + + let metadata = miniserde::json::to_string( + &[ + Some(("text/plain", self.short_description.clone())), + self.long_description + .as_ref() + .map(|s| ("text/long-desc", s.clone())), + self.jpeg + .as_ref() + .map(|s| ("image/jpeg;base64", BASE64_STANDARD.encode(s))), + self.png + .as_ref() + .map(|s| ("image/png;base64", BASE64_STANDARD.encode(s))), + ] + .into_iter() + .flatten() + .collect::>(), + ); + + let success_action = self.success_action.as_ref().map(|sa| { + let mut map = std::collections::BTreeMap::new(); + + match sa { + SuccessAction::Message(m) => { + map.insert("message", m.into()); + } + SuccessAction::Url(u, d) => { + map.insert("description", d.into()); + map.insert("url", u.to_string().into()); + } + } + + map + }); + + f.write_str(&miniserde::json::to_string(&ser::QueryResponse { + metadata, + callback: &self.callback, + min_sendable: self.min, + max_sendable: self.max, + comment_allowed: self.comment_size, + success_action, + })) + } +} + impl PayRequest { /// # Errors /// @@ -120,7 +169,7 @@ impl std::str::FromStr for CallbackResponse { type Err = &'static str; fn from_str(s: &str) -> Result { - let a: serde::CallbackResponse = + let a: de::CallbackResponse = miniserde::json::from_str(s).map_err(|_| "deserialize failed")?; Ok(Self { @@ -130,7 +179,27 @@ impl std::str::FromStr for CallbackResponse { } } -mod serde { +mod ser { + use crate::serde::Url; + use miniserde::Serialize; + use std::collections::BTreeMap; + + #[derive(Serialize)] + pub(super) struct QueryResponse<'a> { + pub metadata: String, + pub callback: &'a Url, + #[serde(rename = "minSendable")] + pub min_sendable: u64, + #[serde(rename = "maxSendable")] + pub max_sendable: u64, + #[serde(rename = "commentAllowed")] + pub comment_allowed: u64, + #[serde(rename = "successAction")] + pub success_action: Option>>, + } +} + +mod de { use crate::serde::Url; use miniserde::Deserialize; use std::collections::BTreeMap; diff --git a/src/serde.rs b/src/serde.rs index 9967709..ab6f06c 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,4 +1,9 @@ -use miniserde::{de::Visitor, make_place, Deserialize, Error, Result}; +use miniserde::{ + de::Visitor, + make_place, + ser::{Fragment, Serialize}, + Deserialize, Error, Result, +}; make_place!(Place); @@ -18,3 +23,9 @@ impl Deserialize for Url { Place::new(out) } } + +impl Serialize for Url { + fn begin(&self) -> Fragment { + Fragment::Str(self.0.to_string().into()) + } +} diff --git a/src/server.rs b/src/server.rs index e69de29..d1f9605 100644 --- a/src/server.rs +++ b/src/server.rs @@ -0,0 +1,28 @@ +use axum::{http::StatusCode, routing::get, Router}; +use std::future::Future; + +pub struct Server

{ + pub pay_request: Option

, +} + +impl Server

+where + P: 'static + Send + Clone + Fn() -> PFut, + PFut: Send + Future>, +{ + pub fn build(self) -> Router<()> { + let mut router = Router::new(); + + if let Some(p) = self.pay_request { + router = router.route( + "/lnurlp", + get(move || { + let p = p.clone(); + async move { p().await.map(|a| a.to_string()) } + }), + ); + } + + router + } +}