Add failure recovery to web package

This commit is contained in:
Tobie Morgan Hitchcock 2022-01-14 17:13:44 +00:00
parent 67d6289d0e
commit 4eca618b77
7 changed files with 204 additions and 25 deletions

View file

@ -1,2 +1,4 @@
// Specifies how many subqueries will be processed recursively before the query fails. // Specifies how many subqueries will be processed recursively before the query fails.
pub const MAX_RECURSIVE_QUERIES: usize = 16; pub const MAX_RECURSIVE_QUERIES: usize = 16;
pub const APP_ENDPOINT: &'static str = "https://app.surrealdb.com";

View file

@ -147,3 +147,5 @@ pub enum Error {
#[error("CBOR Error: {0}")] #[error("CBOR Error: {0}")]
CborError(#[from] CborError), CborError(#[from] CborError),
} }
impl warp::reject::Reject for Error {}

108
src/web/fail.rs Normal file
View file

@ -0,0 +1,108 @@
use crate::err::Error;
use serde::Serialize;
use warp::http::StatusCode;
#[derive(Serialize)]
struct Message {
code: u16,
#[serde(skip_serializing_if = "Option::is_none")]
details: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
information: Option<String>,
}
pub async fn recover(err: warp::Rejection) -> Result<impl warp::Reply, warp::Rejection> {
if err.is_not_found() {
Ok(warp::reply::with_status(
warp::reply::json(&Message {
code: 404,
details: Some(format!("Requested resource not found")),
description: Some(format!("The requested resource does not exist. Check that you have entered the url correctly.")),
information: None,
}),
StatusCode::NOT_FOUND,
))
} else if let Some(err) = err.find::<Error>() {
Ok(warp::reply::with_status(
warp::reply::json(&Message {
code: 400,
details: Some(format!("Request problems detected")),
description: Some(format!("There is a problem with your request. Refer to the documentation for further information.")),
information: Some(err.to_string()),
}),
StatusCode::BAD_REQUEST,
))
} else if let Some(_) = err.find::<warp::reject::MethodNotAllowed>() {
Ok(warp::reply::with_status(
warp::reply::json(&Message {
code: 405,
details: Some(format!("Request content length too large")),
description: Some(format!("The requested http method is not allowed for this resource. Refer to the documentation for allowed methods.")),
information: None,
}),
StatusCode::METHOD_NOT_ALLOWED,
))
} else if let Some(_) = err.find::<warp::reject::PayloadTooLarge>() {
Ok(warp::reply::with_status(
warp::reply::json(&Message {
code: 413,
details: Some(format!("Request problems detected")),
description: Some(format!("The request has exceeded the maximum payload size. Refer to the documentation for the request limitations.")),
information: None,
}),
StatusCode::PAYLOAD_TOO_LARGE,
))
} else if let Some(_) = err.find::<warp::reject::UnsupportedMediaType>() {
Ok(warp::reply::with_status(
warp::reply::json(&Message {
code: 415,
details: Some(format!("Unsupported content type requested")),
description: Some(format!("The request needs to adhere to certain constraints. Refer to the documentation for supported content types.")),
information: None,
}),
StatusCode::UNSUPPORTED_MEDIA_TYPE,
))
} else if let Some(_) = err.find::<warp::reject::MissingHeader>() {
Ok(warp::reply::with_status(
warp::reply::json(&Message {
code: 412,
details: Some(format!("Request problems detected")),
description: Some(format!("The request appears to be missing a required header. Refer to the documentation for request requirements.")),
information: None,
}),
StatusCode::PRECONDITION_FAILED,
))
} else if let Some(_) = err.find::<warp::reject::InvalidQuery>() {
Ok(warp::reply::with_status(
warp::reply::json(&Message {
code: 501,
details: Some(format!("Not Implemented")),
description: Some(format!("The server either does not recognize the request method, or it lacks the ability to fulfill the request.")),
information: None,
}),
StatusCode::NOT_IMPLEMENTED,
))
} else if let Some(_) = err.find::<warp::reject::InvalidHeader>() {
Ok(warp::reply::with_status(
warp::reply::json(&Message {
code: 501,
details: Some(format!("Not Implemented")),
description: Some(format!("The server either does not recognize the request method, or it lacks the ability to fulfill the request.")),
information: None,
}),
StatusCode::NOT_IMPLEMENTED,
))
} else {
Ok(warp::reply::with_status(
warp::reply::json(&Message {
code: 500,
details: Some(format!("Internal server error")),
description: Some(format!("There was a problem with our servers, and we have been notified. Refer to the documentation for further information")),
information: None,
}),
StatusCode::INTERNAL_SERVER_ERROR,
))
}
}

View file

@ -32,6 +32,8 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
let base = warp::any(); let base = warp::any();
// Get session config // Get session config
let base = base.and(conf::build()); let base = base.and(conf::build());
// Get content type header
let base = base.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()));
// Set base path for all // Set base path for all
let base = base.and(path!("key" / String).and(warp::path::end())); let base = base.and(path!("key" / String).and(warp::path::end()));
// Set select method // Set select method
@ -55,6 +57,8 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
let base = warp::any(); let base = warp::any();
// Get session config // Get session config
let base = base.and(conf::build()); let base = base.and(conf::build());
// Get content type header
let base = base.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()));
// Set base path for one // Set base path for one
let base = base.and(path!("key" / String / String).and(warp::path::end())); let base = base.and(path!("key" / String / String).and(warp::path::end()));
// Set select method // Set select method
@ -96,6 +100,7 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
async fn select_all( async fn select_all(
session: Session, session: Session,
output: String,
table: String, table: String,
query: Query, query: Query,
) -> Result<impl warp::Reply, warp::Rejection> { ) -> Result<impl warp::Reply, warp::Rejection> {
@ -106,12 +111,19 @@ async fn select_all(
); );
let mut vars = HashMap::new(); let mut vars = HashMap::new();
vars.insert(String::from("table"), Value::from(table)); vars.insert(String::from("table"), Value::from(table));
let res = crate::dbs::execute(sql.as_str(), session, Some(vars)).await.unwrap(); match crate::dbs::execute(sql.as_str(), session, Some(vars)).await {
Ok(warp::reply::json(&res)) Ok(res) => match output.as_ref() {
"application/json" => Ok(warp::reply::json(&res)),
"application/cbor" => Ok(warp::reply::json(&res)),
_ => Err(warp::reject::not_found()),
},
Err(err) => Err(warp::reject::custom(err)),
}
} }
async fn create_all( async fn create_all(
session: Session, session: Session,
output: String,
table: String, table: String,
body: Bytes, body: Bytes,
) -> Result<impl warp::Reply, warp::Rejection> { ) -> Result<impl warp::Reply, warp::Rejection> {
@ -122,19 +134,35 @@ async fn create_all(
let mut vars = HashMap::new(); let mut vars = HashMap::new();
vars.insert(String::from("table"), Value::from(table)); vars.insert(String::from("table"), Value::from(table));
vars.insert(String::from("data"), Value::from(data)); vars.insert(String::from("data"), Value::from(data));
let res = crate::dbs::execute(sql, session, Some(vars)).await.unwrap(); match crate::dbs::execute(sql, session, Some(vars)).await {
Ok(warp::reply::json(&res)) Ok(res) => match output.as_ref() {
"application/json" => Ok(warp::reply::json(&res)),
"application/cbor" => Ok(warp::reply::json(&res)),
_ => Err(warp::reject::not_found()),
},
Err(err) => Err(warp::reject::custom(err)),
}
} }
Err(_) => todo!(), Err(_) => todo!(),
} }
} }
async fn delete_all(session: Session, table: String) -> Result<impl warp::Reply, warp::Rejection> { async fn delete_all(
session: Session,
output: String,
table: String,
) -> Result<impl warp::Reply, warp::Rejection> {
let sql = "DELETE type::table($table)"; let sql = "DELETE type::table($table)";
let mut vars = HashMap::new(); let mut vars = HashMap::new();
vars.insert(String::from("table"), Value::from(table)); vars.insert(String::from("table"), Value::from(table));
let res = crate::dbs::execute(sql, session, Some(vars)).await.unwrap(); match crate::dbs::execute(sql, session, Some(vars)).await {
Ok(warp::reply::json(&res)) Ok(res) => match output.as_ref() {
"application/json" => Ok(warp::reply::json(&res)),
"application/cbor" => Ok(warp::reply::json(&res)),
_ => Err(warp::reject::not_found()),
},
Err(err) => Err(warp::reject::custom(err)),
}
} }
// ------------------------------ // ------------------------------
@ -143,6 +171,7 @@ async fn delete_all(session: Session, table: String) -> Result<impl warp::Reply,
async fn select_one( async fn select_one(
session: Session, session: Session,
output: String,
table: String, table: String,
id: String, id: String,
) -> Result<impl warp::Reply, warp::Rejection> { ) -> Result<impl warp::Reply, warp::Rejection> {
@ -150,12 +179,19 @@ async fn select_one(
let mut vars = HashMap::new(); let mut vars = HashMap::new();
vars.insert(String::from("table"), Value::from(table)); vars.insert(String::from("table"), Value::from(table));
vars.insert(String::from("id"), Value::from(id)); vars.insert(String::from("id"), Value::from(id));
let res = crate::dbs::execute(sql, session, Some(vars)).await.unwrap(); match crate::dbs::execute(sql, session, Some(vars)).await {
Ok(warp::reply::json(&res)) Ok(res) => match output.as_ref() {
"application/json" => Ok(warp::reply::json(&res)),
"application/cbor" => Ok(warp::reply::json(&res)),
_ => Err(warp::reject::not_found()),
},
Err(err) => Err(warp::reject::custom(err)),
}
} }
async fn create_one( async fn create_one(
session: Session, session: Session,
output: String,
table: String, table: String,
id: String, id: String,
body: Bytes, body: Bytes,
@ -168,8 +204,14 @@ async fn create_one(
vars.insert(String::from("table"), Value::from(table)); vars.insert(String::from("table"), Value::from(table));
vars.insert(String::from("id"), Value::from(id)); vars.insert(String::from("id"), Value::from(id));
vars.insert(String::from("data"), Value::from(data)); vars.insert(String::from("data"), Value::from(data));
let res = crate::dbs::execute(sql, session, Some(vars)).await.unwrap(); match crate::dbs::execute(sql, session, Some(vars)).await {
Ok(warp::reply::json(&res)) Ok(res) => match output.as_ref() {
"application/json" => Ok(warp::reply::json(&res)),
"application/cbor" => Ok(warp::reply::json(&res)),
_ => Err(warp::reject::not_found()),
},
Err(err) => Err(warp::reject::custom(err)),
}
} }
Err(_) => todo!(), Err(_) => todo!(),
} }
@ -177,6 +219,7 @@ async fn create_one(
async fn update_one( async fn update_one(
session: Session, session: Session,
output: String,
table: String, table: String,
id: String, id: String,
body: Bytes, body: Bytes,
@ -189,8 +232,14 @@ async fn update_one(
vars.insert(String::from("table"), Value::from(table)); vars.insert(String::from("table"), Value::from(table));
vars.insert(String::from("id"), Value::from(id)); vars.insert(String::from("id"), Value::from(id));
vars.insert(String::from("data"), Value::from(data)); vars.insert(String::from("data"), Value::from(data));
let res = crate::dbs::execute(sql, session, Some(vars)).await.unwrap(); match crate::dbs::execute(sql, session, Some(vars)).await {
Ok(warp::reply::json(&res)) Ok(res) => match output.as_ref() {
"application/json" => Ok(warp::reply::json(&res)),
"application/cbor" => Ok(warp::reply::json(&res)),
_ => Err(warp::reject::not_found()),
},
Err(err) => Err(warp::reject::custom(err)),
}
} }
Err(_) => todo!(), Err(_) => todo!(),
} }
@ -198,6 +247,7 @@ async fn update_one(
async fn modify_one( async fn modify_one(
session: Session, session: Session,
output: String,
table: String, table: String,
id: String, id: String,
body: Bytes, body: Bytes,
@ -210,8 +260,14 @@ async fn modify_one(
vars.insert(String::from("table"), Value::from(table)); vars.insert(String::from("table"), Value::from(table));
vars.insert(String::from("id"), Value::from(id)); vars.insert(String::from("id"), Value::from(id));
vars.insert(String::from("data"), Value::from(data)); vars.insert(String::from("data"), Value::from(data));
let res = crate::dbs::execute(sql, session, Some(vars)).await.unwrap(); match crate::dbs::execute(sql, session, Some(vars)).await {
Ok(warp::reply::json(&res)) Ok(res) => match output.as_ref() {
"application/json" => Ok(warp::reply::json(&res)),
"application/cbor" => Ok(warp::reply::json(&res)),
_ => Err(warp::reject::not_found()),
},
Err(err) => Err(warp::reject::custom(err)),
}
} }
Err(_) => todo!(), Err(_) => todo!(),
} }
@ -219,6 +275,7 @@ async fn modify_one(
async fn delete_one( async fn delete_one(
session: Session, session: Session,
output: String,
table: String, table: String,
id: String, id: String,
) -> Result<impl warp::Reply, warp::Rejection> { ) -> Result<impl warp::Reply, warp::Rejection> {
@ -226,6 +283,12 @@ async fn delete_one(
let mut vars = HashMap::new(); let mut vars = HashMap::new();
vars.insert(String::from("table"), Value::from(table)); vars.insert(String::from("table"), Value::from(table));
vars.insert(String::from("id"), Value::from(id)); vars.insert(String::from("id"), Value::from(id));
let res = crate::dbs::execute(sql, session, Some(vars)).await.unwrap(); match crate::dbs::execute(sql, session, Some(vars)).await {
Ok(warp::reply::json(&res)) Ok(res) => match output.as_ref() {
"application/json" => Ok(warp::reply::json(&res)),
"application/cbor" => Ok(warp::reply::json(&res)),
_ => Err(warp::reject::not_found()),
},
Err(err) => Err(warp::reject::custom(err)),
}
} }

View file

@ -1,5 +1,6 @@
mod conf; mod conf;
mod export; mod export;
mod fail;
mod head; mod head;
mod import; mod import;
mod key; mod key;
@ -45,6 +46,8 @@ pub async fn init(conf: &clap::ArgMatches) -> Result<(), Error> {
.or(sql::config()) .or(sql::config())
// API query endpoint // API query endpoint
.or(key::config()) .or(key::config())
// Catch all errors
.recover(fail::recover)
// End routes setup // End routes setup
; ;

View file

@ -1,8 +1,7 @@
use crate::cnf;
use warp::http::Uri; use warp::http::Uri;
use warp::Filter; use warp::Filter;
pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone { pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path::end() warp::path::end().and(warp::get()).map(|| warp::redirect(Uri::from_static(cnf::APP_ENDPOINT)))
.and(warp::get())
.map(|| warp::redirect(Uri::from_static("https://app.surrealdb.com")))
} }

View file

@ -40,10 +40,12 @@ async fn handler(
sql: Bytes, sql: Bytes,
) -> Result<impl warp::Reply, warp::Rejection> { ) -> Result<impl warp::Reply, warp::Rejection> {
let sql = std::str::from_utf8(&sql).unwrap(); let sql = std::str::from_utf8(&sql).unwrap();
let res = crate::dbs::execute(sql, session, None).await.unwrap(); match crate::dbs::execute(sql, session, None).await {
match output.as_ref() { Ok(res) => match output.as_ref() {
"application/json" => Ok(warp::reply::json(&res)), "application/json" => Ok(warp::reply::json(&res)),
"application/cbor" => Ok(warp::reply::json(&res)), "application/cbor" => Ok(warp::reply::json(&res)),
_ => Err(warp::reject::not_found()), _ => Err(warp::reject::not_found()),
},
Err(err) => Err(warp::reject::custom(err)),
} }
} }