webhook_catcher/
lib.rs

1use std::{process::Command, sync::Arc};
2
3use axum::{
4    Json, Router,
5    extract::{Path, State},
6    routing::get,
7};
8use serde::{Deserialize, Serialize};
9use tokio::net::TcpListener;
10
11#[derive(Debug, Deserialize, Serialize)]
12pub struct Config {
13    #[serde(default = "default_webhook_listen_address")]
14    addr: String,
15    hooks: Vec<Entry>,
16}
17
18fn default_webhook_listen_address() -> String {
19    format!("0.0.0.0:1234")
20}
21
22#[derive(Debug, Deserialize, Serialize)]
23pub struct Entry {
24    path: String,
25    command: String,
26    args: Vec<String>,
27}
28
29pub async fn run_server(cfg: Config) {
30    let listener = TcpListener::bind(cfg.addr.clone()).await.unwrap();
31    let state = Arc::new(cfg);
32    let app: Router = Router::new()
33        .route("/{*key}", get(handle_url))
34        .with_state(state);
35    axum::serve(listener, app).await.unwrap();
36}
37
38#[derive(Debug, Deserialize, Serialize)]
39#[serde(rename_all = "snake_case")]
40enum Response {
41    Success,
42    Failed,
43}
44
45#[axum::debug_handler]
46async fn handle_url(Path(key): Path<String>, State(cfg): State<Arc<Config>>) -> Json<Response> {
47    if let Some(entry) = cfg.hooks.iter().find(|it| it.path == key) {
48        println!("Running entry {}", key);
49        let mut command = Command::new(&entry.command);
50        command.args(entry.args.iter());
51        let res = command.status();
52
53        match res {
54            Ok(o) if o.success() => Json(Response::Success),
55            _ => Json(Response::Failed),
56        }
57    } else {
58        Json(Response::Failed)
59    }
60}