Implement http SQL functions

This commit is contained in:
Tobie Morgan Hitchcock 2022-07-17 17:17:02 +01:00
parent 51b4ab6ee5
commit c9ad4e60ce
5 changed files with 1027 additions and 41 deletions

725
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,13 +6,14 @@ version = "0.1.0"
authors = ["Tobie Morgan Hitchcock <tobie@surrealdb.com>"]
[features]
default = ["parallel", "kv-tikv", "kv-echodb", "kv-yokudb", "scripting"]
default = ["parallel", "kv-tikv", "kv-echodb", "kv-yokudb", "scripting", "http"]
parallel = ["executor"]
kv-tikv = ["tikv"]
kv-echodb = ["echodb"]
kv-indxdb = ["indxdb"]
kv-yokudb = []
scripting = ["js", "executor"]
http = ["surf"]
[dependencies]
argon2 = "0.4.1"
@ -45,6 +46,7 @@ serde = { version = "1.0.138", features = ["derive"] }
sha-1 = "0.10.0"
sha2 = "0.10.2"
storekey = "0.3.0"
surf = { version = "2.3.2", optional = true }
thiserror = "1.0.31"
tikv = { version = "0.1.0", package = "tikv-client", optional = true }
trice = "0.1.0"

View file

@ -225,6 +225,10 @@ pub enum Error {
value: String,
},
/// There was an error processing a remote HTTP request
#[error("There was an error processing a remote HTTP request")]
Http(String),
/// There was an error processing a value in parallel
#[error("There was an error processing a value in parallel")]
Channel(String),
@ -281,6 +285,12 @@ impl<T> From<channel::SendError<T>> for Error {
}
}
impl From<surf::Error> for Error {
fn from(e: surf::Error) -> Error {
Error::Http(e.to_string())
}
}
impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where

View file

@ -1,10 +1,31 @@
use crate::ctx::Context;
use crate::err::Error;
use crate::fnc::util::http;
use crate::sql::object::Object;
use crate::sql::value::Value;
pub async fn head(_ctx: &Context<'_>, args: Vec<Value>) -> Result<Value, Error> {
pub async fn head(_: &Context<'_>, mut args: Vec<Value>) -> Result<Value, Error> {
match args.len() {
1 | 2 => todo!(),
2 => match args.remove(0) {
Value::Strand(uri) => match args.remove(0) {
Value::Object(opt) => http::head(uri, opt).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::head"),
message: String::from("The second argument should be an object."),
}),
},
_ => Err(Error::InvalidArguments {
name: String::from("http::head"),
message: String::from("The first argument should be a string."),
}),
},
1 => match args.remove(0) {
Value::Strand(uri) => http::head(uri, Object::default()).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::head"),
message: String::from("The first argument should be a string."),
}),
},
_ => Err(Error::InvalidArguments {
name: String::from("http::head"),
message: String::from("The function expects 1 or 2 arguments."),
@ -12,9 +33,28 @@ pub async fn head(_ctx: &Context<'_>, args: Vec<Value>) -> Result<Value, Error>
}
}
pub async fn get(_ctx: &Context<'_>, args: Vec<Value>) -> Result<Value, Error> {
pub async fn get(_: &Context<'_>, mut args: Vec<Value>) -> Result<Value, Error> {
match args.len() {
1 | 2 => todo!(),
2 => match args.remove(0) {
Value::Strand(uri) => match args.remove(0) {
Value::Object(opt) => http::get(uri, opt).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::get"),
message: String::from("The second argument should be an object."),
}),
},
_ => Err(Error::InvalidArguments {
name: String::from("http::get"),
message: String::from("The first argument should be a string."),
}),
},
1 => match args.remove(0) {
Value::Strand(uri) => http::get(uri, Object::default()).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::get"),
message: String::from("The first argument should be a string."),
}),
},
_ => Err(Error::InvalidArguments {
name: String::from("http::get"),
message: String::from("The function expects 1 or 2 arguments."),
@ -22,9 +62,35 @@ pub async fn get(_ctx: &Context<'_>, args: Vec<Value>) -> Result<Value, Error> {
}
}
pub async fn put(_ctx: &Context<'_>, args: Vec<Value>) -> Result<Value, Error> {
pub async fn put(_: &Context<'_>, mut args: Vec<Value>) -> Result<Value, Error> {
match args.len() {
1 | 2 | 3 => todo!(),
3 => match (args.remove(0), args.remove(0)) {
(Value::Strand(uri), val) => match args.remove(0) {
Value::Object(opts) => http::put(uri, val, opts).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::put"),
message: String::from("The third argument should be an object."),
}),
},
_ => Err(Error::InvalidArguments {
name: String::from("http::put"),
message: String::from("The first argument should be a string."),
}),
},
2 => match (args.remove(0), args.remove(0)) {
(Value::Strand(uri), val) => http::put(uri, val, Object::default()).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::put"),
message: String::from("The first argument should be a string."),
}),
},
1 => match args.remove(0) {
Value::Strand(uri) => http::put(uri, Value::Null, Object::default()).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::put"),
message: String::from("The first argument should be a string."),
}),
},
_ => Err(Error::InvalidArguments {
name: String::from("http::put"),
message: String::from("The function expects 1, 2, or 3 arguments."),
@ -32,9 +98,35 @@ pub async fn put(_ctx: &Context<'_>, args: Vec<Value>) -> Result<Value, Error> {
}
}
pub async fn post(_ctx: &Context<'_>, args: Vec<Value>) -> Result<Value, Error> {
pub async fn post(_: &Context<'_>, mut args: Vec<Value>) -> Result<Value, Error> {
match args.len() {
1 | 2 | 3 => todo!(),
3 => match (args.remove(0), args.remove(0)) {
(Value::Strand(uri), val) => match args.remove(0) {
Value::Object(opts) => http::post(uri, val, opts).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::post"),
message: String::from("The third argument should be an object."),
}),
},
_ => Err(Error::InvalidArguments {
name: String::from("http::post"),
message: String::from("The first argument should be a string."),
}),
},
2 => match (args.remove(0), args.remove(0)) {
(Value::Strand(uri), val) => http::post(uri, val, Object::default()).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::post"),
message: String::from("The first argument should be a string."),
}),
},
1 => match args.remove(0) {
Value::Strand(uri) => http::post(uri, Value::Null, Object::default()).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::post"),
message: String::from("The first argument should be a string."),
}),
},
_ => Err(Error::InvalidArguments {
name: String::from("http::post"),
message: String::from("The function expects 1, 2, or 3 arguments."),
@ -42,9 +134,35 @@ pub async fn post(_ctx: &Context<'_>, args: Vec<Value>) -> Result<Value, Error>
}
}
pub async fn patch(_ctx: &Context<'_>, args: Vec<Value>) -> Result<Value, Error> {
pub async fn patch(_: &Context<'_>, mut args: Vec<Value>) -> Result<Value, Error> {
match args.len() {
1 | 2 | 3 => todo!(),
3 => match (args.remove(0), args.remove(0)) {
(Value::Strand(uri), val) => match args.remove(0) {
Value::Object(opts) => http::patch(uri, val, opts).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::patch"),
message: String::from("The third argument should be an object."),
}),
},
_ => Err(Error::InvalidArguments {
name: String::from("http::patch"),
message: String::from("The first argument should be a string."),
}),
},
2 => match (args.remove(0), args.remove(0)) {
(Value::Strand(uri), val) => http::patch(uri, val, Object::default()).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::patch"),
message: String::from("The first argument should be a string."),
}),
},
1 => match args.remove(0) {
Value::Strand(uri) => http::patch(uri, Value::Null, Object::default()).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::patch"),
message: String::from("The first argument should be a string."),
}),
},
_ => Err(Error::InvalidArguments {
name: String::from("http::patch"),
message: String::from("The function expects 1, 2, or 3 arguments."),
@ -52,9 +170,28 @@ pub async fn patch(_ctx: &Context<'_>, args: Vec<Value>) -> Result<Value, Error>
}
}
pub async fn delete(_ctx: &Context<'_>, args: Vec<Value>) -> Result<Value, Error> {
pub async fn delete(_: &Context<'_>, mut args: Vec<Value>) -> Result<Value, Error> {
match args.len() {
1 | 2 => todo!(),
2 => match args.remove(0) {
Value::Strand(uri) => match args.remove(0) {
Value::Object(opt) => http::delete(uri, opt).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::delete"),
message: String::from("The second argument should be an object."),
}),
},
_ => Err(Error::InvalidArguments {
name: String::from("http::delete"),
message: String::from("The first argument should be a string."),
}),
},
1 => match args.remove(0) {
Value::Strand(uri) => http::delete(uri, Object::default()).await,
_ => Err(Error::InvalidArguments {
name: String::from("http::delete"),
message: String::from("The first argument should be a string."),
}),
},
_ => Err(Error::InvalidArguments {
name: String::from("http::delete"),
message: String::from("The function expects 1 or 2 arguments."),

View file

@ -1 +1,169 @@
use crate::err::Error;
use crate::sql::json;
use crate::sql::object::Object;
use crate::sql::strand::Strand;
use crate::sql::value::Value;
pub async fn head(uri: Strand, opts: Object) -> Result<Value, Error> {
// Start a new HEAD request
let mut req = surf::head(uri.as_str());
// Add the User-Agent header
req = req.header("User-Agent", "SurrealDB");
// Add specified header values
for (k, v) in opts.iter() {
req = req.header(k.as_str(), v.to_strand().as_str());
}
// Send the request and wait
let res = req.send().await?;
// Check the response status
match res.status() {
s if s.is_success() => Ok(Value::None),
s => Err(Error::Http(s.canonical_reason().to_owned())),
}
}
pub async fn get(uri: Strand, opts: Object) -> Result<Value, Error> {
// Start a new GET request
let mut req = surf::get(uri.as_str());
// Add the User-Agent header
req = req.header("User-Agent", "SurrealDB");
// Add specified header values
for (k, v) in opts.iter() {
req = req.header(k.as_str(), v.to_strand().as_str());
}
// Send the request and wait
let mut res = req.send().await?;
// Check the response status
match res.status() {
s if s.is_success() => match res.content_type() {
Some(mime) if mime.essence() == "application/json" => {
let txt = res.body_string().await?;
let val = json(&txt)?;
Ok(val)
}
_ => {
let txt = res.body_string().await?;
Ok(txt.into())
}
},
s => Err(Error::Http(s.canonical_reason().to_owned())),
}
}
pub async fn put(uri: Strand, body: Value, opts: Object) -> Result<Value, Error> {
// Start a new GET request
let mut req = surf::put(uri.as_str());
// Add the User-Agent header
req = req.header("User-Agent", "SurrealDB");
// Add specified header values
for (k, v) in opts.iter() {
req = req.header(k.as_str(), v.to_strand().as_str());
}
// Submit the request body
req = req.body_json(&body)?;
// Send the request and wait
let mut res = req.send().await?;
// Check the response status
match res.status() {
s if s.is_success() => match res.content_type() {
Some(mime) if mime.essence() == "application/json" => {
let txt = res.body_string().await?;
let val = json(&txt)?;
Ok(val)
}
_ => {
let txt = res.body_string().await?;
Ok(txt.into())
}
},
s => Err(Error::Http(s.canonical_reason().to_owned())),
}
}
pub async fn post(uri: Strand, body: Value, opts: Object) -> Result<Value, Error> {
// Start a new GET request
let mut req = surf::post(uri.as_str());
// Add the User-Agent header
req = req.header("User-Agent", "SurrealDB");
// Add specified header values
for (k, v) in opts.iter() {
req = req.header(k.as_str(), v.to_strand().as_str());
}
// Submit the request body
req = req.body_json(&body)?;
// Send the request and wait
let mut res = req.send().await?;
// Check the response status
match res.status() {
s if s.is_success() => match res.content_type() {
Some(mime) if mime.essence() == "application/json" => {
let txt = res.body_string().await?;
let val = json(&txt)?;
Ok(val)
}
_ => {
let txt = res.body_string().await?;
Ok(txt.into())
}
},
s => Err(Error::Http(s.canonical_reason().to_owned())),
}
}
pub async fn patch(uri: Strand, body: Value, opts: Object) -> Result<Value, Error> {
// Start a new GET request
let mut req = surf::patch(uri.as_str());
// Add the User-Agent header
req = req.header("User-Agent", "SurrealDB");
// Add specified header values
for (k, v) in opts.iter() {
req = req.header(k.as_str(), v.to_strand().as_str());
}
// Submit the request body
req = req.body_json(&body)?;
// Send the request and wait
let mut res = req.send().await?;
// Check the response status
match res.status() {
s if s.is_success() => match res.content_type() {
Some(mime) if mime.essence() == "application/json" => {
let txt = res.body_string().await?;
let val = json(&txt)?;
Ok(val)
}
_ => {
let txt = res.body_string().await?;
Ok(txt.into())
}
},
s => Err(Error::Http(s.canonical_reason().to_owned())),
}
}
pub async fn delete(uri: Strand, opts: Object) -> Result<Value, Error> {
// Start a new GET request
let mut req = surf::delete(uri.as_str());
// Add the User-Agent header
req = req.header("User-Agent", "SurrealDB");
// Add specified header values
for (k, v) in opts.iter() {
req = req.header(k.as_str(), v.to_strand().as_str());
}
// Send the request and wait
let mut res = req.send().await?;
// Check the response status
match res.status() {
s if s.is_success() => match res.content_type() {
Some(mime) if mime.essence() == "application/json" => {
let txt = res.body_string().await?;
let val = json(&txt)?;
Ok(val)
}
_ => {
let txt = res.body_string().await?;
Ok(txt.into())
}
},
s => Err(Error::Http(s.canonical_reason().to_owned())),
}
}