Skip to main content

smcp_computer/mcp_clients/
render.rs

1/**
2* 文件名: render
3* 作者: JQQ
4* 创建日期: 2025/12/16
5* 最后修改日期: 2025/12/16
6* 版权: 2023 JQQ. All rights reserved.
7* 依赖: serde_json, regex, async-trait
8* 描述: 配置渲染器,支持 ${input:xxx} 占位符解析
9*/
10use async_recursion::async_recursion;
11use regex::Regex;
12use serde_json::Value;
13use thiserror::Error;
14
15#[derive(Error, Debug)]
16pub enum RenderError {
17    #[error("Input not found: {0}")]
18    InputNotFound(String),
19    #[error("Render depth exceeded")]
20    DepthExceeded,
21    #[error("Invalid placeholder format")]
22    InvalidPlaceholder,
23}
24
25/// 配置渲染器,用于处理 ${input:xxx} 占位符
26pub struct ConfigRender {
27    placeholder_regex: Regex,
28    max_depth: usize,
29}
30
31impl ConfigRender {
32    /// 创建新的配置渲染器
33    pub fn new(max_depth: usize) -> Self {
34        Self {
35            placeholder_regex: Regex::new(r"\$\{input:([^}]+)}").unwrap(),
36            max_depth,
37        }
38    }
39
40    /// 渲染配置值
41    pub async fn render<F, Fut>(&self, data: Value, resolver: F) -> Result<Value, RenderError>
42    where
43        F: Fn(String) -> Fut + Copy + Send + Sync,
44        Fut: std::future::Future<Output = Result<Value, RenderError>> + Send,
45    {
46        self.render_with_depth(data, resolver, 0).await
47    }
48
49    #[async_recursion]
50    async fn render_with_depth<F, Fut>(
51        &self,
52        data: Value,
53        resolver: F,
54        depth: usize,
55    ) -> Result<Value, RenderError>
56    where
57        F: Fn(String) -> Fut + Copy + Send + Sync,
58        Fut: std::future::Future<Output = Result<Value, RenderError>> + Send,
59    {
60        if depth > self.max_depth {
61            return Err(RenderError::DepthExceeded);
62        }
63
64        match data {
65            Value::String(s) => self.render_string(s, resolver, depth).await,
66            Value::Object(mut map) => {
67                for (k, v) in map.clone() {
68                    map.insert(k, self.render_with_depth(v, resolver, depth + 1).await?);
69                }
70                Ok(Value::Object(map))
71            }
72            Value::Array(arr) => {
73                let mut new_arr = Vec::with_capacity(arr.len());
74                for item in arr {
75                    new_arr.push(self.render_with_depth(item, resolver, depth + 1).await?);
76                }
77                Ok(Value::Array(new_arr))
78            }
79            _ => Ok(data),
80        }
81    }
82
83    async fn render_string<F, Fut>(
84        &self,
85        s: String,
86        resolver: F,
87        _depth: usize,
88    ) -> Result<Value, RenderError>
89    where
90        F: Fn(String) -> Fut + Copy + Send + Sync,
91        Fut: std::future::Future<Output = Result<Value, RenderError>> + Send,
92    {
93        let matches: Vec<_> = self.placeholder_regex.find_iter(&s).collect();
94
95        if matches.is_empty() {
96            return Ok(Value::String(s));
97        }
98
99        // 如果字符串是单个占位符,直接返回解析后的值(可能不是字符串)
100        if matches.len() == 1 && matches[0].start() == 0 && matches[0].end() == s.len() {
101            let input_id = matches[0]
102                .as_str()
103                .strip_prefix("${input:")
104                .unwrap()
105                .strip_suffix('}')
106                .unwrap();
107            return match resolver(input_id.to_string()).await {
108                Ok(value) => Ok(value),
109                Err(RenderError::InputNotFound(_)) => {
110                    // 未找到输入,返回原字符串
111                    Ok(Value::String(s))
112                }
113                Err(e) => Err(e),
114            };
115        }
116
117        // 处理字符串中的多个占位符
118        let mut result = s.clone();
119        let mut offset: isize = 0;
120
121        for m in matches {
122            let input_id = m
123                .as_str()
124                .strip_prefix("${input:")
125                .unwrap()
126                .strip_suffix('}')
127                .unwrap();
128
129            let replacement = match resolver(input_id.to_string()).await {
130                Ok(value) => match value {
131                    Value::String(s) => s,
132                    other => other.to_string(),
133                },
134                Err(RenderError::InputNotFound(_)) => {
135                    // 未找到输入,保留原占位符
136                    m.as_str().to_string()
137                }
138                Err(e) => return Err(e),
139            };
140
141            let start = (m.start() as isize + offset) as usize;
142            let end = (m.end() as isize + offset) as usize;
143            result.replace_range(start..end, &replacement);
144            offset += replacement.len() as isize - (m.end() - m.start()) as isize;
145        }
146
147        Ok(Value::String(result))
148    }
149}
150
151impl Default for ConfigRender {
152    fn default() -> Self {
153        Self::new(10)
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    async fn mock_resolver(id: String) -> Result<Value, RenderError> {
162        match id.as_str() {
163            "test" => Ok(Value::String("resolved".to_string())),
164            "number" => Ok(Value::Number(serde_json::Number::from(42))),
165            "missing" => Err(RenderError::InputNotFound(id)),
166            _ => Ok(Value::String(format!("resolved_{}", id))),
167        }
168    }
169
170    #[tokio::test]
171    async fn test_simple_placeholder() {
172        let render = ConfigRender::default();
173        let input = Value::String("${input:test}".to_string());
174        let result = render.render(input, mock_resolver).await.unwrap();
175        assert_eq!(result, Value::String("resolved".to_string()));
176    }
177
178    #[tokio::test]
179    async fn test_multiple_placeholders() {
180        let render = ConfigRender::default();
181        let input = Value::String("Hello ${input:test} and ${input:world}".to_string());
182        let result = render.render(input, mock_resolver).await.unwrap();
183        assert_eq!(
184            result,
185            Value::String("Hello resolved and resolved_world".to_string())
186        );
187    }
188
189    #[tokio::test]
190    async fn test_missing_input() {
191        let render = ConfigRender::default();
192        let input = Value::String("${input:missing}".to_string());
193        let result = render.render(input, mock_resolver).await.unwrap();
194        assert_eq!(result, Value::String("${input:missing}".to_string()));
195    }
196
197    #[tokio::test]
198    async fn test_object_render() {
199        let render = ConfigRender::default();
200        let mut obj = serde_json::Map::new();
201        obj.insert(
202            "key".to_string(),
203            Value::String("${input:test}".to_string()),
204        );
205        obj.insert("nested".to_string(), Value::String("value".to_string()));
206        let input = Value::Object(obj);
207        let result = render.render(input, mock_resolver).await.unwrap();
208
209        if let Value::Object(map) = result {
210            assert_eq!(
211                map.get("key").unwrap(),
212                &Value::String("resolved".to_string())
213            );
214            assert_eq!(
215                map.get("nested").unwrap(),
216                &Value::String("value".to_string())
217            );
218        } else {
219            panic!("Expected object");
220        }
221    }
222}