#[cfg(feature = "model")]
use aiway_protocol::model::Provider;
use serde::{Deserialize, Serialize};
use std::any::Any;
use std::collections::HashMap;
use crate::PluginError;
use aiway_protocol::context::HttpContext;
pub const LOG_ERROR: i32 = 1;
pub const LOG_WARN: i32 = 2;
pub const LOG_INFO: i32 = 3;
pub const LOG_DEBUG: i32 = 4;
pub const LOG_TRACE: i32 = 5;
#[derive(Serialize, Deserialize)]
pub struct HttpRequest {
pub method: String,
pub url: String,
pub headers: Vec<(String, String)>,
pub body: Option<Vec<u8>>,
pub form: Option<HashMap<String, String>>,
pub multipart: Option<Vec<FormPart>>,
pub timeout_ms: u64,
}
#[derive(Serialize, Deserialize)]
pub struct FormPart {
pub key: String,
pub value: Vec<u8>,
pub file_name: Option<String>,
pub mime_type: Option<String>,
}
pub struct HttpRequestBuilder {
method: String,
url: String,
headers: Vec<(String, String)>,
body: Option<Vec<u8>>,
form: Option<HashMap<String, String>>,
multipart: Option<Vec<FormPart>>,
timeout_ms: u64,
}
impl HttpRequestBuilder {
pub fn new(method: impl Into<String>, url: impl Into<String>) -> Self {
Self {
method: method.into(),
url: url.into(),
headers: Vec::new(),
body: None,
form: None,
multipart: None,
timeout_ms: 10_000,
}
}
pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.push((key.into(), value.into()));
self
}
pub fn body(mut self, body: Vec<u8>) -> Self {
self.body = Some(body);
self
}
pub fn form(mut self, form: HashMap<String, String>) -> Self {
self.form = Some(form);
self
}
pub fn add_form_field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.form.get_or_insert_with(HashMap::new).insert(key.into(), value.into());
self
}
pub fn multipart(mut self, parts: Vec<FormPart>) -> Self {
self.multipart = Some(parts);
self
}
pub fn add_multipart_part(mut self, part: FormPart) -> Self {
self.multipart.get_or_insert_with(Vec::new).push(part);
self
}
pub fn timeout_ms(mut self, timeout_ms: u64) -> Self {
self.timeout_ms = timeout_ms;
self
}
pub fn build(self) -> HttpRequest {
HttpRequest {
method: self.method,
url: self.url,
headers: self.headers,
body: self.body,
form: self.form,
multipart: self.multipart,
timeout_ms: self.timeout_ms,
}
}
}
#[derive(Serialize, Deserialize)]
pub struct HttpResponse {
pub status: u16,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
impl HttpResponse {
pub fn text(&self) -> Result<String, PluginError> {
String::from_utf8(self.body.clone())
.map_err(|e| PluginError::HttpError(format!("invalid UTF-8 response: {e}")))
}
pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, PluginError> {
serde_json::from_slice(&self.body)
.map_err(|e| PluginError::SerializeError(format!("JSON deserialize failed: {e}")))
}
}
pub trait PluginContext: Send {
fn request_id(&self) -> String;
fn request_ts(&self) -> i64;
fn is_sse(&self) -> bool;
fn is_websocket(&self) -> bool;
fn get_route_name(&self) -> Option<String>;
fn get_routing_url(&self) -> Option<String>;
fn get_response_body_size(&self) -> Option<i64>;
fn set_response_body_size(&mut self, size: i64);
#[cfg(feature = "model")]
fn get_model_name(&self) -> Option<String>;
#[cfg(feature = "model")]
fn get_model_provider(&self) -> Option<Provider>;
fn log(&self, level: i32, msg: &str);
fn log_error(&self, msg: &str) { self.log(LOG_ERROR, msg); }
fn log_warn(&self, msg: &str) { self.log(LOG_WARN, msg); }
fn log_info(&self, msg: &str) { self.log(LOG_INFO, msg); }
fn log_debug(&self, msg: &str) { self.log(LOG_DEBUG, msg); }
fn log_trace(&self, msg: &str) { self.log(LOG_TRACE, msg); }
fn http_request(&self, _req: &HttpRequest) -> Result<HttpResponse, PluginError> {
Err(PluginError::HttpError("http_request not supported in this context".into()))
}
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl PluginContext for HttpContext {
fn request_id(&self) -> String {
HttpContext::request_id(self)
}
fn request_ts(&self) -> i64 {
HttpContext::request_ts(self)
}
fn is_sse(&self) -> bool {
HttpContext::is_sse(self)
}
fn is_websocket(&self) -> bool {
HttpContext::is_websocket(self)
}
fn get_route_name(&self) -> Option<String> {
self.get_route().map(|r| r.name.clone())
}
fn get_routing_url(&self) -> Option<String> {
HttpContext::get_routing_url(self).cloned()
}
fn get_response_body_size(&self) -> Option<i64> {
self.get_state::<i64>(Self::RESPONSE_BODY_SIZE)
}
fn set_response_body_size(&mut self, size: i64) {
self.insert_state(Self::RESPONSE_BODY_SIZE, size);
}
#[cfg(feature = "model")]
fn get_model_name(&self) -> Option<String> {
self.get_proxy_model_name()
}
#[cfg(feature = "model")]
fn get_model_provider(&self) -> Option<Provider> {
self.get_proxy_model_provider()
}
fn log(&self, level: i32, msg: &str) {
match level {
LOG_ERROR => log::error!("{}", msg),
LOG_WARN => log::warn!("{}", msg),
LOG_INFO => log::info!("{}", msg),
LOG_DEBUG => log::debug!("{}", msg),
LOG_TRACE => log::trace!("{}", msg),
_ => log::info!("{}", msg),
}
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}