First routes

This commit is contained in:
Raphaël Thériault 2020-10-01 13:15:15 -04:00
parent 41a745f82a
commit bb4366100f
No known key found for this signature in database
GPG Key ID: D4E92B68275D389F
9 changed files with 177 additions and 8 deletions

1
Cargo.lock generated
View File

@ -278,6 +278,7 @@ dependencies = [
"askama 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
"bincode 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.18 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -19,6 +19,7 @@ anyhow = "1.0.32"
askama = "0.10.3"
base64 = "0.12.3"
bincode = "1.3.1"
bytes = "0.5.6"
chrono = { version = "0.4.18", features = ["serde"] }
futures = "0.3.5"
rand = "0.7.3"

View File

@ -9,7 +9,7 @@ use sled::Db;
use tokio::task;
use warp::{Filter, Rejection};
pub fn auth_optional(
pub fn optional(
db: &'static Db,
config: &'static Config,
) -> impl Filter<Extract = (Option<User>,), Error = Rejection> + Copy + Send + Sync + 'static {
@ -24,7 +24,7 @@ pub fn auth_optional(
})
}
pub fn auth_required(
pub fn required(
db: &'static Db,
config: &'static Config,
) -> impl Filter<Extract = (User,), Error = Rejection> + Copy + Send + Sync + 'static {

View File

@ -6,6 +6,9 @@ mod routes;
mod runtime;
mod util;
#[cfg(test)]
mod tests;
use anyhow::Error;
use config::Config;
use structopt::StructOpt;

View File

@ -1,24 +1,27 @@
use std::fmt::Display;
use std::fmt::{Debug, Display};
use warp::{
http::StatusCode,
reject::{Reject, Rejection},
reply::{Reply, Response},
};
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Clone)]
enum FiliteRejection {
NotFound,
Unauthorized,
InternalServerError,
Conflict,
Custom(String, StatusCode),
}
impl Reject for FiliteRejection {}
impl Reply for FiliteRejection {
fn into_response(self) -> Response {
match self {
FiliteRejection::NotFound => {
Self::NotFound => {
warp::reply::with_status("Not Found", StatusCode::NOT_FOUND).into_response()
}
FiliteRejection::Unauthorized => warp::reply::with_status(
Self::Unauthorized => warp::reply::with_status(
warp::reply::with_header(
"Unauthorized",
"WWW-Authenticate",
@ -27,10 +30,14 @@ impl Reply for FiliteRejection {
StatusCode::UNAUTHORIZED,
)
.into_response(),
FiliteRejection::InternalServerError => {
Self::InternalServerError => {
warp::reply::with_status("Internal Server Error", StatusCode::INTERNAL_SERVER_ERROR)
.into_response()
}
Self::Conflict => {
warp::reply::with_status("Conflict", StatusCode::CONFLICT).into_response()
}
Self::Custom(reply, status) => warp::reply::with_status(reply, status).into_response(),
}
}
}
@ -40,10 +47,16 @@ pub fn unauthorized() -> Rejection {
warp::reject::custom(FiliteRejection::Unauthorized)
}
#[inline]
pub fn custom<T: ToString>(reply: T, status: StatusCode) -> Rejection {
warp::reject::custom(FiliteRejection::Custom(reply.to_string(), status))
}
pub trait TryExt<T> {
fn or_404(self) -> Result<T, Rejection>;
fn or_401(self) -> Result<T, Rejection>;
fn or_500(self) -> Result<T, Rejection>;
fn or_409(self) -> Result<T, Rejection>;
}
impl<T, E: Display> TryExt<T> for Result<T, E> {
@ -67,6 +80,13 @@ impl<T, E: Display> TryExt<T> for Result<T, E> {
warp::reject::custom(FiliteRejection::InternalServerError)
})
}
fn or_409(self) -> Result<T, Rejection> {
self.map_err(|e| {
tracing::error!("{}", e);
warp::reject::custom(FiliteRejection::Conflict)
})
}
}
impl<T> TryExt<T> for Option<T> {
@ -81,6 +101,10 @@ impl<T> TryExt<T> for Option<T> {
fn or_500(self) -> Result<T, Rejection> {
self.ok_or_else(|| warp::reject::custom(FiliteRejection::InternalServerError))
}
fn or_409(self) -> Result<T, Rejection> {
self.ok_or_else(|| warp::reject::custom(FiliteRejection::Conflict))
}
}
#[tracing::instrument(level = "debug")]
@ -88,7 +112,7 @@ pub async fn handle_rejections(err: Rejection) -> Result<impl Reply, Rejection>
if err.is_not_found() {
Ok(FiliteRejection::NotFound)
} else if let Some(err) = err.find::<FiliteRejection>() {
Ok(*err)
Ok(err.clone())
} else {
Err(err)
}

119
src/routes.rs Normal file
View File

@ -0,0 +1,119 @@
use crate::{config::Config, db::User, reject::TryExt};
use bytes::Bytes;
use sled::Db;
use warp::{http::Uri, Filter, Rejection, Reply};
pub fn handler(
config: &'static Config,
db: &'static Db,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Copy + Send + Sync + 'static {
let filite = warp::path!(String)
.and(warp::get())
.and_then(move |id| filite(id, db));
let post_file = warp::path!("f")
.and(warp::post())
.and(crate::auth::required(db, config))
.and(warp::body::bytes())
.and(warp::header("Content-Type"))
.and(warp::header::optional("X-ID-Length"))
.and_then(move |user, data, mime, len| post_file(user, data, mime, len, db));
let put_file = warp::path!("f" / String)
.and(warp::put())
.and(crate::auth::required(db, config))
.and(warp::body::bytes())
.and(warp::header("Content-Type"))
.and_then(move |id, user, data, mime| put_file(id, user, data, mime, db));
let post_link = warp::path!("l")
.and(warp::post())
.and(crate::auth::required(db, config))
.and(crate::util::body())
.and(warp::header::optional("X-ID-Length"))
.and_then(move |user, location, len| post_link(user, location, len, db));
let put_link = warp::path!("l" / String)
.and(warp::put())
.and(crate::auth::required(db, config))
.and(crate::util::body())
.and_then(move |id, user, location| put_link(id, user, location, db));
let post_text = warp::path!("t")
.and(warp::post())
.and(crate::auth::required(db, config))
.and(crate::util::body())
.and(warp::header::optional("X-ID-Length"))
.and_then(move |user, data, len| post_text(user, data, len, db));
let put_text = warp::path!("t" / String)
.and(warp::put())
.and(crate::auth::required(db, config))
.and(crate::util::body())
.and_then(move |id, user, data| put_text(id, user, data, db));
filite
.or(post_file)
.or(put_file)
.or(post_link)
.or(put_link)
.or(post_text)
.or(put_text)
}
async fn filite(id: String, db: &Db) -> Result<impl Reply, Rejection> {}
async fn post_file(
user: User,
data: Bytes,
mime: String,
len: Option<usize>,
db: &Db,
) -> Result<impl Reply, Rejection> {
let id = crate::db::random_id(len.unwrap_or(8), db).or_500()?;
put_file(id, user, data, mime, db).await
}
async fn put_file(
id: String,
user: User,
data: Bytes,
mime: String,
db: &Db,
) -> Result<impl Reply, Rejection> {
crate::db::insert_file(&id, user.id, data.to_vec(), mime, db)
.or_500()?
.or_409()?;
Ok(id)
}
async fn post_link(
user: User,
location: Uri,
len: Option<usize>,
db: &Db,
) -> Result<impl Reply, Rejection> {
let id = crate::db::random_id(len.unwrap_or(8), db).or_500()?;
put_link(id, user, location, db).await
}
async fn put_link(id: String, user: User, location: Uri, db: &Db) -> Result<impl Reply, Rejection> {
crate::db::insert_link(&id, user.id, location.to_string(), db)
.or_500()?
.or_409()?;
Ok(id)
}
async fn post_text(
user: User,
data: String,
len: Option<usize>,
db: &Db,
) -> Result<impl Reply, Rejection> {
let id = crate::db::random_id(len.unwrap_or(8), db).or_500()?;
put_text(id, user, data, db).await
}
async fn put_text(id: String, user: User, data: String, db: &Db) -> Result<impl Reply, Rejection> {
crate::db::insert_text(&id, user.id, data, db)
.or_500()?
.or_409()?;
Ok(id)
}

View File

1
src/tests.rs Normal file
View File

@ -0,0 +1 @@
// TODO

View File

@ -1,3 +1,7 @@
use bytes::Bytes;
use std::str::FromStr;
use warp::{http::StatusCode, Filter, Rejection};
pub trait DefaultExt {
fn is_default(&self) -> bool;
}
@ -6,3 +10,19 @@ impl<T: Default + PartialEq> DefaultExt for T {
self.eq(&Default::default())
}
}
pub fn body<T>() -> impl Filter<Extract = (T,), Error = Rejection> + Copy + Send + Sync + 'static
where
T: FromStr,
T::Err: ToString,
{
warp::body::bytes().and_then(|b: Bytes| async move {
match std::str::from_utf8(&b) {
Ok(s) => match s.parse() {
Ok(v) => Ok(v),
Err(e) => Err(crate::reject::custom(e, StatusCode::BAD_REQUEST)),
},
Err(e) => Err(crate::reject::custom(e, StatusCode::BAD_REQUEST)),
}
})
}