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