cargo-spiel/src/server.rs

106 lines
3 KiB
Rust

use std::{
fs::File,
io::{Read, Write},
path::PathBuf,
process::Command,
};
use axum::{
Json, Router,
body::Bytes,
extract::{Request, State},
http::{HeaderMap, HeaderValue, StatusCode, header::CONTENT_TYPE},
middleware::{self, Next},
response::IntoResponse,
routing::get,
};
use include_dir::{Dir, include_dir};
use serde::Serialize;
static PUBLIC_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/public");
#[derive(Serialize)]
struct OutputResponse {
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
}
async fn serve_mem(request: Request, _next: Next) -> impl IntoResponse {
let uri = request.uri();
let filename = if *uri == *"/" {
"index.html".to_string()
} else {
uri.to_string().trim_start_matches("/").into()
};
if let Some(file) = PUBLIC_DIR.get_file(filename) {
let ext = file.path().extension();
let mut headers = HeaderMap::new();
let mime = if let Some(Some(ext)) = ext.map(|f| f.to_str()) {
match ext {
"html" => "text/html",
"js" => "application/javascript",
"css" => "text/css",
"svg" => "image/svg+xml",
_ => "text/plain",
}
} else {
"text/plain"
};
headers.insert(CONTENT_TYPE, HeaderValue::from_str(mime).unwrap());
(headers, file.contents()).into_response()
} else {
StatusCode::NOT_FOUND.into_response()
}
}
async fn get_code(State(state): State<ServerState>) -> impl IntoResponse {
let mut path = state.path.clone();
path.push("src/main.rs");
let mut file = File::open(path).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
contents.into_response()
}
async fn put_code(State(state): State<ServerState>, body: Bytes) -> impl IntoResponse {
let mut path = state.path.clone();
path.push("src/main.rs");
let mut file = File::options()
.write(true)
.truncate(true)
.open(path)
.unwrap();
file.write_all(&body[..]).unwrap();
StatusCode::OK.into_response()
}
async fn run_code(State(state): State<ServerState>) -> impl IntoResponse {
let mut path = state.path.clone();
path.push("Cargo.toml");
let cargo = std::env::var("CARGO").unwrap();
let output = Command::new(cargo)
.args(["run", "--manifest-path", path.to_str().unwrap()])
.output()
.unwrap();
let res = OutputResponse {
stdout: output.stdout,
stderr: output.stderr,
};
Json(res)
}
#[derive(Clone)]
struct ServerState {
pub path: PathBuf,
}
pub async fn start(path: PathBuf) {
let app = Router::new()
.layer(middleware::from_fn(serve_mem))
.route("/code", get(get_code).post(run_code).put(put_code))
.with_state(ServerState { path });
let listener = tokio::net::TcpListener::bind("127.0.0.1:4555")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}