Merge branch 'warp-0.2' into 'master'

Migrate to warp 0.2

Closes #3

See merge request pastebinrun/pastebinrun!86
This commit is contained in:
Konrad Borowski 2020-02-11 09:39:35 +00:00
commit b3ca2bcb00
20 changed files with 713 additions and 916 deletions

1154
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -13,23 +13,23 @@ chrono = "0.4.10"
diesel = { version = "1.4.3", features = ["chrono", "postgres", "r2d2"] }
diesel_migrations = "1.4.0"
env_logger = { version = "0.7.1", default-features = false }
futures = "0.1.29"
futures03 = { version = "0.3.1", package = "futures", features = ["compat"] }
extension-trait = "0.2.1"
futures = "0.3.1"
itertools = "0.8.2"
log = "0.4.8"
mime = "0.3.16"
once_cell = "1.3.1"
pulldown-cmark = "0.6.1"
rand = "0.7.3"
reqwest = "0.9.24"
reqwest = { version = "0.10.1", features = ["json"] }
serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.47"
time-parse = "0.1.2"
tokio-executor = { version = "=0.2.0-alpha.6", features = ["blocking"] }
warp = "0.1.20"
tokio = { version = "0.2.4", features = ["blocking", "macros"] }
warp = "0.2.1"
[build-dependencies]
ructe = { version = "0.9.2", features = ["mime03", "warp"] }
ructe = "0.9.2"
[dev-dependencies]
scraper = "0.11.0"

27
src/blocking.rs Normal file
View File

@ -0,0 +1,27 @@
// pastebin.run
// Copyright (C) 2020 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use tokio::task;
use warp::Rejection;
pub async fn run<R>(
f: impl FnOnce() -> Result<R, Rejection> + Send + 'static,
) -> Result<R, Rejection>
where
R: Send + 'static,
{
task::spawn_blocking(f).await.unwrap()
}

View File

@ -19,6 +19,7 @@
#[macro_use]
extern crate diesel;
mod blocking;
mod migration;
mod models;
mod routes;
@ -31,14 +32,17 @@ use std::error::Error;
type Connection = PooledConnection<ConnectionManager<PgConnection>>;
fn main() -> Result<(), Box<dyn Error>> {
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL required");
let pool = Pool::new(ConnectionManager::new(database_url))
.expect("Couldn't create a connection connection");
diesel_migrations::run_pending_migrations(&pool.get()?)?;
migration::run(&pool.get()?)?;
warp::serve(routes::routes(pool)).run(([0, 0, 0, 0], 8080));
warp::serve(routes::routes(pool))
.run(([127, 0, 0, 1], 8080))
.await;
Ok(())
}

31
src/models/db.rs Normal file
View File

@ -0,0 +1,31 @@
// pastebin.run
// Copyright (C) 2020 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use extension_trait::extension_trait;
use warp::reject::{Reject, Rejection};
#[derive(Debug)]
struct DbError(diesel::result::Error);
impl Reject for DbError {}
#[extension_trait(pub)]
impl<T> DbErrorExt for Result<T, diesel::result::Error> {
type Error = T;
fn into_rejection(self) -> Result<Self::Error, Rejection> {
self.map_err(|e| warp::reject::custom(DbError(e)))
}
}

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::db::DbErrorExt;
use crate::schema::languages::dsl::*;
use crate::Connection;
use diesel::prelude::*;
@ -32,7 +33,7 @@ impl Language {
.select((language_id, identifier, name))
.order((priority.asc(), name.asc()))
.load(connection)
.map_err(warp::reject::custom)
.into_rejection()
}
}

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pub mod db;
pub mod js;
pub mod language;
pub mod paste;

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::db::DbErrorExt;
use crate::models::rejection::CustomRejection;
use crate::schema::{languages, pastes};
use crate::Connection;
@ -45,7 +46,7 @@ impl Paste {
let pastes = diesel::delete(pastes::table)
.filter(pastes::delete_at.lt(Utc::now()))
.execute(connection)
.map_err(warp::reject::custom)?;
.into_rejection()?;
if pastes > 0 {
info!("Deleted {} paste(s)", pastes);
}
@ -97,7 +98,7 @@ pub fn insert(
.filter(languages::identifier.eq(language))
.get_result(connection)
.optional()
.map_err(warp::reject::custom)?
.into_rejection()?
.ok_or_else(|| warp::reject::custom(CustomRejection::UnrecognizedLanguageIdentifier))?;
for (field, name) in &[(&paste, "paste"), (&stdin, "stdin")] {
if field.len() > 1_000_000 {
@ -124,7 +125,7 @@ pub fn insert(
diesel::insert_into(pastes::table)
.values(&insert_paste)
.execute(connection)
.map_err(warp::reject::custom)?;
.into_rejection()?;
Ok(insert_paste.identifier)
}

View File

@ -14,9 +14,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::error::Error;
use std::fmt::{Display, Formatter, Result};
use warp::http::StatusCode;
use warp::reject::Reject;
#[derive(Debug)]
pub enum CustomRejection {
@ -42,4 +42,4 @@ impl Display for CustomRejection {
}
}
impl Error for CustomRejection {}
impl Reject for CustomRejection {}

View File

@ -15,9 +15,14 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::Connection;
use extension_trait::extension_trait;
use mime::TEXT_HTML_UTF_8;
use std::borrow::Cow;
use warp::http::header::CONTENT_SECURITY_POLICY;
use std::io;
use warp::http;
use warp::http::header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE};
use warp::http::response::{Builder, Response};
use warp::reject::{Reject, Rejection};
pub struct Session {
pub nonce: String,
@ -27,8 +32,7 @@ pub struct Session {
impl Session {
pub fn render(&self) -> Builder {
let mut builder = Response::builder();
builder.header(
Response::builder().header(
CONTENT_SECURITY_POLICY,
format!(
concat!(
@ -44,7 +48,30 @@ impl Session {
),
nonce = self.nonce,
),
);
builder
)
}
}
#[extension_trait(pub)]
impl RenderExt for Builder {
fn html<F>(self, f: F) -> Result<Response<Vec<u8>>, Rejection>
where
F: FnOnce(&mut Vec<u8>) -> io::Result<()>,
{
let mut buf = Vec::new();
f(&mut buf).map_err(|e| warp::reject::custom(TemplateError(e)))?;
self.header(CONTENT_TYPE, TEXT_HTML_UTF_8.as_ref())
.body(buf)
.map_err(|e| warp::reject::custom(RenderError(e)))
}
}
#[derive(Debug)]
struct TemplateError(io::Error);
impl Reject for TemplateError {}
#[derive(Debug)]
struct RenderError(http::Error);
impl Reject for RenderError {}

View File

@ -14,13 +14,11 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::db::DbErrorExt;
use crate::schema::{implementation_wrappers, implementations, languages};
use crate::Connection;
use crate::{blocking, Connection};
use diesel::prelude::*;
use futures::Future;
use futures03::prelude::*;
use serde::Serialize;
use tokio_executor::blocking;
use warp::http::header::CACHE_CONTROL;
use warp::{Rejection, Reply};
@ -69,24 +67,24 @@ struct JsonImplementation {
wrappers: Vec<Wrapper>,
}
pub fn api_language(
pub async fn api_language(
connection: Connection,
identifier: String,
) -> impl Future<Item = impl Reply, Error = Rejection> {
) -> Result<impl Reply, Rejection> {
blocking::run(move || {
let language: Language = languages::table
.filter(languages::identifier.eq(identifier))
.select((languages::language_id, languages::hello_world))
.get_result(&connection)
.optional()
.map_err(warp::reject::custom)?
.into_rejection()?
.ok_or_else(warp::reject::not_found)?;
let implementations = implementations::table
.select((implementations::implementation_id, implementations::label))
.filter(implementations::language_id.eq(language.id))
.order(implementations::ordering)
.load(&connection)
.map_err(warp::reject::custom)?;
.into_rejection()?;
let implementation_wrappers = ImplementationWrapper::belonging_to(&implementations)
.select((
implementation_wrappers::implementation_wrapper_id,
@ -98,7 +96,7 @@ pub fn api_language(
))
.order(implementation_wrappers::ordering)
.load(&connection)
.map_err(warp::reject::custom)?;
.into_rejection()?;
let implementations = implementation_wrappers
.grouped_by(&implementations)
.into_iter()
@ -135,5 +133,5 @@ pub fn api_language(
"max-age=14400",
))
})
.compat()
.await
}

View File

@ -14,13 +14,11 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::db::DbErrorExt;
use crate::schema::languages;
use crate::Connection;
use crate::{blocking, Connection};
use diesel::prelude::*;
use futures::Future;
use futures03::TryFutureExt;
use serde::Serialize;
use tokio_executor::blocking;
use warp::{Rejection, Reply};
#[derive(Queryable, Serialize)]
@ -29,13 +27,13 @@ struct Language {
name: String,
}
pub fn languages(connection: Connection) -> impl Future<Item = impl Reply, Error = Rejection> {
pub async fn languages(connection: Connection) -> Result<impl Reply, Rejection> {
blocking::run(move || {
let languages: Vec<Language> = languages::table
.select((languages::identifier, languages::name))
.load(&connection)
.map_err(warp::reject::custom)?;
.into_rejection()?;
Ok(warp::reply::json(&languages))
})
.compat()
.await
}

View File

@ -15,14 +15,11 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::paste::{self, ExtraPasteParameters};
use crate::Connection;
use crate::{blocking, Connection};
use chrono::{Duration, Utc};
use futures::Future;
use futures03::TryFutureExt;
use serde::de::{Deserializer, Unexpected, Visitor};
use serde::{de, Deserialize};
use std::fmt::{self, Formatter};
use tokio_executor::blocking;
use warp::Rejection;
#[derive(Deserialize)]
@ -71,14 +68,14 @@ fn default_language() -> String {
"plaintext".into()
}
pub fn insert_paste(
pub async fn insert_paste(
PasteForm {
expiration,
language,
code,
}: PasteForm,
connection: Connection,
) -> impl Future<Item = String, Error = Rejection> {
) -> Result<String, Rejection> {
blocking::run(move || {
paste::insert(
&connection,
@ -93,5 +90,5 @@ pub fn insert_paste(
},
)
})
.compat()
.await
}

View File

@ -14,10 +14,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::session::Session;
use crate::templates::{self, RenderRucte};
use crate::models::session::{RenderExt, Session};
use crate::templates;
use warp::{Rejection, Reply};
pub fn config(session: Session) -> Result<impl Reply, Rejection> {
pub async fn config(session: Session) -> Result<impl Reply, Rejection> {
session.render().html(|o| templates::config(o, &session))
}

View File

@ -14,22 +14,21 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::blocking;
use crate::models::db::DbErrorExt;
use crate::models::language::{Language, Selection};
use crate::models::paste::{ExternPaste, Paste};
use crate::models::session::Session;
use crate::models::session::{RenderExt, Session};
use crate::schema::{languages, pastes};
use crate::templates::{self, RenderRucte};
use crate::templates;
use diesel::prelude::*;
use futures::future::*;
use futures03::TryFutureExt;
use std::borrow::Cow;
use tokio_executor::blocking;
use warp::{Rejection, Reply};
pub fn display_paste(
pub async fn display_paste(
requested_identifier: String,
mut session: Session,
) -> impl Future<Item = impl Reply, Error = Rejection> {
) -> Result<impl Reply, Rejection> {
blocking::run(move || {
let connection = &session.connection;
Paste::delete_old(connection)?;
@ -49,7 +48,7 @@ pub fn display_paste(
.filter(pastes::identifier.eq(requested_identifier))
.get_result(connection)
.optional()
.map_err(warp::reject::custom)?
.into_rejection()?
.ok_or_else(warp::reject::not_found)?;
session.description = generate_description(&paste.paste);
let selected_language = Some(paste.language_id);
@ -65,7 +64,7 @@ pub fn display_paste(
)
})
})
.compat()
.await
}
fn generate_description(paste: &str) -> Cow<'static, str> {

View File

@ -14,15 +14,13 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::blocking;
use crate::models::language::{Language, Selection};
use crate::models::session::Session;
use crate::templates::{self, RenderRucte};
use futures::Future;
use futures03::TryFutureExt;
use tokio_executor::blocking;
use crate::models::session::{RenderExt, Session};
use crate::templates;
use warp::{Rejection, Reply};
pub fn index(session: Session) -> impl Future<Item = impl Reply, Error = Rejection> {
pub async fn index(session: Session) -> Result<impl Reply, Rejection> {
blocking::run(move || {
let languages = Language::fetch(&session.connection)?;
session.render().html(|o| {
@ -36,5 +34,5 @@ pub fn index(session: Session) -> impl Future<Item = impl Reply, Error = Rejecti
)
})
})
.compat()
.await
}

View File

@ -16,12 +16,9 @@
use crate::models::paste;
use crate::models::paste::ExtraPasteParameters;
use crate::Connection;
use crate::{blocking, Connection};
use chrono::{Duration, Utc};
use futures::Future;
use futures03::TryFutureExt;
use serde::Deserialize;
use tokio_executor::blocking;
use warp::http::header::LOCATION;
use warp::http::StatusCode;
use warp::{reply, Rejection, Reply};
@ -45,7 +42,7 @@ pub enum Share {
Share24,
}
pub fn insert_paste(
pub async fn insert_paste(
PasteForm {
language,
code,
@ -56,7 +53,7 @@ pub fn insert_paste(
status,
}: PasteForm,
connection: Connection,
) -> impl Future<Item = impl Reply, Error = Rejection> {
) -> Result<impl Reply, Rejection> {
blocking::run(move || {
let delete_at = match share {
Share::Share => None,
@ -80,5 +77,5 @@ pub fn insert_paste(
format!("/{}", identifier),
))
})
.compat()
.await
}

View File

@ -24,128 +24,131 @@ mod raw_paste;
mod run;
use crate::models::rejection::CustomRejection;
use crate::models::session::Session;
use crate::templates::{self, RenderRucte};
use crate::Connection;
use crate::models::session::{RenderExt, Session};
use crate::templates;
use crate::{blocking, Connection};
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool};
use futures03::compat::Compat;
use futures03::{Future, FutureExt, TryFutureExt};
use futures::{Future, FutureExt};
use std::ffi::OsStr;
use std::path::PathBuf;
use std::pin::Pin;
use tokio_executor::blocking;
use warp::filters::cors::Cors;
use warp::filters::BoxedFilter;
use warp::http::header::{
HeaderMap, HeaderValue, CONTENT_SECURITY_POLICY, CONTENT_TYPE, REFERRER_POLICY, X_FRAME_OPTIONS,
HeaderMap, HeaderValue, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN,
CONTENT_SECURITY_POLICY, CONTENT_TYPE, REFERRER_POLICY, X_FRAME_OPTIONS,
};
use warp::http::method::Method;
use warp::http::{Response, StatusCode};
use warp::reject::Reject;
use warp::{path, Filter, Rejection, Reply};
type PgPool = Pool<ConnectionManager<PgConnection>>;
fn connection(pool: PgPool) -> BoxedFilter<(Connection,)> {
warp::any()
.and_then(move || get_connection(pool.clone()).compat())
.and_then(move || get_connection(pool.clone()))
.boxed()
}
fn get_connection(pool: PgPool) -> impl Future<Output = Result<Connection, Rejection>> {
blocking::run(move || pool.get().map_err(warp::reject::custom))
async fn get_connection(pool: PgPool) -> Result<Connection, Rejection> {
blocking::run(move || {
pool.get()
.map_err(|e| warp::reject::custom(ConnectionError(e)))
})
.await
}
#[derive(Debug)]
struct ConnectionError<E>(E);
impl<E: 'static + std::fmt::Debug + Send + Sync> Reject for ConnectionError<E> {}
fn session(pool: PgPool) -> BoxedFilter<(Session,)> {
warp::any()
.and_then(move || get_session(pool.clone()).compat())
.and_then(move || get_session(pool.clone()))
.boxed()
}
fn get_session(pool: PgPool) -> impl Future<Output = Result<Session, Rejection>> {
get_connection(pool).map_ok(|connection| {
let bytes: [u8; 32] = rand::random();
Session {
nonce: base64::encode(&bytes),
connection,
description: "Compile and share code in multiple programming languages".into(),
}
async fn get_session(pool: PgPool) -> Result<Session, Rejection> {
let connection = get_connection(pool).await?;
let bytes: [u8; 32] = rand::random();
Ok(Session {
nonce: base64::encode(&bytes),
connection,
description: "Compile and share code in multiple programming languages".into(),
})
}
fn index(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
warp::path::end()
.and(
warp::post2()
warp::post()
.and(warp::body::form())
.and(connection(pool.clone()))
.and_then(insert_paste::insert_paste)
.or(warp::get2().and(session(pool)).and_then(index::index)),
.or(warp::get().and(session(pool)).and_then(index::index)),
)
.boxed()
}
fn display_paste(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
warp::path::param()
.and(warp::path::end())
.and(warp::get2())
warp::path!(String)
.and(warp::get())
.and(session(pool))
.and_then(display_paste::display_paste)
.boxed()
}
fn options(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
warp::path("config")
.and(warp::path::end())
.and(warp::get2())
warp::path!("config")
.and(warp::get())
.and(session(pool))
.and_then(config::config)
.boxed()
}
fn raw_paste(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
with_ext("txt")
.map(warp::ext::set)
.untuple_one()
.and(
warp::get2()
.and(warp::ext::get())
.and(connection(pool))
.and_then(raw_paste::raw_paste)
.with(cors()),
)
warp::get()
.and(with_ext("txt"))
.and(connection(pool))
.and_then(raw_paste::raw_paste)
.or(warp::options().and(with_ext("txt")).map(|_| {
Response::builder()
.header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
.header(ACCESS_CONTROL_ALLOW_METHODS, "GET")
.body("")
}))
.boxed()
}
fn api_v0(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
let root = path!("api" / "v0").and(connection(pool));
let root = path!("api" / "v0" / ..).and(connection(pool));
let language = root
.clone()
.and(path!("language" / String))
.and(warp::path::end())
.and(warp::get2())
.and(warp::get())
.and_then(api_language::api_language);
let run = root
.and(path!("run" / String))
.and(warp::post2())
.and(warp::post())
.and(warp::body::form())
.and_then(run::run);
language.or(run).boxed()
}
fn api_v1(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
let languages = warp::path("languages")
.and(warp::path::end())
.and(warp::get2())
let languages = warp::path!("languages")
.and(warp::get())
.and(connection(pool.clone()))
.and_then(api_v1::languages::languages);
let pastes = warp::path("pastes")
.and(warp::path::end())
.and(warp::post2())
let pastes = warp::path!("pastes")
.and(warp::post())
.and(warp::body::form())
.and(connection(pool))
.and_then(api_v1::pastes::insert_paste);
path!("api" / "v1")
path!("api" / "v1" / ..)
.and(languages.or(pastes).with(cors()))
.boxed()
}
@ -155,6 +158,7 @@ fn cors() -> Cors {
.allow_any_origin()
.allow_methods(&[Method::GET, Method::POST])
.allow_headers(&[CONTENT_TYPE])
.build()
}
fn static_dir() -> BoxedFilter<(impl Reply,)> {
@ -162,15 +166,14 @@ fn static_dir() -> BoxedFilter<(impl Reply,)> {
}
fn favicon() -> BoxedFilter<(impl Reply,)> {
warp::path("favicon.ico")
.and(warp::path::end())
warp::path!("favicon.ico")
.and(warp::fs::file("static/favicon.ico"))
.boxed()
}
pub fn routes(
pool: Pool<ConnectionManager<PgConnection>>,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> {
) -> impl Clone + Filter<Extract = (impl Reply,), Error = Rejection> {
let mut headers = HeaderMap::new();
headers.insert(X_FRAME_OPTIONS, HeaderValue::from_static("DENY"));
headers.insert(REFERRER_POLICY, HeaderValue::from_static("no-referrer"));
@ -192,30 +195,29 @@ pub fn routes(
}
fn with_ext(ext: &'static str) -> impl Filter<Extract = (String,), Error = Rejection> + Copy {
warp::path::param()
.and(warp::path::end())
.and_then(move |path: PathBuf| {
match (path.extension(), path.file_stem().and_then(OsStr::to_str)) {
(Some(received_ext), Some(file_stem)) if ext == received_ext => {
Ok(file_stem.to_string())
}
_ => Err(warp::reject::not_found()),
warp::path!(PathBuf).and_then(move |path: PathBuf| async move {
match (path.extension(), path.file_stem().and_then(OsStr::to_str)) {
(Some(received_ext), Some(file_stem)) if ext == received_ext => {
Ok(file_stem.to_string())
}
})
_ => Err(warp::reject::not_found()),
}
})
}
type NotFoundFuture =
Compat<Pin<Box<dyn Future<Output = Result<Response<Vec<u8>>, Rejection>> + Send>>>;
fn not_found(pool: PgPool) -> impl Clone + Fn(Rejection) -> NotFoundFuture {
fn not_found(
pool: PgPool,
) -> impl Clone
+ Fn(Rejection) -> Pin<Box<dyn Future<Output = Result<Response<Vec<u8>>, Rejection>> + Send>>
{
move |rejection| {
let pool = pool.clone();
async move {
if let Some(rejection) = rejection.find_cause::<CustomRejection>() {
Response::builder()
if let Some(rejection) = rejection.find::<CustomRejection>() {
Ok(Response::builder()
.status(rejection.status_code())
.body(rejection.to_string().into_bytes())
.map_err(warp::reject::custom)
.unwrap())
} else if rejection.is_not_found() {
let session = get_session(pool.clone()).await?;
session
@ -227,7 +229,6 @@ fn not_found(pool: PgPool) -> impl Clone + Fn(Rejection) -> NotFoundFuture {
}
}
.boxed()
.compat()
}
}
@ -274,9 +275,9 @@ mod test {
}
}
fn get_sh_id() -> String {
async fn get_sh_id() -> String {
let response = warp::test::request().reply(&*ROUTES);
let document = Html::parse_document(str::from_utf8(response.body()).unwrap());
let document = Html::parse_document(str::from_utf8(response.await.body()).unwrap());
document
.select(&Selector::parse("#language option").unwrap())
.find(|element| element.text().next() == Some("Sh"))
@ -303,17 +304,18 @@ mod test {
is_formatter: bool,
}
#[test]
#[tokio::test]
#[cfg_attr(not(feature = "database_tests"), ignore)]
fn test_language_api() {
async fn test_language_api() {
#[derive(Debug, Deserialize, PartialEq)]
pub struct ApiLanguage<'a> {
#[serde(borrow)]
implementations: Vec<Implementation<'a>>,
}
let response = warp::test::request()
.path(&format!("/api/v0/language/{}", get_sh_id()))
.reply(&*ROUTES);
.path(&format!("/api/v0/language/{}", get_sh_id().await))
.reply(&*ROUTES)
.await;
assert_eq!(
serde_json::from_slice::<ApiLanguage>(response.body()).unwrap(),
ApiLanguage {
@ -330,29 +332,31 @@ mod test {
);
}
#[test]
#[tokio::test]
#[cfg_attr(not(feature = "database_tests"), ignore)]
fn test_raw_pastes() {
let body = format!("language={}&code=abc&share=share24", get_sh_id());
async fn test_raw_pastes() {
let body = format!("language={}&code=abc&share=share24", get_sh_id().await);
let reply = warp::test::request()
.method("POST")
.header(CONTENT_LENGTH, body.len())
.body(body)
.reply(&*ROUTES);
.reply(&*ROUTES)
.await;
assert_eq!(reply.status(), StatusCode::SEE_OTHER);
let location = reply.headers()[LOCATION].to_str().unwrap();
assert_eq!(
warp::test::request()
.path(&format!("{}.txt", location))
.reply(&*ROUTES)
.await
.body(),
"abc"
);
}
#[test]
#[tokio::test]
#[cfg_attr(not(feature = "sandbox_tests"), ignore)]
fn test_sandbox() {
async fn test_sandbox() {
#[derive(Deserialize)]
struct LanguageIdentifier<'a> {
identifier: &'a str,
@ -366,13 +370,15 @@ mod test {
}
let languages = warp::test::request()
.path("/api/v1/languages")
.reply(&*ROUTES);
.reply(&*ROUTES)
.await;
let languages =
serde_json::from_slice::<Vec<LanguageIdentifier>>(languages.body()).unwrap();
for LanguageIdentifier { identifier } in languages {
let language = warp::test::request()
.path(&format!("/api/v0/language/{}", identifier))
.reply(&*ROUTES);
.reply(&*ROUTES)
.await;
let ApiLanguage {
hello_world,
implementations,
@ -388,7 +394,8 @@ mod test {
.method("POST")
.header(CONTENT_LENGTH, body.len())
.body(body)
.reply(&*ROUTES);
.reply(&*ROUTES)
.await;
let body = str::from_utf8(out.body()).unwrap();
assert!(
body.contains(r#"Hello, world!\n""#),
@ -400,9 +407,9 @@ mod test {
}
}
#[test]
#[tokio::test]
#[cfg_attr(not(feature = "database_tests"), ignore)]
fn raw_cors() {
async fn raw_cors() {
assert_eq!(
warp::test::request()
.path("/a.txt")
@ -410,14 +417,15 @@ mod test {
.header("origin", "example.com")
.header("access-control-request-method", "GET")
.reply(&*ROUTES)
.await
.status(),
StatusCode::OK,
);
}
#[test]
#[tokio::test]
#[cfg_attr(not(feature = "database_tests"), ignore)]
fn paste_no_cors() {
async fn paste_no_cors() {
assert_eq!(
warp::test::request()
.path("/a")
@ -425,14 +433,15 @@ mod test {
.header("origin", "example.com")
.header("access-control-request-method", "GET")
.reply(&*ROUTES)
.await
.status(),
StatusCode::METHOD_NOT_ALLOWED,
);
}
#[test]
#[tokio::test]
#[cfg_attr(not(feature = "database_tests"), ignore)]
fn api_v1_cors() {
async fn api_v1_cors() {
assert_eq!(
warp::test::request()
.path("/api/v1/languages")
@ -440,6 +449,7 @@ mod test {
.header("origin", "example.com")
.header("access-control-request-method", "GET")
.reply(&*ROUTES)
.await
.status(),
StatusCode::OK,
);

View File

@ -14,19 +14,17 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::db::DbErrorExt;
use crate::models::paste::Paste;
use crate::schema::pastes::dsl::*;
use crate::Connection;
use crate::{blocking, Connection};
use diesel::prelude::*;
use futures::Future;
use futures03::TryFutureExt;
use tokio_executor::blocking;
use warp::Rejection;
pub fn raw_paste(
pub async fn raw_paste(
requested_identifier: String,
connection: Connection,
) -> impl Future<Item = String, Error = Rejection> {
) -> Result<String, Rejection> {
blocking::run(move || {
Paste::delete_old(&connection)?;
pastes
@ -34,8 +32,8 @@ pub fn raw_paste(
.filter(identifier.eq(requested_identifier))
.get_result(&connection)
.optional()
.map_err(warp::reject::custom)?
.into_rejection()?
.ok_or_else(warp::reject::not_found)
})
.compat()
.await
}

View File

@ -14,16 +14,16 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::db::DbErrorExt;
use crate::schema::implementation_wrappers;
use crate::Connection;
use crate::{blocking, Connection};
use diesel::prelude::*;
use futures::Future;
use futures03::TryFutureExt;
use futures::TryFutureExt;
use once_cell::sync::Lazy;
use reqwest::r#async::Client;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::env;
use tokio_executor::blocking;
use warp::reject::Reject;
use warp::{Rejection, Reply};
static CLIENT: Lazy<Client> = Lazy::new(Client::new);
@ -57,7 +57,7 @@ struct Output {
stderr: String,
}
pub fn run(
pub async fn run(
connection: Connection,
identifier: String,
Form {
@ -65,31 +65,35 @@ pub fn run(
compiler_options,
stdin,
}: Form,
) -> impl Future<Item = impl Reply, Error = Rejection> {
blocking::run(move || {
) -> Result<impl Reply, Rejection> {
let language_code: String = blocking::run(move || {
implementation_wrappers::table
.filter(implementation_wrappers::identifier.eq(identifier))
.select(implementation_wrappers::code)
.get_result(&connection)
.optional()
.map_err(warp::reject::custom)?
.into_rejection()?
.ok_or_else(warp::reject::not_found)
})
.compat()
.and_then(move |language_code: String| {
CLIENT
.post(SANDBOX_URL.as_str())
.json(&Request {
files: vec![File {
name: "code",
contents: code,
}],
stdin,
code: language_code.replace("%s", &compiler_options),
})
.send()
.and_then(|mut r| r.json())
.map_err(warp::reject::custom)
})
.map(|json: Output| warp::reply::json(&json))
.await?;
let json: Output = CLIENT
.post(SANDBOX_URL.as_str())
.json(&Request {
files: vec![File {
name: "code",
contents: code,
}],
stdin,
code: language_code.replace("%s", &compiler_options),
})
.send()
.and_then(|r| r.json())
.map_err(|e| warp::reject::custom(RemoteServerError(e)))
.await?;
Ok(warp::reply::json(&json))
}
#[derive(Debug)]
struct RemoteServerError(reqwest::Error);
impl Reject for RemoteServerError {}