use std::net::SocketAddr;
use std::path::PathBuf;
use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Redirect, Response};
use axum::routing::{get, post};
use axum::{Json, Router};
use serde::{Deserialize, Serialize};
use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer};
use tracing::Level;
use crate::key::Key;
use crate::store::Store;
#[derive(Debug, Deserialize)]
struct UrlRequest {
url: String,
}
#[derive(Debug, Serialize)]
struct UrlResponse {
key: Key,
url: String,
}
async fn redirect(
Path(key): Path<Key>,
State(store): State<Store>,
) -> Result<Response> {
Ok(match store.get(&key)? {
Some(url) => Redirect::temporary(&url).into_response(),
None => StatusCode::NOT_FOUND.into_response(),
})
}
async fn shorten(
store: State<Store>,
input: Json<UrlRequest>,
) -> Result<impl IntoResponse> {
let key = Key::gen();
store.insert(&key, &input.url)?;
Ok(Json(UrlResponse {
key,
url: input.url.clone(),
}))
}
pub struct Server {
port: u16,
db_path: PathBuf,
}
impl Server {
pub fn new(port: u16, db_path: PathBuf) -> Self {
Self { port, db_path }
}
pub async fn listen(&self) -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_target(false)
.compact()
.init();
let store = Store::new(&self.db_path)?;
let app = Router::new()
.route("/", post(shorten))
.route("/:key", get(redirect))
.layer(
TraceLayer::new_for_http()
.make_span_with(DefaultMakeSpan::new().level(Level::INFO))
.on_response(DefaultOnResponse::new().level(Level::INFO)),
)
.with_state(store);
let addr: SocketAddr = format!("0.0.0.0:{}", self.port).parse().unwrap();
tracing::info!("listening on {addr}");
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await?;
Ok(())
}
}
type Result<T> = std::result::Result<T, Error>;
struct Error(anyhow::Error);
impl IntoResponse for Error {
fn into_response(self) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", self.0),
)
.into_response()
}
}
impl<E> From<E> for Error
where
E: Into<anyhow::Error>,
{
fn from(err: E) -> Self {
Self(err.into())
}
}