This commit is contained in:
Michael Zhang 2020-11-02 19:56:57 -06:00
parent 9ae78d506e
commit 6b02463308
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
11 changed files with 725 additions and 872 deletions

1131
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,28 +1,33 @@
[package] [package]
name = "dip" name = "dip"
description = "Configurable webhook server." description = "Configurable webhook server."
version = "0.1.4" version = "0.2.0"
authors = ["Michael Zhang <failed.down@gmail.com>"] authors = ["Michael Zhang <mail@mzhang.io>"]
edition = "2018"
[features]
github = []
default = ["github"]
[dependencies] [dependencies]
failure = "0.1" anyhow = "1.0.34"
futures = "0.1" futures = "0.3.7"
generic-array = "0.9" generic-array = "0.14.4"
hmac = "0.6" hmac = "0.10.1"
hyper = "0.12" hyper = { version = "0.13", features = ["stream"] }
mktemp = "0.3" lazy_static = "1.4.0"
lazy_static = "1.1" mktemp = "0.4.0"
notify = "4.0" notify = "4.0.15"
owning_ref = "0.3" owning_ref = "0.4.1"
regex = "1.0" parking_lot = "0.11.0"
secstr = "0.3" regex = "1.4.2"
serde = "1.0" secstr = "0.4.0"
serde_derive = "1.0" serde = "1.0.117"
serde_json = "1.0" serde_derive = "1.0.117"
sha-1 = "0.7" serde_json = "1.0.59"
structopt = "0.2" sha-1 = "0.9.1"
tokio = "0.1" structopt = "0.3.20"
tokio-process = "0.2" tokio = { version = "0.3", features = ["full"] }
toml = "0.4" toml = "0.5.7"
walkdir = "2.2" walkdir = "2.3.1"

13
src/builtins/github.rs Normal file
View file

@ -0,0 +1,13 @@
use serde_json::Value;
use anyhow::Result;
use super::Builtin;
#[derive(Default)]
pub struct Github;
impl Builtin for Github {
fn handle(&self, env: (), config: (), input: ()) -> Result<Value> {
todo!()
}
}

10
src/builtins/mod.rs Normal file
View file

@ -0,0 +1,10 @@
mod github;
use serde_json::Value;
use anyhow::Result;
pub use self::github::Github;
pub trait Builtin {
fn handle(&self, env: (), config: (), input: ()) -> Result<Value>;
}

View file

@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
use std::sync::mpsc; use std::sync::mpsc;
use std::time::Duration; use std::time::Duration;
use failure::{err_msg, Error}; use anyhow::Result;
use notify::{self, RecommendedWatcher, RecursiveMode, Watcher}; use notify::{self, RecommendedWatcher, RecursiveMode, Watcher};
use walkdir::WalkDir; use walkdir::WalkDir;
@ -19,10 +19,12 @@ pub struct Config {
/// The root configuration directory for dip. This argument is required. /// The root configuration directory for dip. This argument is required.
#[structopt(short = "d", long = "root", parse(from_os_str))] #[structopt(short = "d", long = "root", parse(from_os_str))]
pub root: PathBuf, pub root: PathBuf,
/// A string containing the address to bind to. This defaults to /// A string containing the address to bind to. This defaults to
/// "0.0.0.0:5000". /// "0.0.0.0:5000".
#[structopt(short = "b", long = "bind", default_value = "0.0.0.0:5000")] #[structopt(short = "b", long = "bind", default_value = "0.0.0.0:5000")]
pub bind: String, pub bind: String,
/// If a hook is specified here, it will be triggered manually exactly once /// If a hook is specified here, it will be triggered manually exactly once
/// and then the program will exit rather than running as a server. /// and then the program will exit rather than running as a server.
#[structopt(short = "h", long = "hook")] #[structopt(short = "h", long = "hook")]
@ -69,13 +71,7 @@ where
println!("Reloading config..."); println!("Reloading config...");
// hold on to the lock while config is being reloaded // hold on to the lock while config is being reloaded
{ {
let mut programs = match PROGRAMS.lock() { let mut programs = PROGRAMS.lock();
Ok(programs) => programs,
Err(err) => {
eprintln!("Could not acquire programs lock: {}", err);
return;
}
};
// TODO: some kind of smart diff // TODO: some kind of smart diff
programs.clear(); programs.clear();
@ -96,7 +92,7 @@ where
match path match path
.file_name() .file_name()
.and_then(|s| s.to_str()) .and_then(|s| s.to_str())
.ok_or(err_msg("???")) .ok_or(anyhow!("???"))
.map(|s| { .map(|s| {
let filename = s.to_owned(); let filename = s.to_owned();
programs.insert(filename, path.to_path_buf()) programs.insert(filename, path.to_path_buf())
@ -106,14 +102,9 @@ where
} }
} }
} }
{ {
let mut hooks = match HOOKS.lock() { let mut hooks = HOOKS.lock();
Ok(hooks) => hooks,
Err(err) => {
eprintln!("Could not acquire hooks lock: {}", err);
return;
}
};
hooks.clear(); hooks.clear();
let hooks_dir = { let hooks_dir = {
let mut p = root.as_ref().to_path_buf(); let mut p = root.as_ref().to_path_buf();
@ -129,7 +120,7 @@ where
if !path.is_file() { if !path.is_file() {
continue; continue;
} }
match (|path: &Path| -> Result<(), Error> { match (|path: &Path| -> Result<()> {
let hook = Hook::from_file(path)?; let hook = Hook::from_file(path)?;
let name = hook.get_name(); let name = hook.get_name();
hooks.insert(name, hook); hooks.insert(name, hook);

View file

@ -3,9 +3,9 @@ use std::iter::FromIterator;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use failure::{err_msg, Error}; use anyhow::Result;
use generic_array::GenericArray; use generic_array::GenericArray;
use hmac::{Hmac, Mac}; use hmac::{Hmac, Mac, NewMac};
use secstr::*; use secstr::*;
use serde::Serialize; use serde::Serialize;
use serde_json::{self, Serializer as JsonSerializer, Value as JsonValue}; use serde_json::{self, Serializer as JsonSerializer, Value as JsonValue};
@ -55,7 +55,7 @@ pub(crate) fn main(
env: &Environment, env: &Environment,
config: &TomlValue, config: &TomlValue,
input: &JsonValue, input: &JsonValue,
) -> Result<JsonValue, Error> { ) -> Result<JsonValue> {
let config_str = { let config_str = {
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
{ {
@ -72,10 +72,10 @@ pub(crate) fn main(
if !config.disable_hmac_verify { if !config.disable_hmac_verify {
let secret = GenericArray::from_iter(config.secret.bytes()); let secret = GenericArray::from_iter(config.secret.bytes());
let mut mac = Hmac::<Sha1>::new(&secret); let mut mac = Hmac::<Sha1>::new(&secret);
mac.input(payload.body.as_bytes()); mac.update(payload.body.as_bytes());
let signature = mac let signature = mac
.result() .finalize()
.code() .into_bytes()
.into_iter() .into_iter()
.map(|b| format!("{:02x}", b)) .map(|b| format!("{:02x}", b))
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -84,7 +84,7 @@ pub(crate) fn main(
let auth = payload let auth = payload
.headers .headers
.get("x-hub-signature") .get("x-hub-signature")
.ok_or(err_msg("Missing auth header")) .ok_or(anyhow!("Missing auth header"))
.expect("Missing auth header"); .expect("Missing auth header");
let left = SecStr::from(format!("sha1={}", signature)); let left = SecStr::from(format!("sha1={}", signature));

View file

@ -1,36 +1,31 @@
use std::fmt; use std::fmt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::{Command, Stdio}; use std::sync::Arc;
use std::process::Stdio;
use failure::{err_msg, Error}; use anyhow::Result;
use futures::{
future::{self, Either},
Future,
};
use serde::Serialize; use serde::Serialize;
use serde_json::{Serializer as JsonSerializer, Value as JsonValue}; use serde_json::{Serializer as JsonSerializer, Value as JsonValue};
use tokio::io::write_all; use tokio::{io::AsyncWriteExt, process::Command};
use tokio_process::CommandExt;
use toml::Value as TomlValue; use toml::Value as TomlValue;
use crate::github; use crate::builtins::{Builtin, Github};
/// A single instance of handler as defined by the config. /// A single instance of handler as defined by the config.
#[derive(Clone, Debug)]
pub struct Handler { pub struct Handler {
pub(crate) config: TomlValue, pub(crate) config: TomlValue,
pub(crate) action: Action, pub(crate) action: Action,
} }
/// Describes an action that a hook can take. /// Describes an action that a hook can take.
#[derive(Clone)]
pub enum Action { pub enum Action {
/// A builtin function (for example, the Github handler). /// A builtin function (for example, the Github handler).
Builtin( Builtin(Box<dyn Builtin + Send>),
fn(&Environment, &TomlValue, &JsonValue) -> Result<JsonValue, Error>, // Fn(&Environment, &TomlValue, &JsonValue) -> Result<JsonValue>>),
),
/// A command represents a string to be executed by `bash -c`. /// A command represents a string to be executed by `bash -c`.
Command(String), Command(String),
/// A program represents one of the handlers specified in the `handlers` /// A program represents one of the handlers specified in the `handlers`
/// directory. /// directory.
Program(String), Program(String),
@ -53,22 +48,23 @@ impl fmt::Debug for Action {
} }
impl Handler { impl Handler {
pub(crate) fn from(config: &TomlValue) -> Result<Self, Error> { pub(crate) fn from(config: &TomlValue) -> Result<Self> {
let handler = config let handler = config
.get("type") .get("type")
.ok_or(err_msg("No 'type' found."))? .ok_or(anyhow!("No 'type' found."))?
.as_str() .as_str()
.ok_or(err_msg("'type' is not a string."))?; .ok_or(anyhow!("'type' is not a string."))?;
let action = match handler { let action = match handler {
"command" => { "command" => {
let command = config let command = config
.get("command") .get("command")
.ok_or(err_msg("No 'command' found"))? .ok_or(anyhow!("No 'command' found"))?
.as_str() .as_str()
.ok_or(err_msg("'command' is not a string."))?; .ok_or(anyhow!("'command' is not a string."))?;
Action::Command(command.to_owned()) Action::Command(command.to_owned())
} }
"github" => Action::Builtin(github::main), "github" => Action::Builtin(Box::new(Github::default())),
handler => Action::Program(handler.to_owned()), handler => Action::Program(handler.to_owned()),
}; };
let config = config.clone(); let config = config.clone();
@ -76,12 +72,12 @@ impl Handler {
} }
/// Runs the given [action](Action) and produces a [Future](Future). /// Runs the given [action](Action) and produces a [Future](Future).
pub fn run( pub async fn run(
config: TomlValue, config: TomlValue,
action: Action, action: Action,
temp_path: PathBuf, temp_path: PathBuf,
input: JsonValue, input: JsonValue,
) -> impl Future<Item = (PathBuf, JsonValue), Error = Error> { ) -> Result<(PathBuf, JsonValue)> {
let temp_path_cp = temp_path.clone(); let temp_path_cp = temp_path.clone();
let config_str = { let config_str = {
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
@ -102,72 +98,57 @@ impl Handler {
.stderr(Stdio::piped()); .stderr(Stdio::piped());
}; };
let output: Box<dyn Future<Item = JsonValue, Error = Error> + Send> = let output = match action {
match action { Action::Builtin(ref builtin) => {
Action::Builtin(ref func) => { let workdir = temp_path_cp.clone();
let workdir = temp_path_cp.clone(); let env = Environment { workdir };
let env = Environment { workdir }; builtin.handle(&env, &config, &input)
let result = func(&env, &config, &input); }
Box::new(future::result(result)) Action::Command(ref cmd) => {
} // TODO: allow some kind of simple variable replacement
Action::Command(ref cmd) => { let mut command = Command::new("/bin/bash");
// TODO: allow some kind of simple variable replacement command_helper(&mut command);
let mut command = Command::new("/bin/bash"); let child = command.arg("-c").arg(cmd);
command_helper(&mut command); let output = child.output().await?;
let child = command.arg("-c").arg(cmd);
let result = child
.output_async()
.map_err(|err| {
err_msg(format!("failed to spawn child: {}", err))
})
.and_then(|output| {
let stdout = String::from_utf8(output.stdout)
.unwrap_or_else(|_| String::new());
let stderr = String::from_utf8(output.stderr)
.unwrap_or_else(|_| String::new());
future::ok(json!({
"stdout": stdout,
"stderr": stderr,
}))
});
Box::new(result)
}
Action::Program(ref path) => {
let mut command = Command::new(&path);
command_helper(&mut command);
let mut child = command
.arg("--config")
.arg(config_str)
.spawn_async()
.expect("could not spawn child");
let stdin = child.stdin().take().unwrap(); let stdout = String::from_utf8(output.stdout)?;
let stderr = String::from_utf8(output.stderr)?;
let input = format!("{}", input); Ok(json!({
let result = write_all(stdin, input) "stdout": stdout,
.and_then(|_| child.wait_with_output()) "stderr": stderr,
.map_err(|err| err_msg(format!("error: {}", err))) }))
.and_then(|output| { }
let stdout = String::from_utf8(output.stdout) Action::Program(ref path) => {
.unwrap_or_else(|_| String::new()); let mut command = Command::new(&path);
let stderr = String::from_utf8(output.stderr) command_helper(&mut command);
.unwrap_or_else(|_| String::new()); let mut child =
if output.status.success() { command.arg("--config").arg(config_str).spawn()?;
Either::A(future::ok(json!({ let mut stdin = child.stdin.take().unwrap();
"stdout": stdout,
"stderr": stderr,
})))
} else {
Either::B(future::err(err_msg(format!(
"Failed, stdout: '{}', stderr: '{}'",
stdout, stderr
))))
}
});
Box::new(result) let input = format!("{}", input);
stdin.write_all(input.as_bytes()).await?;
let output = child.wait_with_output().await?;
let stdout = String::from_utf8(output.stdout)?;
let stderr = String::from_utf8(output.stderr)?;
if output.status.success() {
Ok(json!({
"stdout": stdout,
"stderr": stderr,
}))
} else {
Err(anyhow!(
"Failed, stdout: '{}', stderr: '{}'",
stdout,
stderr
))
} }
}; }
};
output.map(|x| (temp_path_cp, x)) output.map(|x| (temp_path_cp, x))
} }
} }

View file

@ -4,10 +4,8 @@ use std::fs::File;
use std::io::Read; use std::io::Read;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use failure::{err_msg, Error}; use anyhow::Result;
use futures::{stream, Future};
use serde_json::Value as JsonValue; use serde_json::Value as JsonValue;
use tokio::{self, prelude::*};
use toml::Value; use toml::Value;
use crate::Handler; use crate::Handler;
@ -20,16 +18,13 @@ pub struct Hook {
impl Hook { impl Hook {
/// Creates a hook from a (name, config) pair. /// Creates a hook from a (name, config) pair.
pub fn from( pub fn from(name: impl Into<String>, config: &Value) -> Result<Self> {
name: impl Into<String>,
config: &Value,
) -> Result<Self, Error> {
let name = name.into(); let name = name.into();
let handlers = config let handlers = config
.get("handlers") .get("handlers")
.ok_or(err_msg("No 'handlers' found."))? .ok_or(anyhow!("No 'handlers' found."))?
.as_array() .as_array()
.ok_or(err_msg("'handlers' is not an array."))? .ok_or(anyhow!("'handlers' is not an array."))?
.iter() .iter()
.map(|value: &Value| Handler::from(value)) .map(|value: &Value| Handler::from(value))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
@ -37,16 +32,16 @@ impl Hook {
} }
/// Creates a hook from a configuration file. /// Creates a hook from a configuration file.
pub fn from_file<P>(path: P) -> Result<Hook, Error> pub fn from_file<P>(path: P) -> Result<Hook>
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
let filename = path let filename = path
.as_ref() .as_ref()
.file_name() .file_name()
.ok_or(err_msg("what the fuck bro"))? .ok_or(anyhow!("what the fuck bro"))?
.to_str() .to_str()
.ok_or(err_msg("???"))? .ok_or(anyhow!("???"))?
.to_owned(); .to_owned();
let mut file = File::open(path)?; let mut file = File::open(path)?;
let mut contents = String::new(); let mut contents = String::new();
@ -62,25 +57,25 @@ impl Hook {
self.name.clone() self.name.clone()
} }
pub(crate) fn handle( pub(crate) async fn handle(
&self, &self,
req: JsonValue, req: JsonValue,
temp_path: PathBuf, temp_path: PathBuf,
) -> Result<String, String> { ) -> Result<String> {
let handlers = self let handlers = self
.handlers .handlers
.iter() .iter()
.map(|handler| (handler.config.clone(), handler.action.clone())) .map(|handler| (handler.config, handler.action))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let st = stream::iter_ok::<_, Error>(handlers.into_iter())
.fold((temp_path, req), |(path, prev), (config, action)| { let mut path = temp_path;
Handler::run(config, action, path, prev) let mut prev = req;
}) for (config, action) in handlers {
.map(|_| ()) let result = Handler::run(config, action, path, prev).await?;
.map_err(|err: Error| { path = result.0;
println!("Error from stream: {}", err); prev = result.1;
}); }
tokio::executor::spawn(st);
Ok("success".to_owned()) Ok("success".to_owned())
} }
} }

View file

@ -6,31 +6,18 @@ The configurable webhook server. Latest stable binary releases for Linux are ava
#![deny(missing_docs)] #![deny(missing_docs)]
extern crate hmac; #[macro_use]
extern crate secstr; extern crate anyhow;
extern crate sha1;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
extern crate failure;
extern crate futures;
extern crate generic_array;
extern crate hyper;
extern crate mktemp;
extern crate owning_ref;
extern crate serde;
extern crate tokio;
extern crate tokio_process;
#[macro_use] #[macro_use]
extern crate serde_json; extern crate serde_json;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
extern crate notify;
#[macro_use] #[macro_use]
extern crate structopt; extern crate structopt;
extern crate regex;
extern crate toml;
extern crate walkdir;
mod builtins;
pub mod config; pub mod config;
mod github; mod github;
mod handler; mod handler;
@ -41,13 +28,15 @@ use std::collections::HashMap;
use std::net::SocketAddrV4; use std::net::SocketAddrV4;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::sync::{Arc, Mutex}; use std::sync::Arc;
use std::thread; use std::thread;
use failure::Error; use anyhow::{Error, Result};
use hyper::rt::Future; use hyper::{
use hyper::service::service_fn; service::{make_service_fn, service_fn},
use hyper::Server; Server,
};
use parking_lot::Mutex;
use regex::Regex; use regex::Regex;
pub use crate::config::Config; pub use crate::config::Config;
@ -67,17 +56,23 @@ lazy_static! {
} }
/// Main entry point of the entire application. /// Main entry point of the entire application.
pub fn run(config: &Config) -> Result<(), Error> { pub async fn run(config: &Config) -> Result<()> {
config::load_config(&config.root); config::load_config(&config.root);
let v = config.root.clone(); let v = config.root.clone();
thread::spawn(|| config::watch(v)); thread::spawn(|| config::watch(v));
let addr: SocketAddrV4 = SocketAddrV4::from_str(config.bind.as_ref())?; let addr: SocketAddrV4 = SocketAddrV4::from_str(config.bind.as_ref())?;
let server = Server::bind(&addr.into()) let make_svc = make_service_fn(|conn| async {
.serve(|| service_fn(dip_service)) Ok::<_, Error>(service_fn(dip_service))
.map_err(|e| eprintln!("server error: {}", e)); });
let server = Server::bind(&addr.into()).serve(make_svc);
println!("Listening on {:?}", addr); println!("Listening on {:?}", addr);
hyper::rt::run(server);
if let Err(e) = server.await {
eprintln!("server error: {}", e);
}
Ok(()) Ok(())
} }

View file

@ -1,12 +1,9 @@
extern crate dip; use anyhow::Result;
extern crate failure;
extern crate structopt;
use dip::Config; use dip::Config;
use failure::Error;
use structopt::StructOpt; use structopt::StructOpt;
fn main() -> Result<(), Error> { #[tokio::main]
async fn main() -> Result<()> {
let config = Config::from_args(); let config = Config::from_args();
dip::run(&config) dip::run(&config)
} }

View file

@ -1,35 +1,34 @@
use std::collections::HashMap; use std::collections::HashMap;
use futures::{future, Future, Stream}; use anyhow::Result;
use hyper::{Body, Error, Request, Response, StatusCode}; use futures::stream::TryStreamExt;
use hyper::{
body::{to_bytes, Body},
Request, Response, StatusCode,
};
use mktemp::Temp; use mktemp::Temp;
use crate::{HOOKS, URIPATTERN}; use crate::{HOOKS, URIPATTERN};
pub(crate) fn dip_service( pub(crate) async fn dip_service(req: Request<Body>) -> Result<Response<Body>> {
req: Request<Body>,
) -> Box<dyn Future<Item = Response<Body>, Error = Error> + Send> {
let path = req.uri().path().to_owned(); let path = req.uri().path().to_owned();
let captures = match URIPATTERN.captures(path.as_ref()) { let captures = match URIPATTERN.captures(path.as_ref()) {
Some(value) => value, Some(value) => value,
None => { None => {
return Box::new(future::ok( return Ok(Response::builder()
Response::builder() .status(StatusCode::NOT_FOUND)
.status(StatusCode::NOT_FOUND) .body(Body::from("not found"))
.body(Body::from("not found")) .unwrap());
.unwrap(),
))
} }
}; };
let name = match captures.name("name") { let name = match captures.name("name") {
Some(value) => value.as_str().to_owned(), Some(value) => value.as_str().to_owned(),
None => { None => {
return Box::new(future::ok( return Ok(Response::builder()
Response::builder() .status(StatusCode::NOT_FOUND)
.status(StatusCode::NOT_FOUND) .body(Body::from("not found"))
.body(Body::from("not found")) .unwrap());
.unwrap(),
))
} }
}; };
@ -47,43 +46,45 @@ pub(crate) fn dip_service(
let method = req.method().as_str().to_owned(); let method = req.method().as_str().to_owned();
// spawn job // spawn job
Box::new(req.into_body().concat2().map(move |body| { let body = to_bytes(req.into_body()).await?;
let body = String::from_utf8(body.to_vec()).unwrap();
let req_obj = json!({
"body": body,
"headers": headers,
"method": method,
});
let hooks = HOOKS.lock().unwrap();
{
let mut temp_dir = Temp::new_dir().unwrap();
let temp_path = temp_dir.to_path_buf();
assert!(temp_path.exists());
let hook = match hooks.get(&name) { let body = String::from_utf8(body.to_vec()).unwrap();
Some(hook) => hook, let req_obj = json!({
None => { "body": body,
temp_dir.release(); "headers": headers,
return Response::builder() "method": method,
.status(StatusCode::NOT_FOUND) });
.body(Body::from("not found"))
.unwrap_or_else(|_| {
Response::new(Body::from("not found"))
});
}
};
let (code, msg) = match hook.handle(req_obj, temp_path) {
Ok(msg) => (StatusCode::ACCEPTED, msg),
Err(msg) => (StatusCode::BAD_REQUEST, msg),
};
temp_dir.release(); let mut temp_dir = Temp::new_dir().unwrap();
Response::builder() let temp_path = temp_dir.to_path_buf();
.status(code) assert!(temp_path.exists());
.body(Body::from(msg))
.unwrap_or_else(|err| { let (code, msg) = {
Response::new(Body::from(format!("{}", err))) let hooks = HOOKS.lock();
})
let hook = match hooks.get(&name) {
Some(hook) => hook,
None => {
temp_dir.release();
return Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from("not found"))
.unwrap_or_else(|_| {
Response::new(Body::from("not found"))
}));
}
};
match hook.handle(req_obj, temp_path).await {
Ok(msg) => (StatusCode::ACCEPTED, msg),
Err(msg) => (StatusCode::BAD_REQUEST, msg.to_string()),
} }
})) };
temp_dir.release();
Ok(Response::builder()
.status(code)
.body(Body::from(msg))
.unwrap_or_else(|err| Response::new(Body::from(format!("{}", err)))))
} }