I want to create a struct of which has four methods: new(), handle_interaction(), add_shortcut(), async start_server()
The struct should contain a web server, which is started using the async start_server()
, a list of available shortcuts (callback-closures), added by the user.
When the server is started, i want to have a single endpoint (/
) which then will recieve JSON-data. When the json data is recieved, the server will determine which interaction is incoming, and start the interaction-closure concurrently.
I've tried a lot of different solutions, and i must say that a lot of them incorporate rust-types and concepts that i'm not familiar with just yet.
The code i've landed on right now, which still fails however, is this: (I've highlighted the errors with comments, just so you know what fails)
use axum::{
routing::{get, post},
http::StatusCode,
Json, Router,
};
use std::{net::SocketAddr, future::Future, pin::Pin, collections::HashMap};
use tracing::{debug, info, error};
use futures::{FutureExt, future::BoxFuture};
pub struct Interaction {
r#type: String,
}
#[derive(Default)]
pub struct App {
router: Router,
shortcuts: HashMap<String, Box<dyn Fn(Interaction) -> BoxFuture<'static, ()>>>
}
impl App {
pub fn new() -> Self {
Self::default()
}
async fn handle_interaction(
&self,
Json(interaction): Json<Interaction>
) -> StatusCode {
let t = interaction.r#type.clone();
debug!("Got shortcut");
debug!("{t}");
let closure = self.shortcuts.get(&t).unwrap();
tokio::task::spawn(closure(interaction));
StatusCode::OK
}
pub fn add_shortcut<C, Fut>(
mut self,
callback_id: &str,
fun: C,
) -> Self
where
Fut: Future<Output = ()> + Send + 'static,
C: Fn(Interaction) -> Pin<Box<Fut>>,
{
self.shortcuts.insert(callback_id.to_string(), Box::new(move |interaction| Box::pin(fun(interaction))));
self
}
async fn start(self, addr: SocketAddr) {
let interaction_handler = move |interaction| async move {
self.handle_interaction(interaction).await
};
// ERROR: `interaction_handler` doesn't match axums route-types... Don't know why this is the case either.
let router = Router::new()
.route("/", post(interaction_handler));
debug!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(self.router.into_make_service())
.await
.unwrap();
}
}
async fn shortcut1(i: Interaction) {
println!("Hello, World!");
}
async fn shortcut2(i: Interaction) {
println!("Hello, Other World!")
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
// ERROR: `shortcut1` and `shortcut2` doesn't match input type,
// and i don't know how to do this properly.
let app = App::new()
.add_shortcut("shortcut1", shortcut1)
.add_shortcut("shortcut2", shortcut2);
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
app.start(addr).await;
}
A lot of this is the result of trail and error, chatgpt and random docs - I'm not familiar with box, pin and alike.
I've also read Mutex or Arc could maybe help solve the issue, but i can't get that to work either.
I hope someone here can help point me in the right direction!