Implement http SQL functions
This commit is contained in:
parent
51b4ab6ee5
commit
c9ad4e60ce
5 changed files with 1027 additions and 41 deletions
725
Cargo.lock
generated
725
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."),
|
||||
|
|
|
@ -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())),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue