1#[cfg(feature = "model")]
6use aiway_protocol::model::Provider;
7use serde::{Deserialize, Serialize};
8use std::any::Any;
9use std::collections::HashMap;
10
11use crate::PluginError;
12use aiway_protocol::context::HttpContext;
13
14pub const LOG_ERROR: i32 = 1;
16pub const LOG_WARN: i32 = 2;
17pub const LOG_INFO: i32 = 3;
18pub const LOG_DEBUG: i32 = 4;
19pub const LOG_TRACE: i32 = 5;
20
21#[derive(Serialize, Deserialize)]
23pub struct HttpRequest {
24 pub method: String,
25 pub url: String,
26 pub headers: Vec<(String, String)>,
27 pub body: Option<Vec<u8>>,
28 pub form: Option<HashMap<String, String>>,
30 pub multipart: Option<Vec<FormPart>>,
32 pub timeout_ms: u64,
33}
34
35#[derive(Serialize, Deserialize)]
37pub struct FormPart {
38 pub key: String,
39 pub value: Vec<u8>,
41 pub file_name: Option<String>,
43 pub mime_type: Option<String>,
45}
46
47pub struct HttpRequestBuilder {
49 method: String,
50 url: String,
51 headers: Vec<(String, String)>,
52 body: Option<Vec<u8>>,
53 form: Option<HashMap<String, String>>,
54 multipart: Option<Vec<FormPart>>,
55 timeout_ms: u64,
56}
57
58impl HttpRequestBuilder {
59 pub fn new(method: impl Into<String>, url: impl Into<String>) -> Self {
61 Self {
62 method: method.into(),
63 url: url.into(),
64 headers: Vec::new(),
65 body: None,
66 form: None,
67 multipart: None,
68 timeout_ms: 10_000,
69 }
70 }
71
72 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
74 self.headers.push((key.into(), value.into()));
75 self
76 }
77
78 pub fn body(mut self, body: Vec<u8>) -> Self {
80 self.body = Some(body);
81 self
82 }
83
84 pub fn form(mut self, form: HashMap<String, String>) -> Self {
86 self.form = Some(form);
87 self
88 }
89
90 pub fn add_form_field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
92 self.form.get_or_insert_with(HashMap::new).insert(key.into(), value.into());
93 self
94 }
95
96 pub fn multipart(mut self, parts: Vec<FormPart>) -> Self {
98 self.multipart = Some(parts);
99 self
100 }
101
102 pub fn add_multipart_part(mut self, part: FormPart) -> Self {
104 self.multipart.get_or_insert_with(Vec::new).push(part);
105 self
106 }
107
108 pub fn timeout_ms(mut self, timeout_ms: u64) -> Self {
110 self.timeout_ms = timeout_ms;
111 self
112 }
113
114 pub fn build(self) -> HttpRequest {
116 HttpRequest {
117 method: self.method,
118 url: self.url,
119 headers: self.headers,
120 body: self.body,
121 form: self.form,
122 multipart: self.multipart,
123 timeout_ms: self.timeout_ms,
124 }
125 }
126}
127
128#[derive(Serialize, Deserialize)]
130pub struct HttpResponse {
131 pub status: u16,
132 pub headers: Vec<(String, String)>,
133 pub body: Vec<u8>,
134}
135
136impl HttpResponse {
137 pub fn text(&self) -> Result<String, PluginError> {
139 String::from_utf8(self.body.clone())
140 .map_err(|e| PluginError::HttpError(format!("invalid UTF-8 response: {e}")))
141 }
142
143 pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, PluginError> {
145 serde_json::from_slice(&self.body)
146 .map_err(|e| PluginError::SerializeError(format!("JSON deserialize failed: {e}")))
147 }
148}
149
150pub trait PluginContext: Send {
155 fn request_id(&self) -> String;
157 fn request_ts(&self) -> i64;
159 fn is_sse(&self) -> bool;
161 fn is_websocket(&self) -> bool;
163 fn get_route_name(&self) -> Option<String>;
165 fn get_routing_url(&self) -> Option<String>;
167 fn get_response_body_size(&self) -> Option<i64>;
169 fn set_response_body_size(&mut self, size: i64);
171 #[cfg(feature = "model")]
173 fn get_model_name(&self) -> Option<String>;
174 #[cfg(feature = "model")]
176 fn get_model_provider(&self) -> Option<Provider>;
177
178 fn log(&self, level: i32, msg: &str);
180 fn log_error(&self, msg: &str) { self.log(LOG_ERROR, msg); }
182 fn log_warn(&self, msg: &str) { self.log(LOG_WARN, msg); }
184 fn log_info(&self, msg: &str) { self.log(LOG_INFO, msg); }
186 fn log_debug(&self, msg: &str) { self.log(LOG_DEBUG, msg); }
188 fn log_trace(&self, msg: &str) { self.log(LOG_TRACE, msg); }
190
191 fn http_request(&self, _req: &HttpRequest) -> Result<HttpResponse, PluginError> {
193 Err(PluginError::HttpError("http_request not supported in this context".into()))
194 }
195
196 fn as_any_mut(&mut self) -> &mut dyn Any;
198}
199
200impl PluginContext for HttpContext {
201 fn request_id(&self) -> String {
202 HttpContext::request_id(self)
203 }
204
205 fn request_ts(&self) -> i64 {
206 HttpContext::request_ts(self)
207 }
208
209 fn is_sse(&self) -> bool {
210 HttpContext::is_sse(self)
211 }
212
213 fn is_websocket(&self) -> bool {
214 HttpContext::is_websocket(self)
215 }
216
217 fn get_route_name(&self) -> Option<String> {
218 self.get_route().map(|r| r.name.clone())
219 }
220
221 fn get_routing_url(&self) -> Option<String> {
222 HttpContext::get_routing_url(self).cloned()
223 }
224
225 fn get_response_body_size(&self) -> Option<i64> {
226 self.get_state::<i64>(Self::RESPONSE_BODY_SIZE)
227 }
228
229 fn set_response_body_size(&mut self, size: i64) {
230 self.insert_state(Self::RESPONSE_BODY_SIZE, size);
231 }
232
233 #[cfg(feature = "model")]
234 fn get_model_name(&self) -> Option<String> {
235 self.get_proxy_model_name()
236 }
237
238 #[cfg(feature = "model")]
239 fn get_model_provider(&self) -> Option<Provider> {
240 self.get_proxy_model_provider()
241 }
242
243 fn log(&self, level: i32, msg: &str) {
244 match level {
245 LOG_ERROR => log::error!("{}", msg),
246 LOG_WARN => log::warn!("{}", msg),
247 LOG_INFO => log::info!("{}", msg),
248 LOG_DEBUG => log::debug!("{}", msg),
249 LOG_TRACE => log::trace!("{}", msg),
250 _ => log::info!("{}", msg),
251 }
252 }
253
254 fn as_any_mut(&mut self) -> &mut dyn Any {
255 self
256 }
257}