Skip to main content

aiway_plugin/
plugin_ctx.rs

1//! 插件上下文接口定义
2//!
3//! 定义插件可访问的上下文操作,宿主侧和 WASM 侧分别提供实现。
4
5#[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::{
13    HeaderOp, HttpContext, REQUEST_HEADER_PATCH, REQUEST_URI_PATCH, RESPONSE_HEADER_PATCH,
14    parts::SerdeParts,
15};
16use http::Uri;
17
18/// 日志级别常量,与 WASM 侧和 Host 侧保持一致
19pub const LOG_ERROR: i32 = 1;
20pub const LOG_WARN: i32 = 2;
21pub const LOG_INFO: i32 = 3;
22pub const LOG_DEBUG: i32 = 4;
23pub const LOG_TRACE: i32 = 5;
24
25/// HTTP 请求参数
26#[derive(Serialize, Deserialize)]
27pub struct HttpRequest {
28    pub method: String,
29    pub url: String,
30    pub headers: Vec<(String, String)>,
31    pub body: Option<Vec<u8>>,
32    /// URL 编码表单(与 body/multipart 互斥,优先级: multipart > form > body)
33    pub form: Option<HashMap<String, String>>,
34    /// Multipart 表单(与 body/form 互斥,优先级最高)
35    pub multipart: Option<Vec<FormPart>>,
36    pub timeout_ms: u64,
37}
38
39/// Multipart 表单字段
40#[derive(Serialize, Deserialize)]
41pub struct FormPart {
42    pub key: String,
43    /// 字段值(文本或文件内容)
44    pub value: Vec<u8>,
45    /// 文件名(文件上传时设置)
46    pub file_name: Option<String>,
47    /// MIME 类型(如 "text/plain"、"image/png")
48    pub mime_type: Option<String>,
49}
50
51/// HTTP 请求构建器
52pub struct HttpRequestBuilder {
53    method: String,
54    url: String,
55    headers: Vec<(String, String)>,
56    body: Option<Vec<u8>>,
57    form: Option<HashMap<String, String>>,
58    multipart: Option<Vec<FormPart>>,
59    timeout_ms: u64,
60}
61
62impl HttpRequestBuilder {
63    /// 创建构建器,`method` 和 `url` 为必填项
64    pub fn new(method: impl Into<String>, url: impl Into<String>) -> Self {
65        Self {
66            method: method.into(),
67            url: url.into(),
68            headers: Vec::new(),
69            body: None,
70            form: None,
71            multipart: None,
72            timeout_ms: 10_000,
73        }
74    }
75
76    /// 添加请求头
77    pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
78        self.headers.push((key.into(), value.into()));
79        self
80    }
81
82    /// 设置原始请求体(与 form/multipart 互斥)
83    pub fn body(mut self, body: Vec<u8>) -> Self {
84        self.body = Some(body);
85        self
86    }
87
88    /// 设置 URL 编码表单(与 body/multipart 互斥)
89    pub fn form(mut self, form: HashMap<String, String>) -> Self {
90        self.form = Some(form);
91        self
92    }
93
94    /// 添加单个表单字段
95    pub fn add_form_field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
96        self.form
97            .get_or_insert_with(HashMap::new)
98            .insert(key.into(), value.into());
99        self
100    }
101
102    /// 设置 Multipart 表单字段列表(与 body/form 互斥)
103    pub fn multipart(mut self, parts: Vec<FormPart>) -> Self {
104        self.multipart = Some(parts);
105        self
106    }
107
108    /// 添加单个 Multipart 字段
109    pub fn add_multipart_part(mut self, part: FormPart) -> Self {
110        self.multipart.get_or_insert_with(Vec::new).push(part);
111        self
112    }
113
114    /// 设置超时时间(毫秒),默认 10000
115    pub fn timeout_ms(mut self, timeout_ms: u64) -> Self {
116        self.timeout_ms = timeout_ms;
117        self
118    }
119
120    /// 构建 HttpRequest
121    pub fn build(self) -> HttpRequest {
122        HttpRequest {
123            method: self.method,
124            url: self.url,
125            headers: self.headers,
126            body: self.body,
127            form: self.form,
128            multipart: self.multipart,
129            timeout_ms: self.timeout_ms,
130        }
131    }
132}
133
134/// HTTP 响应结果
135#[derive(Serialize, Deserialize)]
136pub struct HttpResponse {
137    pub status: u16,
138    pub headers: Vec<(String, String)>,
139    pub body: Vec<u8>,
140}
141
142impl HttpResponse {
143    /// 将响应体作为 UTF-8 文本返回
144    pub fn text(&self) -> Result<String, PluginError> {
145        String::from_utf8(self.body.clone())
146            .map_err(|e| PluginError::HttpError(format!("invalid UTF-8 response: {e}")))
147    }
148
149    /// 将响应体作为 JSON 反序列化
150    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, PluginError> {
151        serde_json::from_slice(&self.body)
152            .map_err(|e| PluginError::SerdeError(format!("JSON deserialize failed: {e}")))
153    }
154}
155
156/// 插件上下文接口
157///
158/// 宿主侧通过 `HttpContext` 实现,WASM 侧通过 `WasmHttpContext` 实现。
159/// 插件开发者面向此 trait 编程,不依赖具体实现。
160pub trait PluginContext: Send {
161    /// 请求 ID
162    fn request_id(&self) -> String;
163    /// 请求时间戳(毫秒)
164    fn request_ts(&self) -> i64;
165    /// 是否为 SSE 连接
166    fn is_sse(&self) -> bool;
167    /// 是否为 WebSocket 连接
168    fn is_websocket(&self) -> bool;
169    /// 获取原始请求头(从 REQUEST_RAW_PARTS 读取,跨阶段可用)
170    fn get_request_header(&self, name: &str) -> Option<String>;
171    /// 获取原始响应头(从 RESPONSE_SERDE_PARTS 读取,跨阶段可用)
172    fn get_response_header(&self, name: &str) -> Option<String>;
173    /// 请求方法(仅 on_request 阶段有值)
174    fn method(&self) -> Option<String>;
175    /// 请求 URI(仅 on_request 阶段有值)
176    fn uri(&self) -> Option<Uri>;
177    /// 覆盖写入请求 URI(仅 on_request 阶段生效,路径改写场景)
178    fn set_uri(&mut self, uri: Uri);
179    /// 响应状态码(仅 on_response 阶段有值)
180    fn status(&self) -> Option<u16>;
181    /// 路由名称
182    fn get_route_name(&self) -> Option<String>;
183    /// 路由目标地址
184    fn get_routing_url(&self) -> Option<String>;
185    /// 响应体大小
186    fn get_response_body_size(&self) -> Option<i64>;
187    /// 设置响应体大小
188    fn set_response_body_size(&mut self, size: i64);
189
190    /// 覆盖写入请求头
191    fn set_request_header(&mut self, name: &str, value: &str);
192    /// 覆盖写入响应头
193    fn set_response_header(&mut self, name: &str, value: &str);
194    /// 多值追加请求头
195    fn append_request_header(&mut self, name: &str, value: &str);
196    /// 多值追加响应头
197    fn append_response_header(&mut self, name: &str, value: &str);
198    /// 移除请求头
199    fn remove_request_header(&mut self, name: &str);
200    /// 移除响应头
201    fn remove_response_header(&mut self, name: &str);
202    /// 模型名称(仅模型插件可用)
203    #[cfg(feature = "model")]
204    fn get_model_name(&self) -> Option<String>;
205    /// 模型提供商(仅模型插件可用)
206    #[cfg(feature = "model")]
207    fn get_model_provider(&self) -> Option<Provider>;
208
209    /// 输出日志(底层接口,level 使用 LOG_* 常量)
210    fn log(&self, level: i32, msg: &str);
211    /// 输出 ERROR 级别日志
212    fn log_error(&self, msg: &str) {
213        self.log(LOG_ERROR, msg);
214    }
215    /// 输出 WARN 级别日志
216    fn log_warn(&self, msg: &str) {
217        self.log(LOG_WARN, msg);
218    }
219    /// 输出 INFO 级别日志
220    fn log_info(&self, msg: &str) {
221        self.log(LOG_INFO, msg);
222    }
223    /// 输出 DEBUG 级别日志
224    fn log_debug(&self, msg: &str) {
225        self.log(LOG_DEBUG, msg);
226    }
227    /// 输出 TRACE 级别日志
228    fn log_trace(&self, msg: &str) {
229        self.log(LOG_TRACE, msg);
230    }
231
232    /// 发起 HTTP 请求(默认实现返回错误,WASM 侧通过宿主函数重写)
233    fn http_request(&self, _req: &HttpRequest) -> Result<HttpResponse, PluginError> {
234        Err(PluginError::HttpError(
235            "http_request not supported in this context".into(),
236        ))
237    }
238
239    /// 类型擦除,供宿主侧 downcast 获取 `HttpContext`
240    fn as_any_mut(&mut self) -> &mut dyn Any;
241}
242
243impl PluginContext for HttpContext {
244    fn request_id(&self) -> String {
245        HttpContext::request_id(self)
246    }
247
248    fn request_ts(&self) -> i64 {
249        HttpContext::request_ts(self)
250    }
251
252    fn is_sse(&self) -> bool {
253        HttpContext::is_sse(self)
254    }
255
256    fn is_websocket(&self) -> bool {
257        HttpContext::is_websocket(self)
258    }
259
260    fn get_request_header(&self, name: &str) -> Option<String> {
261        self.get_state::<SerdeParts>(Self::REQUEST_RAW_PARTS)
262            .and_then(|parts| {
263                parts
264                    .headers
265                    .as_ref()?
266                    .get(name)
267                    .and_then(|v| v.to_str().ok())
268                    .map(|s| s.to_string())
269            })
270    }
271
272    fn get_response_header(&self, name: &str) -> Option<String> {
273        self.get_state::<SerdeParts>(Self::RESPONSE_SERDE_PARTS)
274            .and_then(|parts| {
275                parts
276                    .headers
277                    .as_ref()?
278                    .get(name)
279                    .and_then(|v| v.to_str().ok())
280                    .map(|s| s.to_string())
281            })
282    }
283
284    fn method(&self) -> Option<String> {
285        self.get_state::<SerdeParts>(Self::REQUEST_RAW_PARTS)
286            .and_then(|parts| parts.method.map(|m| m.to_string()))
287    }
288
289    fn uri(&self) -> Option<Uri> {
290        self.get_state::<SerdeParts>(Self::REQUEST_RAW_PARTS)
291            .and_then(|parts| parts.uri)
292    }
293
294    fn set_uri(&mut self, uri: Uri) {
295        self.insert_any_state(REQUEST_URI_PATCH, uri);
296    }
297
298    fn status(&self) -> Option<u16> {
299        self.get_state::<SerdeParts>(Self::RESPONSE_SERDE_PARTS)
300            .and_then(|parts| parts.status_code.map(|s| s.as_u16()))
301    }
302
303    fn get_route_name(&self) -> Option<String> {
304        self.get_route().map(|r| r.name.clone())
305    }
306
307    fn get_routing_url(&self) -> Option<String> {
308        HttpContext::get_routing_url(self).cloned()
309    }
310
311    fn get_response_body_size(&self) -> Option<i64> {
312        self.get_state::<i64>(Self::RESPONSE_BODY_SIZE)
313    }
314
315    fn set_response_body_size(&mut self, size: i64) {
316        self.insert_state(Self::RESPONSE_BODY_SIZE, size);
317    }
318
319    fn set_request_header(&mut self, name: &str, value: &str) {
320        let mut ops = self
321            .get_any_state::<Vec<HeaderOp>>(REQUEST_HEADER_PATCH)
322            .map(|arc| (*arc).clone())
323            .unwrap_or_default();
324        ops.push(HeaderOp::Set(name.to_string(), value.to_string()));
325        self.insert_any_state(REQUEST_HEADER_PATCH, ops);
326    }
327
328    fn set_response_header(&mut self, name: &str, value: &str) {
329        let mut ops = self
330            .get_any_state::<Vec<HeaderOp>>(RESPONSE_HEADER_PATCH)
331            .map(|arc| (*arc).clone())
332            .unwrap_or_default();
333        ops.push(HeaderOp::Set(name.to_string(), value.to_string()));
334        self.insert_any_state(RESPONSE_HEADER_PATCH, ops);
335    }
336
337    fn append_request_header(&mut self, name: &str, value: &str) {
338        let mut ops = self
339            .get_any_state::<Vec<HeaderOp>>(REQUEST_HEADER_PATCH)
340            .map(|arc| (*arc).clone())
341            .unwrap_or_default();
342        ops.push(HeaderOp::Append(name.to_string(), value.to_string()));
343        self.insert_any_state(REQUEST_HEADER_PATCH, ops);
344    }
345
346    fn append_response_header(&mut self, name: &str, value: &str) {
347        let mut ops = self
348            .get_any_state::<Vec<HeaderOp>>(RESPONSE_HEADER_PATCH)
349            .map(|arc| (*arc).clone())
350            .unwrap_or_default();
351        ops.push(HeaderOp::Append(name.to_string(), value.to_string()));
352        self.insert_any_state(RESPONSE_HEADER_PATCH, ops);
353    }
354
355    fn remove_request_header(&mut self, name: &str) {
356        let mut ops = self
357            .get_any_state::<Vec<HeaderOp>>(REQUEST_HEADER_PATCH)
358            .map(|arc| (*arc).clone())
359            .unwrap_or_default();
360        ops.push(HeaderOp::Remove(name.to_string()));
361        self.insert_any_state(REQUEST_HEADER_PATCH, ops);
362    }
363
364    fn remove_response_header(&mut self, name: &str) {
365        let mut ops = self
366            .get_any_state::<Vec<HeaderOp>>(RESPONSE_HEADER_PATCH)
367            .map(|arc| (*arc).clone())
368            .unwrap_or_default();
369        ops.push(HeaderOp::Remove(name.to_string()));
370        self.insert_any_state(RESPONSE_HEADER_PATCH, ops);
371    }
372
373    #[cfg(feature = "model")]
374    fn get_model_name(&self) -> Option<String> {
375        self.get_proxy_model_name()
376    }
377
378    #[cfg(feature = "model")]
379    fn get_model_provider(&self) -> Option<Provider> {
380        self.get_proxy_model_provider()
381    }
382
383    fn log(&self, level: i32, msg: &str) {
384        match level {
385            LOG_ERROR => log::error!("{}", msg),
386            LOG_WARN => log::warn!("{}", msg),
387            LOG_INFO => log::info!("{}", msg),
388            LOG_DEBUG => log::debug!("{}", msg),
389            LOG_TRACE => log::trace!("{}", msg),
390            _ => log::info!("{}", msg),
391        }
392    }
393
394    fn as_any_mut(&mut self) -> &mut dyn Any {
395        self
396    }
397}