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    addr: String,
14    hooks: Vec<Entry>,
15}
16
17#[derive(Debug, Deserialize, Serialize)]
18pub struct Entry {
19    path: String,
20    command: String,
21    args: Vec<String>,
22}
23
24pub async fn run_server(cfg: Config) {
25    let listener = TcpListener::bind(cfg.addr.clone()).await.unwrap();
26    let state = Arc::new(cfg);
27    let app: Router = Router::new()
28        .route("/{*key}", get(handle_url))
29        .with_state(state);
30    axum::serve(listener, app).await.unwrap();
31}
32
33#[derive(Debug, Deserialize, Serialize)]
34#[serde(rename_all = "snake_case")]
35enum Response {
36    Success,
37    Failed,
38}
39
40#[axum::debug_handler]
41async fn handle_url(Path(key): Path<String>, State(cfg): State<Arc<Config>>) -> Json<Response> {
42    if let Some(entry) = cfg.hooks.iter().find(|it| it.path == key) {
43        println!("Running entry {}", key);
44        let mut command = Command::new(&entry.command);
45        command.args(entry.args.iter());
46        let res = command.status();
47
48        match res {
49            Ok(o) if o.success() => Json(Response::Success),
50            _ => Json(Response::Failed),
51        }
52    } else {
53        Json(Response::Failed)
54    }
55}