lnurlkit/
server.rs

1use axum::{
2    extract::{Path, RawQuery},
3    http::StatusCode,
4    routing::get,
5    Router,
6};
7use std::future::Future;
8
9pub struct Server<AR, CE, CC, PE, PC, WE, WC> {
10    auth_request: AR,
11    channel_entrypoint: CE,
12    channel_callback: CC,
13    pay_entrypoint: PE,
14    pay_callback: PC,
15    withdraw_entrypoint: WE,
16    withdraw_callback: WC,
17}
18
19impl Default
20    for Server<
21        // Auth
22        unimplemented::Handler<crate::auth::server::Callback, crate::CallbackResponse>,
23        // Channel Request
24        unimplemented::Handler<(), crate::channel::server::Entrypoint>,
25        unimplemented::Handler<crate::channel::server::Callback, crate::CallbackResponse>,
26        // Pay Request
27        unimplemented::Handler<Option<String>, crate::pay::server::Entrypoint>,
28        unimplemented::Handler<crate::pay::server::Callback, crate::pay::server::CallbackResponse>,
29        // Withdraw Request
30        unimplemented::Handler<(), crate::withdraw::server::Entrypoint>,
31        unimplemented::Handler<crate::withdraw::server::Callback, crate::CallbackResponse>,
32    >
33{
34    fn default() -> Self {
35        Server {
36            auth_request: unimplemented::handler,
37
38            channel_entrypoint: unimplemented::handler,
39            channel_callback: unimplemented::handler,
40
41            pay_entrypoint: unimplemented::handler,
42            pay_callback: unimplemented::handler,
43
44            withdraw_entrypoint: unimplemented::handler,
45            withdraw_callback: unimplemented::handler,
46        }
47    }
48}
49
50impl<AR, CE, CC, PE, PC, WE, WC> Server<AR, CE, CC, PE, PC, WE, WC> {
51    pub fn auth<AR2>(self, auth_request: AR2) -> Server<AR2, CE, CC, PE, PC, WE, WC> {
52        Server {
53            auth_request,
54            channel_entrypoint: self.channel_entrypoint,
55            channel_callback: self.channel_callback,
56            pay_entrypoint: self.pay_entrypoint,
57            pay_callback: self.pay_callback,
58            withdraw_entrypoint: self.withdraw_entrypoint,
59            withdraw_callback: self.withdraw_callback,
60        }
61    }
62
63    pub fn channel_request<CE2, CC2>(
64        self,
65        channel_entrypoint: CE2,
66        channel_callback: CC2,
67    ) -> Server<AR, CE2, CC2, PE, PC, WE, WC> {
68        Server {
69            auth_request: self.auth_request,
70            channel_entrypoint,
71            channel_callback,
72            pay_entrypoint: self.pay_entrypoint,
73            pay_callback: self.pay_callback,
74            withdraw_entrypoint: self.withdraw_entrypoint,
75            withdraw_callback: self.withdraw_callback,
76        }
77    }
78
79    pub fn pay_request<PE2, PC2>(
80        self,
81        pay_entrypoint: PE2,
82        pay_callback: PC2,
83    ) -> Server<AR, CE, CC, PE2, PC2, WE, WC> {
84        Server {
85            auth_request: self.auth_request,
86            channel_entrypoint: self.channel_entrypoint,
87            channel_callback: self.channel_callback,
88            pay_entrypoint,
89            pay_callback,
90            withdraw_entrypoint: self.withdraw_entrypoint,
91            withdraw_callback: self.withdraw_callback,
92        }
93    }
94
95    pub fn withdraw_request<WE2, WC2>(
96        self,
97        withdraw_entrypoint: WE2,
98        withdraw_callback: WC2,
99    ) -> Server<AR, CE, CC, PE, PC, WE2, WC2> {
100        Server {
101            auth_request: self.auth_request,
102            channel_entrypoint: self.channel_entrypoint,
103            channel_callback: self.channel_callback,
104            pay_entrypoint: self.pay_entrypoint,
105            pay_callback: self.pay_callback,
106            withdraw_entrypoint,
107            withdraw_callback,
108        }
109    }
110}
111
112impl<AR, ARFut, CE, CQFut, CC, CCFut, PE, PEFut, PC, PCFut, WE, WEFut, WC, WCFut>
113    Server<AR, CE, CC, PE, PC, WE, WC>
114where
115    AR: 'static + Send + Clone + Fn(crate::auth::server::Callback) -> ARFut,
116    ARFut: Send + Future<Output = Result<crate::CallbackResponse, StatusCode>>,
117
118    CE: 'static + Send + Clone + Fn(()) -> CQFut,
119    CQFut: Send + Future<Output = Result<crate::channel::server::Entrypoint, StatusCode>>,
120
121    CC: 'static + Send + Clone + Fn(crate::channel::server::Callback) -> CCFut,
122    CCFut: Send + Future<Output = Result<crate::CallbackResponse, StatusCode>>,
123
124    PE: 'static + Send + Clone + Fn(Option<String>) -> PEFut,
125    PEFut: Send + Future<Output = Result<crate::pay::server::Entrypoint, StatusCode>>,
126
127    PC: 'static + Send + Clone + Fn(crate::pay::server::Callback) -> PCFut,
128    PCFut: Send + Future<Output = Result<crate::pay::server::CallbackResponse, StatusCode>>,
129
130    WE: 'static + Send + Clone + Fn(()) -> WEFut,
131    WEFut: Send + Future<Output = Result<crate::withdraw::server::Entrypoint, StatusCode>>,
132
133    WC: 'static + Send + Clone + Fn(crate::withdraw::server::Callback) -> WCFut,
134    WCFut: Send + Future<Output = Result<crate::CallbackResponse, StatusCode>>,
135{
136    #[allow(clippy::too_many_lines)]
137    pub fn build(self) -> Router<()> {
138        Router::new()
139            .route(
140                "/keyauth",
141                get(move |RawQuery(q): RawQuery| {
142                    let ar = self.auth_request.clone();
143                    async move {
144                        let q = q.ok_or(StatusCode::BAD_REQUEST)?;
145                        let p = q.as_str().try_into().map_err(|_| StatusCode::BAD_REQUEST)?;
146                        ar(p).await.and_then(|a| {
147                            Vec::<u8>::try_from(a).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
148                        })
149                    }
150                }),
151            )
152            .route(
153                "/lnurlc",
154                get(move || {
155                    let ce = self.channel_entrypoint.clone();
156                    async move {
157                        ce(()).await.and_then(|a| {
158                            Vec::<u8>::try_from(a).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
159                        })
160                    }
161                }),
162            )
163            .route(
164                "/lnurlc/callback",
165                get(move |RawQuery(q): RawQuery| {
166                    let cc = self.channel_callback.clone();
167                    async move {
168                        let q = q.ok_or(StatusCode::BAD_REQUEST)?;
169                        let p = q.as_str().try_into().map_err(|_| StatusCode::BAD_REQUEST)?;
170                        cc(p).await.and_then(|a| {
171                            Vec::<u8>::try_from(a).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
172                        })
173                    }
174                }),
175            )
176            .route(
177                "/.well-known/lnurlp/:identifier",
178                get({
179                    let pe = self.pay_entrypoint.clone();
180                    move |Path(identifier): Path<String>| {
181                        let pe = pe.clone();
182                        async move {
183                            pe(Some(identifier)).await.and_then(|a| {
184                                Vec::<u8>::try_from(a)
185                                    .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
186                            })
187                        }
188                    }
189                }),
190            )
191            .route(
192                "/lnurlp",
193                get(move || {
194                    let pe = self.pay_entrypoint.clone();
195                    async move {
196                        pe(None).await.and_then(|a| {
197                            Vec::<u8>::try_from(a).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
198                        })
199                    }
200                }),
201            )
202            .route(
203                "/lnurlp/callback",
204                get(move |RawQuery(q): RawQuery| {
205                    let pc = self.pay_callback.clone();
206                    async move {
207                        let q = q.ok_or(StatusCode::BAD_REQUEST)?;
208                        let p = q.as_str().try_into().map_err(|_| StatusCode::BAD_REQUEST)?;
209                        pc(p).await.map(|a| a.to_string())
210                    }
211                }),
212            )
213            .route(
214                "/lnurlw",
215                get(move || {
216                    let we = self.withdraw_entrypoint.clone();
217                    async move {
218                        we(()).await.and_then(|a| {
219                            Vec::<u8>::try_from(a).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
220                        })
221                    }
222                }),
223            )
224            .route(
225                "/lnurlw/callback",
226                get(move |RawQuery(q): RawQuery| {
227                    let wc = self.withdraw_callback.clone();
228                    async move {
229                        let q = q.ok_or(StatusCode::BAD_REQUEST)?;
230                        let p = q.as_str().try_into().map_err(|_| StatusCode::BAD_REQUEST)?;
231                        wc(p).await.and_then(|a| {
232                            Vec::<u8>::try_from(a).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
233                        })
234                    }
235                }),
236            )
237    }
238}
239
240mod unimplemented {
241    use axum::http::StatusCode;
242    use std::{
243        future::Future,
244        marker::PhantomData,
245        pin::Pin,
246        task::{Context, Poll},
247    };
248
249    pub(super) type Handler<Param, Ret> = fn(Param) -> Unimplemented<Ret>;
250    pub(super) fn handler<Param, Ret>(_: Param) -> Unimplemented<Ret> {
251        Unimplemented(PhantomData)
252    }
253
254    pub struct Unimplemented<T>(PhantomData<T>);
255
256    impl<T> Future for Unimplemented<T> {
257        type Output = Result<T, StatusCode>;
258
259        fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<T, StatusCode>> {
260            Poll::Ready(Err(StatusCode::NOT_IMPLEMENTED))
261        }
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    #[test]
268    fn default_builds() {
269        drop(super::Server::default().build());
270    }
271}