Skip to main content

nargo_sdk/
lib.rs

1#![warn(missing_docs)]
2
3//! 前端SDK生成器模块
4//!
5//! 提供基于API元数据生成TypeScript前端SDK的功能。
6
7use nargo_metadata::{ApiEndpoint, ApiMetadata, ApiParam, ApiParamType};
8use std::{collections::HashMap, fs::File, io::Write, path::Path};
9
10/// 前端SDK生成器
11///
12/// 负责根据API元数据生成TypeScript SDK
13pub struct SdkGenerator;
14
15impl SdkGenerator {
16    /// 生成前端SDK
17    ///
18    /// # Arguments
19    /// * `metadata` - API元数据
20    /// * `output_dir` - 输出目录
21    ///
22    /// # Returns
23    /// * `std::io::Result<()>` - 操作结果
24    pub fn generate(metadata: &ApiMetadata, output_dir: &Path) -> std::io::Result<()> {
25        std::fs::create_dir_all(output_dir)?;
26
27        Self::generate_types(metadata, &output_dir.join("types.ts"))?;
28
29        Self::generate_sdk(metadata, &output_dir.join("api.ts"))?;
30
31        Ok(())
32    }
33
34    /// 生成类型定义文件
35    ///
36    /// # Arguments
37    /// * `metadata` - API元数据
38    /// * `output_path` - 输出文件路径
39    ///
40    /// # Returns
41    /// * `std::io::Result<()>` - 操作结果
42    fn generate_types(metadata: &ApiMetadata, output_path: &Path) -> std::io::Result<()> {
43        let mut file = File::create(output_path)?;
44
45        writeln!(file, "// Generated by Nargo SDK Generator")?;
46        writeln!(file)?;
47
48        for (name, type_def) in &metadata.types {
49            writeln!(file, "export interface {name} {{")?;
50            for field in &type_def.fields {
51                let optional = if field.optional { "?" } else { "" };
52                writeln!(file, "  {}{}: {};", field.name, optional, field.type_name)?;
53            }
54            writeln!(file, "}}")?;
55            writeln!(file)?;
56        }
57
58        Ok(())
59    }
60
61    /// 生成SDK文件
62    ///
63    /// # Arguments
64    /// * `metadata` - API元数据
65    /// * `output_path` - 输出文件路径
66    ///
67    /// # Returns
68    /// * `std::io::Result<()>` - 操作结果
69    fn generate_sdk(metadata: &ApiMetadata, output_path: &Path) -> std::io::Result<()> {
70        let mut file = File::create(output_path)?;
71
72        writeln!(file, "/* eslint-disable */")?;
73        writeln!(file, "// @ts-nocheck")?;
74        writeln!(file, "import {{ useClient }} from '@nargo/client';")?;
75        writeln!(file, "import type * as Types from './types';")?;
76        writeln!(file)?;
77
78        let mut controllers: HashMap<String, Vec<&ApiEndpoint>> = HashMap::new();
79        for endpoint in &metadata.endpoints {
80            controllers.entry(endpoint.controller.clone()).or_default().push(endpoint);
81        }
82
83        for (controller_name, endpoints) in controllers {
84            Self::generate_controller(&mut file, &controller_name, &endpoints)?;
85        }
86
87        Ok(())
88    }
89
90    /// 生成控制器
91    ///
92    /// # Arguments
93    /// * `file` - 输出文件
94    /// * `controller_name` - 控制器名称
95    /// * `endpoints` - 端点列表
96    ///
97    /// # Returns
98    /// * `std::io::Result<()>` - 操作结果
99    fn generate_controller(file: &mut File, controller_name: &str, endpoints: &[&ApiEndpoint]) -> std::io::Result<()> {
100        writeln!(file, "export const {controller_name} = {{")?;
101
102        for endpoint in endpoints {
103            Self::generate_endpoint_method(file, controller_name, endpoint)?;
104        }
105
106        writeln!(file, "}};")?;
107        writeln!(file)?;
108
109        for endpoint in endpoints {
110            Self::generate_endpoint_metadata(file, controller_name, endpoint)?;
111        }
112        writeln!(file)?;
113
114        Ok(())
115    }
116
117    /// 生成端点方法
118    ///
119    /// # Arguments
120    /// * `file` - 输出文件
121    /// * `_controller_name` - 控制器名称
122    /// * `endpoint` - API端点
123    ///
124    /// # Returns
125    /// * `std::io::Result<()>` - 操作结果
126    fn generate_endpoint_method(file: &mut File, _controller_name: &str, endpoint: &ApiEndpoint) -> std::io::Result<()> {
127        let return_type = endpoint.return_type.as_deref().unwrap_or("any");
128
129        writeln!(file, "  /**")?;
130        writeln!(file, "   */")?;
131
132        let params_str = Self::generate_params_list(&endpoint.params);
133        writeln!(file, "  async {name}({params_str}): Promise<{return_type}> {{", name = endpoint.name)?;
134
135        writeln!(file, "    const client = useClient();")?;
136
137        let path = Self::generate_path(&endpoint.path, &endpoint.params);
138        let query_params = Self::generate_query_params(&endpoint.params);
139        let body_param = endpoint.params.iter().find(|p| p.param_type == ApiParamType::Body);
140        let method = endpoint.method.to_lowercase();
141
142        match method.as_str() {
143            "get" | "delete" => {
144                writeln!(file, "    return client.{}(`{}`, {{ params: {{ {} }} }});", method, path, query_params)?;
145            }
146            "post" | "put" | "patch" => {
147                if let Some(body) = body_param {
148                    writeln!(file, "    return client.{}(`{}`, {}, {{ params: {{ {} }} }});", method, path, body.name, query_params)?;
149                }
150                else {
151                    writeln!(file, "    return client.{}(`{}`, undefined, {{ params: {{ {} }} }});", method, path, query_params)?;
152                }
153            }
154            _ => {
155                if let Some(body) = body_param {
156                    writeln!(file, "    return client.{}(`{}`, {}, {{ params: {{ {} }} }});", method, path, body.name, query_params)?;
157                }
158                else {
159                    writeln!(file, "    return client.{}(`{}`, {{ params: {{ {} }} }});", method, path, query_params)?;
160                }
161            }
162        }
163
164        writeln!(file, "  }},")?;
165
166        Ok(())
167    }
168
169    /// 生成参数列表
170    ///
171    /// # Arguments
172    /// * `params` - 参数列表
173    ///
174    /// # Returns
175    /// * `String` - 参数列表字符串
176    fn generate_params_list(params: &[ApiParam]) -> String {
177        let filtered: Vec<String> = params.iter().filter(|p| p.param_type != ApiParamType::CurrentUser && p.param_type != ApiParamType::Session && p.param_type != ApiParamType::I18n).map(|p| format!("{}: {}", p.name, p.type_name)).collect();
178
179        filtered.join(", ")
180    }
181
182    /// 生成路径字符串(替换路径参数)
183    ///
184    /// # Arguments
185    /// * `path` - 原始路径
186    /// * `params` - 参数列表
187    ///
188    /// # Returns
189    /// * `String` - 替换后的路径字符串
190    fn generate_path(path: &str, params: &[ApiParam]) -> String {
191        let mut result = path.to_string();
192
193        for param in params {
194            if param.param_type == ApiParamType::Path {
195                let placeholder = if let Some(extractor_param) = &param.extractor_param { format!(":{}", extractor_param) } else { format!(":{}", param.name) };
196                result = result.replace(&placeholder, &format!("${{{}}}", param.name));
197            }
198        }
199
200        result
201    }
202
203    /// 生成查询参数字符串
204    ///
205    /// # Arguments
206    /// * `params` - 参数列表
207    ///
208    /// # Returns
209    /// * `String` - 查询参数字符串
210    fn generate_query_params(params: &[ApiParam]) -> String {
211        let query_params: Vec<String> = params.iter().filter(|p| p.param_type == ApiParamType::Query).map(|p| p.name.clone()).collect();
212
213        query_params.join(", ")
214    }
215
216    /// 生成端点元数据
217    ///
218    /// # Arguments
219    /// * `file` - 输出文件
220    /// * `controller_name` - 控制器名称
221    /// * `endpoint` - API端点
222    ///
223    /// # Returns
224    /// * `std::io::Result<()>` - 操作结果
225    fn generate_endpoint_metadata(file: &mut File, controller_name: &str, endpoint: &ApiEndpoint) -> std::io::Result<()> {
226        let nargo_id = format!("{}.{}", controller_name, endpoint.name);
227
228        writeln!(file, "( {}.{} as any)._nargo_id = '{}';", controller_name, endpoint.name, nargo_id)?;
229        writeln!(file, "( {}.{} as any).url = '{}';", controller_name, endpoint.name, endpoint.path)?;
230        writeln!(file, "( {}.{} as any).method = '{}';", controller_name, endpoint.name, endpoint.method)?;
231
232        Ok(())
233    }
234}