surrealpatch/src/net/key.rs
2024-09-03 14:01:53 +01:00

515 lines
17 KiB
Rust

use super::headers::Accept;
use super::AppState;
use crate::cnf::HTTP_MAX_KEY_BODY_SIZE;
use crate::err::Error;
use crate::net::input::bytes_to_utf8;
use crate::net::output;
use crate::net::params::Params;
use axum::extract::{DefaultBodyLimit, Path};
use axum::response::IntoResponse;
use axum::routing::options;
use axum::Extension;
use axum::Router;
use axum_extra::extract::Query;
use axum_extra::TypedHeader;
use bytes::Bytes;
use serde::Deserialize;
use std::str;
use surrealdb::dbs::Session;
use surrealdb::iam::check::check_ns_db;
use surrealdb::sql::Value;
use tower_http::limit::RequestBodyLimitLayer;
#[derive(Default, Deserialize, Debug, Clone)]
struct QueryOptions {
pub limit: Option<i64>,
pub start: Option<i64>,
pub fields: Option<Vec<String>>,
}
pub(super) fn router<S>() -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
Router::new()
.route(
"/key/:table",
options(|| async {})
.get(select_all)
.post(create_all)
.put(update_all)
.patch(modify_all)
.delete(delete_all),
)
.route_layer(DefaultBodyLimit::disable())
.layer(RequestBodyLimitLayer::new(*HTTP_MAX_KEY_BODY_SIZE))
.merge(
Router::new()
.route(
"/key/:table/:key",
options(|| async {})
.get(select_one)
.post(create_one)
.put(update_one)
.patch(modify_one)
.delete(delete_one),
)
.route_layer(DefaultBodyLimit::disable())
.layer(RequestBodyLimitLayer::new(*HTTP_MAX_KEY_BODY_SIZE)),
)
}
// ------------------------------
// Routes for a table
// ------------------------------
async fn select_all(
Extension(state): Extension<AppState>,
Extension(session): Extension<Session>,
accept: Option<TypedHeader<Accept>>,
Path(table): Path<String>,
Query(query): Query<QueryOptions>,
) -> Result<impl IntoResponse, impl IntoResponse> {
// Get the datastore reference
let db = &state.datastore;
// Ensure a NS and DB are set
let _ = check_ns_db(&session)?;
// Specify the request statement
let sql = match query.fields {
None => "SELECT * FROM type::table($table) LIMIT $limit START $start",
_ => "SELECT type::fields($fields) FROM type::table($table) LIMIT $limit START $start",
};
// Specify the request variables
let vars = map! {
String::from("table") => Value::from(table),
String::from("start") => Value::from(query.start.unwrap_or(0)),
String::from("limit") => Value::from(query.limit.unwrap_or(100)),
String::from("fields") => Value::from(query.fields.unwrap_or_default()),
};
// Execute the query and return the result
match db.execute(sql, &session, Some(vars)).await {
Ok(res) => match accept.as_deref() {
// Simple serialization
Some(Accept::ApplicationJson) => Ok(output::json(&output::simplify(res))),
Some(Accept::ApplicationCbor) => Ok(output::cbor(&output::simplify(res))),
Some(Accept::ApplicationPack) => Ok(output::pack(&output::simplify(res))),
// Internal serialization
// TODO: remove format in 2.0.0
Some(Accept::Surrealdb) => Ok(output::full(&res)),
// An incorrect content-type was requested
_ => Err(Error::InvalidType),
},
// There was an error when executing the query
Err(err) => Err(Error::from(err)),
}
}
async fn create_all(
Extension(state): Extension<AppState>,
Extension(session): Extension<Session>,
accept: Option<TypedHeader<Accept>>,
Path(table): Path<String>,
Query(params): Query<Params>,
body: Bytes,
) -> Result<impl IntoResponse, impl IntoResponse> {
// Get the datastore reference
let db = &state.datastore;
// Ensure a NS and DB are set
let _ = check_ns_db(&session)?;
// Convert the HTTP request body
let data = bytes_to_utf8(&body)?;
// Parse the request body as JSON
match surrealdb::sql::value(data) {
Ok(data) => {
// Specify the request statement
let sql = "CREATE type::table($table) CONTENT $data";
// Specify the request variables
let vars = map! {
String::from("table") => Value::from(table),
String::from("data") => data,
=> params.parse()
};
// Execute the query and return the result
match db.execute(sql, &session, Some(vars)).await {
Ok(res) => match accept.as_deref() {
// Simple serialization
Some(Accept::ApplicationJson) => Ok(output::json(&output::simplify(res))),
Some(Accept::ApplicationCbor) => Ok(output::cbor(&output::simplify(res))),
Some(Accept::ApplicationPack) => Ok(output::pack(&output::simplify(res))),
// Internal serialization
Some(Accept::Surrealdb) => Ok(output::full(&res)),
// An incorrect content-type was requested
_ => Err(Error::InvalidType),
},
// There was an error when executing the query
Err(err) => Err(Error::from(err)),
}
}
Err(_) => Err(Error::Request),
}
}
async fn update_all(
Extension(state): Extension<AppState>,
Extension(session): Extension<Session>,
accept: Option<TypedHeader<Accept>>,
Path(table): Path<String>,
Query(params): Query<Params>,
body: Bytes,
) -> Result<impl IntoResponse, impl IntoResponse> {
// Get the datastore reference
let db = &state.datastore;
// Ensure a NS and DB are set
let _ = check_ns_db(&session)?;
// Convert the HTTP request body
let data = bytes_to_utf8(&body)?;
// Parse the request body as JSON
match surrealdb::sql::value(data) {
Ok(data) => {
// Specify the request statement
let sql = "UPSERT type::table($table) CONTENT $data";
// Specify the request variables
let vars = map! {
String::from("table") => Value::from(table),
String::from("data") => data,
=> params.parse()
};
// Execute the query and return the result
match db.execute(sql, &session, Some(vars)).await {
Ok(res) => match accept.as_deref() {
// Simple serialization
Some(Accept::ApplicationJson) => Ok(output::json(&output::simplify(res))),
Some(Accept::ApplicationCbor) => Ok(output::cbor(&output::simplify(res))),
Some(Accept::ApplicationPack) => Ok(output::pack(&output::simplify(res))),
// Internal serialization
Some(Accept::Surrealdb) => Ok(output::full(&res)),
// An incorrect content-type was requested
_ => Err(Error::InvalidType),
},
// There was an error when executing the query
Err(err) => Err(Error::from(err)),
}
}
Err(_) => Err(Error::Request),
}
}
async fn modify_all(
Extension(state): Extension<AppState>,
Extension(session): Extension<Session>,
accept: Option<TypedHeader<Accept>>,
Path(table): Path<String>,
Query(params): Query<Params>,
body: Bytes,
) -> Result<impl IntoResponse, impl IntoResponse> {
// Get the datastore reference
let db = &state.datastore;
// Ensure a NS and DB are set
let _ = check_ns_db(&session)?;
// Convert the HTTP request body
let data = bytes_to_utf8(&body)?;
// Parse the request body as JSON
match surrealdb::sql::value(data) {
Ok(data) => {
// Specify the request statement
let sql = "UPSERT type::table($table) MERGE $data";
// Specify the request variables
let vars = map! {
String::from("table") => Value::from(table),
String::from("data") => data,
=> params.parse()
};
// Execute the query and return the result
match db.execute(sql, &session, Some(vars)).await {
Ok(res) => match accept.as_deref() {
// Simple serialization
Some(Accept::ApplicationJson) => Ok(output::json(&output::simplify(res))),
Some(Accept::ApplicationCbor) => Ok(output::cbor(&output::simplify(res))),
Some(Accept::ApplicationPack) => Ok(output::pack(&output::simplify(res))),
// Internal serialization
Some(Accept::Surrealdb) => Ok(output::full(&res)),
// An incorrect content-type was requested
_ => Err(Error::InvalidType),
},
// There was an error when executing the query
Err(err) => Err(Error::from(err)),
}
}
Err(_) => Err(Error::Request),
}
}
async fn delete_all(
Extension(state): Extension<AppState>,
Extension(session): Extension<Session>,
accept: Option<TypedHeader<Accept>>,
Path(table): Path<String>,
Query(params): Query<Params>,
) -> Result<impl IntoResponse, impl IntoResponse> {
// Get the datastore reference
let db = &state.datastore;
// Ensure a NS and DB are set
let _ = check_ns_db(&session)?;
// Specify the request statement
let sql = "DELETE type::table($table) RETURN BEFORE";
// Specify the request variables
let vars = map! {
String::from("table") => Value::from(table),
=> params.parse()
};
// Execute the query and return the result
match db.execute(sql, &session, Some(vars)).await {
Ok(res) => match accept.as_deref() {
// Simple serialization
Some(Accept::ApplicationJson) => Ok(output::json(&output::simplify(res))),
Some(Accept::ApplicationCbor) => Ok(output::cbor(&output::simplify(res))),
Some(Accept::ApplicationPack) => Ok(output::pack(&output::simplify(res))),
// Internal serialization
Some(Accept::Surrealdb) => Ok(output::full(&res)),
// An incorrect content-type was requested
_ => Err(Error::InvalidType),
},
// There was an error when executing the query
Err(err) => Err(Error::from(err)),
}
}
// ------------------------------
// Routes for a thing
// ------------------------------
async fn select_one(
Extension(state): Extension<AppState>,
Extension(session): Extension<Session>,
accept: Option<TypedHeader<Accept>>,
Path((table, id)): Path<(String, String)>,
Query(query): Query<QueryOptions>,
) -> Result<impl IntoResponse, impl IntoResponse> {
// Get the datastore reference
let db = &state.datastore;
// Ensure a NS and DB are set
let _ = check_ns_db(&session)?;
// Specify the request statement
let sql = match query.fields {
None => "SELECT * FROM type::thing($table, $id)",
_ => "SELECT type::fields($fields) FROM type::thing($table, $id)",
};
// Parse the Record ID as a SurrealQL value
let rid = match surrealdb::sql::json(&id) {
Ok(id) => id,
Err(_) => Value::from(id),
};
// Specify the request variables
let vars = map! {
String::from("table") => Value::from(table),
String::from("id") => rid,
String::from("fields") => Value::from(query.fields.unwrap_or_default()),
};
// Execute the query and return the result
match db.execute(sql, &session, Some(vars)).await {
Ok(res) => match accept.as_deref() {
// Simple serialization
Some(Accept::ApplicationJson) => Ok(output::json(&output::simplify(res))),
Some(Accept::ApplicationCbor) => Ok(output::cbor(&output::simplify(res))),
Some(Accept::ApplicationPack) => Ok(output::pack(&output::simplify(res))),
// Internal serialization
Some(Accept::Surrealdb) => Ok(output::full(&res)),
// An incorrect content-type was requested
_ => Err(Error::InvalidType),
},
// There was an error when executing the query
Err(err) => Err(Error::from(err)),
}
}
async fn create_one(
Extension(state): Extension<AppState>,
Extension(session): Extension<Session>,
accept: Option<TypedHeader<Accept>>,
Query(params): Query<Params>,
Path((table, id)): Path<(String, String)>,
body: Bytes,
) -> Result<impl IntoResponse, impl IntoResponse> {
// Get the datastore reference
let db = &state.datastore;
// Ensure a NS and DB are set
let _ = check_ns_db(&session)?;
// Convert the HTTP request body
let data = bytes_to_utf8(&body)?;
// Parse the Record ID as a SurrealQL value
let rid = match surrealdb::sql::json(&id) {
Ok(id) => id,
Err(_) => Value::from(id),
};
// Parse the request body as JSON
match surrealdb::sql::value(data) {
Ok(data) => {
// Specify the request statement
let sql = "CREATE type::thing($table, $id) CONTENT $data";
// Specify the request variables
let vars = map! {
String::from("table") => Value::from(table),
String::from("id") => rid,
String::from("data") => data,
=> params.parse()
};
// Execute the query and return the result
match db.execute(sql, &session, Some(vars)).await {
Ok(res) => match accept.as_deref() {
// Simple serialization
Some(Accept::ApplicationJson) => Ok(output::json(&output::simplify(res))),
Some(Accept::ApplicationCbor) => Ok(output::cbor(&output::simplify(res))),
Some(Accept::ApplicationPack) => Ok(output::pack(&output::simplify(res))),
// Internal serialization
Some(Accept::Surrealdb) => Ok(output::full(&res)),
// An incorrect content-type was requested
_ => Err(Error::InvalidType),
},
// There was an error when executing the query
Err(err) => Err(Error::from(err)),
}
}
Err(_) => Err(Error::Request),
}
}
async fn update_one(
Extension(state): Extension<AppState>,
Extension(session): Extension<Session>,
accept: Option<TypedHeader<Accept>>,
Query(params): Query<Params>,
Path((table, id)): Path<(String, String)>,
body: Bytes,
) -> Result<impl IntoResponse, impl IntoResponse> {
// Get the datastore reference
let db = &state.datastore;
// Ensure a NS and DB are set
let _ = check_ns_db(&session)?;
// Convert the HTTP request body
let data = bytes_to_utf8(&body)?;
// Parse the Record ID as a SurrealQL value
let rid = match surrealdb::sql::json(&id) {
Ok(id) => id,
Err(_) => Value::from(id),
};
// Parse the request body as JSON
match surrealdb::sql::value(data) {
Ok(data) => {
// Specify the request statement
let sql = "UPSERT type::thing($table, $id) CONTENT $data";
// Specify the request variables
let vars = map! {
String::from("table") => Value::from(table),
String::from("id") => rid,
String::from("data") => data,
=> params.parse()
};
// Execute the query and return the result
match db.execute(sql, &session, Some(vars)).await {
Ok(res) => match accept.as_deref() {
// Simple serialization
Some(Accept::ApplicationJson) => Ok(output::json(&output::simplify(res))),
Some(Accept::ApplicationCbor) => Ok(output::cbor(&output::simplify(res))),
Some(Accept::ApplicationPack) => Ok(output::pack(&output::simplify(res))),
// Internal serialization
Some(Accept::Surrealdb) => Ok(output::full(&res)),
// An incorrect content-type was requested
_ => Err(Error::InvalidType),
},
// There was an error when executing the query
Err(err) => Err(Error::from(err)),
}
}
Err(_) => Err(Error::Request),
}
}
async fn modify_one(
Extension(state): Extension<AppState>,
Extension(session): Extension<Session>,
accept: Option<TypedHeader<Accept>>,
Query(params): Query<Params>,
Path((table, id)): Path<(String, String)>,
body: Bytes,
) -> Result<impl IntoResponse, impl IntoResponse> {
// Get the datastore reference
let db = &state.datastore;
// Ensure a NS and DB are set
let _ = check_ns_db(&session)?;
// Convert the HTTP request body
let data = bytes_to_utf8(&body)?;
// Parse the Record ID as a SurrealQL value
let rid = match surrealdb::sql::json(&id) {
Ok(id) => id,
Err(_) => Value::from(id),
};
// Parse the request body as JSON
match surrealdb::sql::value(data) {
Ok(data) => {
// Specify the request statement
let sql = "UPSERT type::thing($table, $id) MERGE $data";
// Specify the request variables
let vars = map! {
String::from("table") => Value::from(table),
String::from("id") => rid,
String::from("data") => data,
=> params.parse()
};
// Execute the query and return the result
match db.execute(sql, &session, Some(vars)).await {
Ok(res) => match accept.as_deref() {
// Simple serialization
Some(Accept::ApplicationJson) => Ok(output::json(&output::simplify(res))),
Some(Accept::ApplicationCbor) => Ok(output::cbor(&output::simplify(res))),
Some(Accept::ApplicationPack) => Ok(output::pack(&output::simplify(res))),
// Internal serialization
Some(Accept::Surrealdb) => Ok(output::full(&res)),
// An incorrect content-type was requested
_ => Err(Error::InvalidType),
},
// There was an error when executing the query
Err(err) => Err(Error::from(err)),
}
}
Err(_) => Err(Error::Request),
}
}
async fn delete_one(
Extension(state): Extension<AppState>,
Extension(session): Extension<Session>,
accept: Option<TypedHeader<Accept>>,
Path((table, id)): Path<(String, String)>,
) -> Result<impl IntoResponse, impl IntoResponse> {
// Get the datastore reference
let db = &state.datastore;
// Ensure a NS and DB are set
let _ = check_ns_db(&session)?;
// Specify the request statement
let sql = "DELETE type::thing($table, $id) RETURN BEFORE";
// Parse the Record ID as a SurrealQL value
let rid = match surrealdb::sql::json(&id) {
Ok(id) => id,
Err(_) => Value::from(id),
};
// Specify the request variables
let vars = map! {
String::from("table") => Value::from(table),
String::from("id") => rid,
};
// Execute the query and return the result
match db.execute(sql, &session, Some(vars)).await {
Ok(res) => match accept.as_deref() {
// Simple serialization
Some(Accept::ApplicationJson) => Ok(output::json(&output::simplify(res))),
Some(Accept::ApplicationCbor) => Ok(output::cbor(&output::simplify(res))),
Some(Accept::ApplicationPack) => Ok(output::pack(&output::simplify(res))),
// Internal serialization
Some(Accept::Surrealdb) => Ok(output::full(&res)),
// An incorrect content-type was requested
_ => Err(Error::InvalidType),
},
// There was an error when executing the query
Err(err) => Err(Error::from(err)),
}
}