use crate::ctx::Context;
use crate::err::Error;
use crate::syn;
use crate::{Bytes, Object, Strand, Value};
use reqwest::header::CONTENT_TYPE;
use reqwest::{Client, RequestBuilder, Response};
use url::Url;
pub(crate) fn uri_is_valid(uri: &str) -> bool {
reqwest::Url::parse(uri).is_ok()
}
fn encode_body(req: RequestBuilder, body: Value) -> RequestBuilder {
match body {
Value::Bytes(bytes) => req.header(CONTENT_TYPE, "application/octet-stream").body(bytes.0),
_ if body.is_some() => req.json(&body.into_json()),
_ => req,
}
}
async fn decode_response(res: Response) -> Result<Value, Error> {
match res.status() {
s if s.is_success() => match res.headers().get(CONTENT_TYPE) {
Some(mime) => match mime.to_str() {
Ok(v) if v.starts_with("application/json") => {
let txt = res.text().await?;
let val = syn::json(&txt)?;
Ok(val)
}
Ok(v) if v.starts_with("application/octet-stream") => {
let bytes = res.bytes().await?;
Ok(Value::Bytes(Bytes(bytes.into())))
}
Ok(v) if v.starts_with("text") => {
let txt = res.text().await?;
let val = txt.into();
Ok(val)
}
_ => Ok(Value::None),
},
_ => Ok(Value::None),
},
s => Err(Error::Http(s.canonical_reason().unwrap_or_default().to_owned())),
}
}
pub async fn head(ctx: &Context<'_>, uri: Strand, opts: impl Into<Object>) -> Result<Value, Error> {
let url = Url::parse(&uri).map_err(|_| Error::InvalidUrl(uri.to_string()))?;
ctx.check_allowed_net(&url)?;
let cli = Client::builder().build()?;
let mut req = cli.head(url);
if cfg!(not(target_arch = "wasm32")) {
req = req.header("User-Agent", "SurrealDB");
}
for (k, v) in opts.into().iter() {
req = req.header(k.as_str(), v.to_raw_string());
}
let res = match ctx.timeout() {
#[cfg(not(target_arch = "wasm32"))]
Some(d) => req.timeout(d).send().await?,
_ => req.send().await?,
};
match res.status() {
s if s.is_success() => Ok(Value::None),
s => Err(Error::Http(s.canonical_reason().unwrap_or_default().to_owned())),
}
}
pub async fn get(ctx: &Context<'_>, uri: Strand, opts: impl Into<Object>) -> Result<Value, Error> {
let url = Url::parse(&uri).map_err(|_| Error::InvalidUrl(uri.to_string()))?;
ctx.check_allowed_net(&url)?;
let cli = Client::builder().build()?;
let mut req = cli.get(url);
if cfg!(not(target_arch = "wasm32")) {
req = req.header("User-Agent", "SurrealDB");
}
for (k, v) in opts.into().iter() {
req = req.header(k.as_str(), v.to_raw_string());
}
let res = match ctx.timeout() {
#[cfg(not(target_arch = "wasm32"))]
Some(d) => req.timeout(d).send().await?,
_ => req.send().await?,
};
decode_response(res).await
}
pub async fn put(
ctx: &Context<'_>,
uri: Strand,
body: Value,
opts: impl Into<Object>,
) -> Result<Value, Error> {
let url = Url::parse(&uri).map_err(|_| Error::InvalidUrl(uri.to_string()))?;
ctx.check_allowed_net(&url)?;
let cli = Client::builder().build()?;
let mut req = cli.put(url);
if cfg!(not(target_arch = "wasm32")) {
req = req.header("User-Agent", "SurrealDB");
}
for (k, v) in opts.into().iter() {
req = req.header(k.as_str(), v.to_raw_string());
}
req = encode_body(req, body);
let res = match ctx.timeout() {
#[cfg(not(target_arch = "wasm32"))]
Some(d) => req.timeout(d).send().await?,
_ => req.send().await?,
};
decode_response(res).await
}
pub async fn post(
ctx: &Context<'_>,
uri: Strand,
body: Value,
opts: impl Into<Object>,
) -> Result<Value, Error> {
let url = Url::parse(&uri).map_err(|_| Error::InvalidUrl(uri.to_string()))?;
ctx.check_allowed_net(&url)?;
let cli = Client::builder().build()?;
let mut req = cli.post(url);
if cfg!(not(target_arch = "wasm32")) {
req = req.header("User-Agent", "SurrealDB");
}
for (k, v) in opts.into().iter() {
req = req.header(k.as_str(), v.to_raw_string());
}
req = encode_body(req, body);
let res = match ctx.timeout() {
#[cfg(not(target_arch = "wasm32"))]
Some(d) => req.timeout(d).send().await?,
_ => req.send().await?,
};
decode_response(res).await
}
pub async fn patch(
ctx: &Context<'_>,
uri: Strand,
body: Value,
opts: impl Into<Object>,
) -> Result<Value, Error> {
let url = Url::parse(&uri).map_err(|_| Error::InvalidUrl(uri.to_string()))?;
ctx.check_allowed_net(&url)?;
let cli = Client::builder().build()?;
let mut req = cli.patch(url);
if cfg!(not(target_arch = "wasm32")) {
req = req.header("User-Agent", "SurrealDB");
}
for (k, v) in opts.into().iter() {
req = req.header(k.as_str(), v.to_raw_string());
}
req = encode_body(req, body);
let res = match ctx.timeout() {
#[cfg(not(target_arch = "wasm32"))]
Some(d) => req.timeout(d).send().await?,
_ => req.send().await?,
};
decode_response(res).await
}
pub async fn delete(
ctx: &Context<'_>,
uri: Strand,
opts: impl Into<Object>,
) -> Result<Value, Error> {
let url = Url::parse(&uri).map_err(|_| Error::InvalidUrl(uri.to_string()))?;
ctx.check_allowed_net(&url)?;
let cli = Client::builder().build()?;
let mut req = cli.delete(url);
if cfg!(not(target_arch = "wasm32")) {
req = req.header("User-Agent", "SurrealDB");
}
for (k, v) in opts.into().iter() {
req = req.header(k.as_str(), v.to_raw_string());
}
let res = match ctx.timeout() {
#[cfg(not(target_arch = "wasm32"))]
Some(d) => req.timeout(d).send().await?,
_ => req.send().await?,
};
decode_response(res).await
}