use crate::abi::{call_host, raw};
use crate::types::SkillError;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct Header {
#[serde(rename = "name")]
pub name: String,
#[serde(rename = "value")]
pub value: String,
}
impl Header {
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
Header { name: name.into(), value: value.into() }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct Cookie {
#[serde(rename = "name")]
pub name: String,
#[serde(rename = "value")]
pub value: String,
#[serde(rename = "domain", default, skip_serializing_if = "String::is_empty")]
pub domain: String,
#[serde(rename = "path", default, skip_serializing_if = "String::is_empty")]
pub path: String,
#[serde(rename = "expires", default, skip_serializing_if = "is_zero_i64")]
pub expires: i64,
#[serde(rename = "secure", default, skip_serializing_if = "is_false")]
pub secure: bool,
#[serde(rename = "httponly", default, skip_serializing_if = "is_false")]
pub http_only: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct HttpResponse {
#[serde(rename = "status")]
pub status: u32,
#[serde(rename = "headers", default)]
pub headers: Vec<Header>,
#[serde(rename = "cookies", default)]
pub cookies: Vec<Cookie>,
#[serde(rename = "body", default, with = "crate::bytes_or_null")]
pub body: Vec<u8>,
#[serde(rename = "truncated", default)]
pub truncated: bool,
#[serde(rename = "error", default)]
pub(crate) error: String,
}
pub fn http_get(url: &str, timeout_ms: i64, max_bytes: i64) -> Result<HttpResponse, SkillError> {
#[derive(Serialize, Default)]
struct Req<'a> {
url: &'a str,
timeout_ms: i64,
max_bytes: i64,
}
let resp: HttpResponse =
call_host(raw::http_get, &Req { url, timeout_ms, max_bytes })?;
if !resp.error.is_empty() {
return Err(SkillError(resp.error));
}
Ok(resp)
}
pub fn http_post(
url: &str,
body: Vec<u8>,
content_type: &str,
timeout_ms: i64,
max_bytes: i64,
) -> Result<HttpResponse, SkillError> {
#[derive(Serialize, Default)]
struct Req<'a> {
url: &'a str,
#[serde(with = "crate::bytes_or_null")]
body: Vec<u8>,
content_type: &'a str,
timeout_ms: i64,
max_bytes: i64,
}
let resp: HttpResponse = call_host(
raw::http_post,
&Req { url, body, content_type, timeout_ms, max_bytes },
)?;
if !resp.error.is_empty() {
return Err(SkillError(resp.error));
}
Ok(resp)
}
#[derive(Debug, Clone, Default)]
pub struct HttpRequestOptions {
pub method: String,
pub url: String,
pub headers: Vec<Header>,
pub cookies: Vec<Cookie>,
pub body: Vec<u8>,
pub timeout_ms: i64,
pub max_bytes: i64,
pub follow_redirects: bool,
pub use_jar: bool,
}
pub fn http_request(opts: HttpRequestOptions) -> Result<HttpResponse, SkillError> {
#[derive(Serialize, Default)]
struct Req {
method: String,
url: String,
headers: Vec<Header>,
cookies: Vec<Cookie>,
#[serde(with = "crate::bytes_or_null")]
body: Vec<u8>,
timeout_ms: i64,
max_bytes: i64,
follow_redirects: bool,
use_jar: bool,
}
let resp: HttpResponse = call_host(
raw::http_request,
&Req {
method: opts.method,
url: opts.url,
headers: opts.headers,
cookies: opts.cookies,
body: opts.body,
timeout_ms: opts.timeout_ms,
max_bytes: opts.max_bytes,
follow_redirects: opts.follow_redirects,
use_jar: opts.use_jar,
},
)?;
if !resp.error.is_empty() {
return Err(SkillError(resp.error));
}
Ok(resp)
}
pub fn headers_from_json(json: &str) -> Vec<Header> {
let mut result = Vec::new();
let bytes = json.as_bytes();
let mut i = match json.find('{') {
Some(p) => p + 1,
None => return result,
};
while i < bytes.len() {
if bytes[i] == b'}' { break; }
if bytes[i] != b'"' { i += 1; continue; }
i += 1; let key_end = find_quote(bytes, i);
let key = &json[i..key_end];
i = key_end + 1;
while i < bytes.len() && bytes[i] != b'"' { i += 1; }
if i >= bytes.len() { break; }
i += 1; let val_end = find_quote(bytes, i);
let val = &json[i..val_end];
i = val_end + 1;
result.push(Header::new(key, val));
}
result
}
fn find_quote(bytes: &[u8], start: usize) -> usize {
let mut i = start;
while i < bytes.len() {
if bytes[i] == b'"' { return i; }
if bytes[i] == b'\\' { i += 2; continue; }
i += 1;
}
i
}
fn is_false(v: &bool) -> bool { !v }
fn is_zero_i64(v: &i64) -> bool { *v == 0 }