mcp_execution_codegen/common/
typescript.rs1use serde_json::Value;
24
25#[must_use]
37pub fn to_camel_case(snake_case: &str) -> String {
38 let mut result = String::new();
39 let mut capitalize_next = false;
40
41 for ch in snake_case.chars() {
42 if ch == '_' {
43 capitalize_next = true;
44 } else if capitalize_next {
45 result.push(ch.to_ascii_uppercase());
46 capitalize_next = false;
47 } else {
48 result.push(ch);
49 }
50 }
51
52 result
53}
54
55#[must_use]
67pub fn to_pascal_case(snake_case: &str) -> String {
68 let camel = to_camel_case(snake_case);
69 let mut chars = camel.chars();
70 match chars.next() {
71 None => String::new(),
72 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
73 }
74}
75
76#[must_use]
92pub fn json_type_to_typescript(json_type: &str) -> &'static str {
93 match json_type {
94 "string" => "string",
95 "number" | "integer" => "number",
96 "boolean" => "boolean",
97 "array" => "unknown[]",
98 "object" => "Record<string, unknown>",
99 "null" => "null",
100 _ => "unknown",
101 }
102}
103
104#[must_use]
127pub fn json_schema_to_typescript(schema: &Value) -> String {
128 match schema {
129 Value::Object(obj) => {
130 let schema_type = obj
132 .get("type")
133 .and_then(|v| v.as_str())
134 .unwrap_or("unknown");
135
136 match schema_type {
137 "object" => {
138 let properties = obj.get("properties").and_then(|v| v.as_object());
140 let required = obj
141 .get("required")
142 .and_then(|v| v.as_array())
143 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>())
144 .unwrap_or_default();
145
146 if let Some(props) = properties {
147 let mut fields = Vec::new();
148 for (key, value) in props {
149 let is_required = required.contains(&key.as_str());
150 let optional_marker = if is_required { "" } else { "?" };
151 let ts_type = json_schema_to_typescript(value);
152 fields.push(format!(" {}{}: {};", key, optional_marker, ts_type));
153 }
154
155 if fields.is_empty() {
156 "Record<string, unknown>".to_string()
157 } else {
158 format!("{{\n{}\n}}", fields.join("\n"))
159 }
160 } else {
161 "Record<string, unknown>".to_string()
162 }
163 }
164 "array" => {
165 let items = obj.get("items");
166 if let Some(item_schema) = items {
167 format!("{}[]", json_schema_to_typescript(item_schema))
168 } else {
169 "unknown[]".to_string()
170 }
171 }
172 other => json_type_to_typescript(other).to_string(),
173 }
174 }
175 Value::String(s) => json_type_to_typescript(s).to_string(),
176 _ => "unknown".to_string(),
177 }
178}
179
180#[must_use]
203pub fn extract_properties(schema: &Value) -> Vec<serde_json::Value> {
204 let mut properties = Vec::new();
205
206 if let Some(obj) = schema.as_object()
207 && let Some(props) = obj.get("properties").and_then(|v| v.as_object())
208 {
209 let required = obj
210 .get("required")
211 .and_then(|v| v.as_array())
212 .map(|arr| {
213 arr.iter()
214 .filter_map(|v| v.as_str())
215 .map(String::from)
216 .collect::<Vec<_>>()
217 })
218 .unwrap_or_default();
219
220 for (name, prop_schema) in props {
221 let ts_type = json_schema_to_typescript(prop_schema);
222 let is_required = required.contains(name);
223
224 properties.push(serde_json::json!({
225 "name": name,
226 "type": ts_type,
227 "required": is_required,
228 }));
229 }
230 }
231
232 properties
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use serde_json::json;
239
240 #[test]
241 fn test_to_camel_case() {
242 assert_eq!(to_camel_case("send_message"), "sendMessage");
243 assert_eq!(to_camel_case("get_user_data"), "getUserData");
244 assert_eq!(to_camel_case("hello"), "hello");
245 assert_eq!(to_camel_case("a_b_c"), "aBC");
246 }
247
248 #[test]
249 fn test_to_pascal_case() {
250 assert_eq!(to_pascal_case("send_message"), "SendMessage");
251 assert_eq!(to_pascal_case("get_user_data"), "GetUserData");
252 assert_eq!(to_pascal_case("hello"), "Hello");
253 }
254
255 #[test]
256 fn test_json_type_to_typescript() {
257 assert_eq!(json_type_to_typescript("string"), "string");
258 assert_eq!(json_type_to_typescript("number"), "number");
259 assert_eq!(json_type_to_typescript("integer"), "number");
260 assert_eq!(json_type_to_typescript("boolean"), "boolean");
261 assert_eq!(json_type_to_typescript("array"), "unknown[]");
262 assert_eq!(json_type_to_typescript("object"), "Record<string, unknown>");
263 assert_eq!(json_type_to_typescript("null"), "null");
264 assert_eq!(json_type_to_typescript("unknown_type"), "unknown");
265 }
266
267 #[test]
268 fn test_json_schema_to_typescript_primitive() {
269 assert_eq!(
270 json_schema_to_typescript(&json!({"type": "string"})),
271 "string"
272 );
273 assert_eq!(
274 json_schema_to_typescript(&json!({"type": "number"})),
275 "number"
276 );
277 }
278
279 #[test]
280 fn test_json_schema_to_typescript_object() {
281 let schema = json!({
282 "type": "object",
283 "properties": {
284 "name": {"type": "string"},
285 "age": {"type": "number"}
286 },
287 "required": ["name"]
288 });
289
290 let result = json_schema_to_typescript(&schema);
291 assert!(result.contains("name: string"));
292 assert!(result.contains("age?: number"));
293 }
294
295 #[test]
296 fn test_json_schema_to_typescript_array() {
297 let schema = json!({
298 "type": "array",
299 "items": {"type": "string"}
300 });
301
302 assert_eq!(json_schema_to_typescript(&schema), "string[]");
303 }
304
305 #[test]
306 fn test_extract_properties() {
307 let schema = json!({
308 "type": "object",
309 "properties": {
310 "name": {"type": "string"},
311 "age": {"type": "number"}
312 },
313 "required": ["name"]
314 });
315
316 let props = extract_properties(&schema);
317 assert_eq!(props.len(), 2);
318
319 let name_prop = props
321 .iter()
322 .find(|p| p["name"] == "name")
323 .expect("name property not found");
324
325 assert_eq!(name_prop["type"], "string");
326 assert_eq!(name_prop["required"], true);
327
328 let age_prop = props
330 .iter()
331 .find(|p| p["name"] == "age")
332 .expect("age property not found");
333
334 assert_eq!(age_prop["type"], "number");
335 assert_eq!(age_prop["required"], false);
336 }
337
338 #[test]
339 fn test_extract_properties_empty() {
340 let schema = json!({"type": "string"});
341 let props = extract_properties(&schema);
342 assert_eq!(props.len(), 0);
343 }
344}