Skip to main content

cargo_mate/tools/
proto_bind.rs

1use super::{Tool, Result, ToolError, common_options, parse_output_format, OutputFormat};
2use clap::{Arg, ArgMatches, Command};
3use colored::*;
4use std::fs;
5use std::path::Path;
6use serde_json::{Value, Map};
7use serde_yaml;
8use syn::{parse_file, File, Item, ItemStruct, Fields, Field, Type, PathSegment, Ident};
9use quote::quote;
10use proc_macro2::TokenStream;
11#[derive(Debug, Clone)]
12pub struct ProtoBindTool;
13#[derive(Debug, Clone)]
14struct ProtoSchema {
15    messages: Vec<ProtoMessage>,
16    services: Vec<ProtoService>,
17}
18#[derive(Debug, Clone)]
19struct ProtoMessage {
20    name: String,
21    fields: Vec<ProtoField>,
22}
23#[derive(Debug, Clone)]
24struct ProtoField {
25    name: String,
26    ty: String,
27    number: u32,
28    repeated: bool,
29}
30#[derive(Debug, Clone)]
31struct ProtoService {
32    name: String,
33    methods: Vec<ProtoMethod>,
34}
35#[derive(Debug, Clone)]
36struct ProtoMethod {
37    name: String,
38    input_type: String,
39    output_type: String,
40}
41#[derive(Debug, Clone)]
42struct OpenAPISchema {
43    components: OpenAPIComponents,
44    paths: Vec<OpenAPIPath>,
45}
46#[derive(Debug, Clone)]
47struct OpenAPIComponents {
48    schemas: Vec<OpenAPISchemaItem>,
49}
50#[derive(Debug, Clone)]
51struct OpenAPISchemaItem {
52    name: String,
53    properties: Vec<OpenAPIProperty>,
54}
55#[derive(Debug, Clone)]
56struct OpenAPIProperty {
57    name: String,
58    ty: String,
59    required: bool,
60}
61#[derive(Debug, Clone)]
62struct OpenAPIPath {
63    path: String,
64    method: String,
65    operation_id: String,
66    request_body: Option<String>,
67    response_body: Option<String>,
68}
69#[derive(Debug, Clone)]
70struct GraphQLSchema {
71    types: Vec<GraphQLType>,
72    queries: Vec<GraphQLField>,
73    mutations: Vec<GraphQLField>,
74}
75#[derive(Debug, Clone)]
76struct GraphQLType {
77    name: String,
78    fields: Vec<GraphQLField>,
79}
80#[derive(Debug, Clone)]
81struct GraphQLField {
82    name: String,
83    ty: String,
84    args: Vec<GraphQLArg>,
85}
86#[derive(Debug, Clone)]
87struct GraphQLArg {
88    name: String,
89    ty: String,
90}
91impl ProtoBindTool {
92    pub fn new() -> Self {
93        Self
94    }
95    fn parse_proto_file(&self, file_path: &str) -> Result<ProtoSchema> {
96        let content = fs::read_to_string(file_path)
97            .map_err(|e| ToolError::ExecutionFailed(
98                format!("Failed to read {}: {}", file_path, e),
99            ))?;
100        let mut messages = Vec::new();
101        let mut services = Vec::new();
102        let mut lines = content.lines().peekable();
103        while let Some(line) = lines.next() {
104            let line = line.trim();
105            if line.starts_with("message ") {
106                if let Some(message) = self.parse_proto_message(line, &mut lines) {
107                    messages.push(message);
108                }
109            } else if line.starts_with("service ") {
110                if let Some(service) = self.parse_proto_service(line, &mut lines) {
111                    services.push(service);
112                }
113            }
114        }
115        Ok(ProtoSchema { messages, services })
116    }
117    fn parse_proto_message(
118        &self,
119        first_line: &str,
120        lines: &mut std::iter::Peekable<std::str::Lines>,
121    ) -> Option<ProtoMessage> {
122        let name = first_line
123            .strip_prefix("message ")?
124            .trim_end_matches(" {")
125            .trim()
126            .to_string();
127        let mut fields = Vec::new();
128        while let Some(line) = lines.next() {
129            let line = line.trim();
130            if line == "}" {
131                break;
132            }
133            if line.is_empty() || line.starts_with("//") {
134                continue;
135            }
136            if let Some(field) = self.parse_proto_field(line) {
137                fields.push(field);
138            }
139        }
140        Some(ProtoMessage { name, fields })
141    }
142    fn parse_proto_field(&self, line: &str) -> Option<ProtoField> {
143        let parts: Vec<&str> = line.split_whitespace().collect();
144        if parts.len() < 3 {
145            return None;
146        }
147        let repeated = parts[0] == "repeated";
148        let ty = if repeated { parts[1] } else { parts[0] };
149        let name = if repeated { parts[2] } else { parts[1] };
150        let number_part = if repeated { parts[3] } else { parts[2] };
151        let number_str = number_part
152            .strip_prefix("=")
153            .unwrap_or("")
154            .trim_end_matches(";");
155        let number: u32 = number_str.parse().ok()?;
156        Some(ProtoField {
157            name: name.to_string(),
158            ty: ty.to_string(),
159            number,
160            repeated,
161        })
162    }
163    fn parse_proto_service(
164        &self,
165        first_line: &str,
166        lines: &mut std::iter::Peekable<std::str::Lines>,
167    ) -> Option<ProtoService> {
168        let name = first_line
169            .strip_prefix("service ")?
170            .trim_end_matches(" {")
171            .trim()
172            .to_string();
173        let mut methods = Vec::new();
174        while let Some(line) = lines.next() {
175            let line = line.trim();
176            if line == "}" {
177                break;
178            }
179            if line.is_empty() || line.starts_with("//") {
180                continue;
181            }
182            if let Some(method) = self.parse_proto_method(line) {
183                methods.push(method);
184            }
185        }
186        Some(ProtoService { name, methods })
187    }
188    fn parse_proto_method(&self, line: &str) -> Option<ProtoMethod> {
189        let line = line.strip_prefix("rpc ")?.trim();
190        let paren_pos = line.find('(')?;
191        let returns_pos = line.find("returns")?;
192        let name = line[..paren_pos].trim().to_string();
193        let input_part = &line[paren_pos + 1..returns_pos];
194        let output_part = &line[returns_pos + 8..];
195        let input_type = input_part.trim_end_matches(')').trim().to_string();
196        let output_type = output_part
197            .trim_start_matches('(')
198            .trim_end_matches(");")
199            .trim()
200            .to_string();
201        Some(ProtoMethod {
202            name,
203            input_type,
204            output_type,
205        })
206    }
207    fn parse_openapi_spec(&self, file_path: &str) -> Result<OpenAPISchema> {
208        let content = fs::read_to_string(file_path)
209            .map_err(|e| ToolError::ExecutionFailed(
210                format!("Failed to read {}: {}", file_path, e),
211            ))?;
212        let ext = Path::new(file_path).extension().unwrap_or_default();
213        let value: Value = if ext == "yaml" || ext == "yml" {
214            serde_yaml::from_str(&content)
215                .map_err(|e| ToolError::ExecutionFailed(
216                    format!("Failed to parse YAML: {}", e),
217                ))?
218        } else {
219            serde_json::from_str(&content)
220                .map_err(|e| ToolError::ExecutionFailed(
221                    format!("Failed to parse JSON: {}", e),
222                ))?
223        };
224        let mut schemas = Vec::new();
225        let mut paths = Vec::new();
226        if let Some(components) = value.get("components") {
227            if let Some(schemas_obj) = components.get("schemas") {
228                if let Some(schemas_map) = schemas_obj.as_object() {
229                    for (name, schema) in schemas_map {
230                        if let Some(schema_obj) = schema.as_object() {
231                            let mut properties = Vec::new();
232                            let mut required_fields = Vec::new();
233                            if let Some(props) = schema_obj.get("properties") {
234                                if let Some(props_map) = props.as_object() {
235                                    for (prop_name, prop_schema) in props_map {
236                                        if let Some(prop_obj) = prop_schema.as_object() {
237                                            let ty = self.extract_openapi_type(prop_obj);
238                                            properties
239                                                .push(OpenAPIProperty {
240                                                    name: prop_name.clone(),
241                                                    ty,
242                                                    required: false,
243                                                });
244                                        }
245                                    }
246                                }
247                            }
248                            if let Some(required) = schema_obj.get("required") {
249                                if let Some(required_arr) = required.as_array() {
250                                    for req in required_arr {
251                                        if let Some(req_str) = req.as_str() {
252                                            required_fields.push(req_str.to_string());
253                                        }
254                                    }
255                                }
256                            }
257                            for prop in &mut properties {
258                                prop.required = required_fields.contains(&prop.name);
259                            }
260                            schemas
261                                .push(OpenAPISchemaItem {
262                                    name: name.clone(),
263                                    properties,
264                                });
265                        }
266                    }
267                }
268            }
269        }
270        if let Some(paths_obj) = value.get("paths") {
271            if let Some(paths_map) = paths_obj.as_object() {
272                for (path, path_item) in paths_map {
273                    if let Some(path_obj) = path_item.as_object() {
274                        for (method, operation) in path_obj {
275                            if let Some(op_obj) = operation.as_object() {
276                                let operation_id = op_obj
277                                    .get("operationId")
278                                    .and_then(|v| v.as_str())
279                                    .unwrap_or("unknown")
280                                    .to_string();
281                                let request_body = self
282                                    .extract_openapi_request_body(op_obj);
283                                let response_body = self
284                                    .extract_openapi_response_body(op_obj);
285                                paths
286                                    .push(OpenAPIPath {
287                                        path: path.clone(),
288                                        method: method.to_uppercase(),
289                                        operation_id,
290                                        request_body,
291                                        response_body,
292                                    });
293                            }
294                        }
295                    }
296                }
297            }
298        }
299        Ok(OpenAPISchema {
300            components: OpenAPIComponents { schemas },
301            paths,
302        })
303    }
304    fn extract_openapi_type(&self, schema: &Map<String, Value>) -> String {
305        if let Some(ty) = schema.get("type") {
306            if let Some(type_str) = ty.as_str() {
307                match type_str {
308                    "string" => "String".to_string(),
309                    "integer" => "i64".to_string(),
310                    "number" => "f64".to_string(),
311                    "boolean" => "bool".to_string(),
312                    "array" => {
313                        if let Some(items) = schema.get("items") {
314                            if let Some(items_obj) = items.as_object() {
315                                let item_type = self.extract_openapi_type(items_obj);
316                                format!("Vec<{}>", item_type)
317                            } else {
318                                "Vec<String>".to_string()
319                            }
320                        } else {
321                            "Vec<String>".to_string()
322                        }
323                    }
324                    _ => "String".to_string(),
325                }
326            } else {
327                "String".to_string()
328            }
329        } else {
330            "String".to_string()
331        }
332    }
333    fn extract_openapi_request_body(
334        &self,
335        operation: &Map<String, Value>,
336    ) -> Option<String> {
337        operation
338            .get("requestBody")
339            .and_then(|rb| rb.get("content"))
340            .and_then(|content| content.get("application/json"))
341            .and_then(|schema| schema.get("schema"))
342            .and_then(|schema| schema.get("$ref"))
343            .and_then(|ref_str| ref_str.as_str())
344            .map(|ref_str| {
345                ref_str
346                    .strip_prefix("#/components/schemas/")
347                    .unwrap_or(ref_str)
348                    .to_string()
349            })
350    }
351    fn extract_openapi_response_body(
352        &self,
353        operation: &Map<String, Value>,
354    ) -> Option<String> {
355        operation
356            .get("responses")
357            .and_then(|resp| resp.get("200"))
358            .and_then(|resp| resp.get("content"))
359            .and_then(|content| content.get("application/json"))
360            .and_then(|schema| schema.get("schema"))
361            .and_then(|schema| schema.get("$ref"))
362            .and_then(|ref_str| ref_str.as_str())
363            .map(|ref_str| {
364                ref_str
365                    .strip_prefix("#/components/schemas/")
366                    .unwrap_or(ref_str)
367                    .to_string()
368            })
369    }
370    fn parse_graphql_schema(&self, file_path: &str) -> Result<GraphQLSchema> {
371        let content = fs::read_to_string(file_path)
372            .map_err(|e| ToolError::ExecutionFailed(
373                format!("Failed to read {}: {}", file_path, e),
374            ))?;
375        let mut types = Vec::new();
376        let mut queries = Vec::new();
377        let mut mutations = Vec::new();
378        let mut lines = content.lines().peekable();
379        while let Some(line) = lines.next() {
380            let line = line.trim();
381            if line.starts_with("type ") && !line.contains("Query")
382                && !line.contains("Mutation")
383            {
384                if let Some(ty) = self.parse_graphql_type(line, &mut lines) {
385                    types.push(ty);
386                }
387            } else if line.contains("type Query") {
388                queries = self.parse_graphql_fields(&mut lines);
389            } else if line.contains("type Mutation") {
390                mutations = self.parse_graphql_fields(&mut lines);
391            }
392        }
393        Ok(GraphQLSchema {
394            types,
395            queries,
396            mutations,
397        })
398    }
399    fn parse_graphql_type(
400        &self,
401        first_line: &str,
402        lines: &mut std::iter::Peekable<std::str::Lines>,
403    ) -> Option<GraphQLType> {
404        let name = first_line
405            .strip_prefix("type ")?
406            .trim_end_matches(" {")
407            .trim()
408            .to_string();
409        let fields = self.parse_graphql_fields(lines);
410        Some(GraphQLType { name, fields })
411    }
412    fn parse_graphql_fields(
413        &self,
414        lines: &mut std::iter::Peekable<std::str::Lines>,
415    ) -> Vec<GraphQLField> {
416        let mut fields = Vec::new();
417        while let Some(line) = lines.next() {
418            let line = line.trim();
419            if line == "}" {
420                break;
421            }
422            if line.is_empty() || line.starts_with("#") {
423                continue;
424            }
425            if let Some(field) = self.parse_graphql_field(line) {
426                fields.push(field);
427            }
428        }
429        fields
430    }
431    fn parse_graphql_field(&self, line: &str) -> Option<GraphQLField> {
432        let line = line.trim_end_matches(',').trim();
433        let colon_pos = line.find(':')?;
434        let name_part = &line[..colon_pos];
435        let type_part = &line[colon_pos + 1..];
436        let (name, args) = if let Some(paren_pos) = name_part.find('(') {
437            let name = name_part[..paren_pos].trim().to_string();
438            let args_str = &name_part[paren_pos + 1..name_part.len() - 1];
439            let args = self.parse_graphql_args(args_str);
440            (name, args)
441        } else {
442            (name_part.trim().to_string(), Vec::new())
443        };
444        let ty = self.parse_graphql_type_annotation(type_part.trim());
445        Some(GraphQLField { name, ty, args })
446    }
447    fn parse_graphql_args(&self, args_str: &str) -> Vec<GraphQLArg> {
448        let mut args = Vec::new();
449        if args_str.is_empty() {
450            return args;
451        }
452        for arg in args_str.split(',') {
453            let arg = arg.trim();
454            if let Some(colon_pos) = arg.find(':') {
455                let name = arg[..colon_pos].trim().to_string();
456                let ty = self.parse_graphql_type_annotation(arg[colon_pos + 1..].trim());
457                args.push(GraphQLArg { name, ty });
458            }
459        }
460        args
461    }
462    fn parse_graphql_type_annotation(&self, ty_str: &str) -> String {
463        let ty_str = ty_str.trim();
464        if ty_str.starts_with('[') && ty_str.ends_with(']') {
465            let inner = &ty_str[1..ty_str.len() - 1];
466            let inner = inner.trim_end_matches('!');
467            let inner_type = self.parse_graphql_type_annotation(inner);
468            format!("Vec<{}>", inner_type)
469        } else {
470            let ty = ty_str.trim_end_matches('!');
471            match ty {
472                "String" => "String".to_string(),
473                "Int" => "i32".to_string(),
474                "Float" => "f64".to_string(),
475                "Boolean" => "bool".to_string(),
476                "ID" => "String".to_string(),
477                _ => ty.to_string(),
478            }
479        }
480    }
481    fn generate_rust_bindings(
482        &self,
483        schema: &SchemaType,
484        format: &str,
485    ) -> Result<String> {
486        match schema {
487            SchemaType::Proto(proto) => self.generate_proto_rust_bindings(proto, format),
488            SchemaType::OpenAPI(openapi) => {
489                self.generate_openapi_rust_bindings(openapi, format)
490            }
491            SchemaType::GraphQL(graphql) => {
492                self.generate_graphql_rust_bindings(graphql, format)
493            }
494        }
495    }
496    fn generate_proto_rust_bindings(
497        &self,
498        schema: &ProtoSchema,
499        format: &str,
500    ) -> Result<String> {
501        let mut code = format!(
502            "// Generated Rust bindings from Protocol Buffer schema\n\n"
503        );
504        if format == "serde" {
505            code.push_str("use serde::{Deserialize, Serialize};\n\n");
506        }
507        for message in &schema.messages {
508            if format == "serde" {
509                code.push_str("#[derive(Debug, Clone, Serialize, Deserialize)]\n");
510            } else {
511                code.push_str("#[derive(Debug, Clone)]\n");
512            }
513            code.push_str(&format!("pub struct {} {{\n", message.name));
514            for field in &message.fields {
515                let rust_type = self.proto_type_to_rust(&field.ty, field.repeated);
516                code.push_str(&format!("    pub {}: {},\n", field.name, rust_type));
517            }
518            code.push_str("}\n\n");
519            code.push_str(&format!("impl {} {{\n", message.name));
520            code.push_str(&format!("    pub fn new() -> Self {{\n"));
521            code.push_str(&format!("        {} {{\n", message.name));
522            for field in &message.fields {
523                let default_value = self.get_default_value(&field.ty, field.repeated);
524                code.push_str(
525                    &format!("            {}: {},\n", field.name, default_value),
526                );
527            }
528            code.push_str("        }\n");
529            code.push_str("    }\n");
530            code.push_str("}\n\n");
531        }
532        for service in &schema.services {
533            code.push_str(&format!("pub trait {} {{\n", service.name));
534            for method in &service.methods {
535                code.push_str(
536                    &format!("    async fn {}(\n", method.name.to_lowercase()),
537                );
538                code.push_str(&format!("        &mut self,\n"));
539                code.push_str(&format!("        request: {},\n", method.input_type));
540                code.push_str(
541                    &format!("    ) -> Result<{}, tonic::Status>;\n", method.output_type),
542                );
543            }
544            code.push_str("}\n\n");
545        }
546        Ok(code)
547    }
548    fn generate_openapi_rust_bindings(
549        &self,
550        schema: &OpenAPISchema,
551        format: &str,
552    ) -> Result<String> {
553        let mut code = format!("// Generated Rust bindings from OpenAPI schema\n\n");
554        if format == "serde" {
555            code.push_str("use serde::{Deserialize, Serialize};\n\n");
556        }
557        for schema_item in &schema.components.schemas {
558            if format == "serde" {
559                code.push_str("#[derive(Debug, Clone, Serialize, Deserialize)]\n");
560            } else {
561                code.push_str("#[derive(Debug, Clone)]\n");
562            }
563            code.push_str(&format!("pub struct {} {{\n", schema_item.name));
564            for property in &schema_item.properties {
565                let rust_type = if property.required {
566                    property.ty.clone()
567                } else {
568                    format!("Option<{}>", property.ty)
569                };
570                code.push_str(&format!("    pub {}: {},\n", property.name, rust_type));
571            }
572            code.push_str("}\n\n");
573        }
574        code.push_str("pub struct ApiClient {\n");
575        code.push_str("    base_url: String,\n");
576        code.push_str("    client: reqwest::Client,\n");
577        code.push_str("}\n\n");
578        code.push_str("impl ApiClient {\n");
579        code.push_str("    pub fn new(base_url: String) -> Self {\n");
580        code.push_str("        Self {\n");
581        code.push_str("            base_url,\n");
582        code.push_str("            client: reqwest::Client::new(),\n");
583        code.push_str("        }\n");
584        code.push_str("    }\n\n");
585        for path in &schema.paths {
586            let method_name = path.operation_id.replace("-", "_").replace(".", "_");
587            let http_method = path.method.to_lowercase();
588            code.push_str(&format!("    pub async fn {}(&self", method_name));
589            if let Some(req_body) = &path.request_body {
590                code.push_str(&format!(", request: &{}", req_body));
591            }
592            let response_type = path
593                .response_body
594                .as_ref()
595                .map(|s| s.as_str())
596                .unwrap_or("String");
597            code.push_str(
598                &format!(") -> Result<{}, reqwest::Error> {{\n", response_type),
599            );
600            code.push_str(
601                &format!(
602                    "        let url = format!(\"{{}}{{}}\", self.base_url, \"{}\");\n",
603                    path.path
604                ),
605            );
606            if let Some(req_body) = &path.request_body {
607                code.push_str(
608                    &format!("        let response = self.client.{}(url)\n", http_method),
609                );
610                code.push_str("            .json(request)\n");
611                code.push_str("            .send()\n");
612                code.push_str("            .await?;\n");
613            } else {
614                code.push_str(
615                    &format!("        let response = self.client.{}(url)\n", http_method),
616                );
617                code.push_str("            .send()\n");
618                code.push_str("            .await?;\n");
619            }
620            if let Some(resp_body) = &path.response_body {
621                code.push_str(
622                    &format!(
623                        "        let result = response.json::<{}>().await?;\n", resp_body
624                    ),
625                );
626                code.push_str("        Ok(result)\n");
627            } else {
628                code.push_str("        Ok(())\n");
629            }
630            code.push_str("    }\n\n");
631        }
632        code.push_str("}\n");
633        Ok(code)
634    }
635    fn generate_graphql_rust_bindings(
636        &self,
637        schema: &GraphQLSchema,
638        format: &str,
639    ) -> Result<String> {
640        let mut code = format!("// Generated Rust bindings from GraphQL schema\n\n");
641        if format == "serde" {
642            code.push_str("use serde::{Deserialize, Serialize};\n");
643            code.push_str("use graphql_client::{GraphQLQuery, Response};\n\n");
644        }
645        for ty in &schema.types {
646            if format == "serde" {
647                code.push_str("#[derive(Debug, Clone, Serialize, Deserialize)]\n");
648            } else {
649                code.push_str("#[derive(Debug, Clone)]\n");
650            }
651            code.push_str(&format!("pub struct {} {{\n", ty.name));
652            for field in &ty.fields {
653                code.push_str(&format!("    pub {}: {},\n", field.name, field.ty));
654            }
655            code.push_str("}\n\n");
656        }
657        for query in &schema.queries {
658            let query_name = format!("{}Query", query.name);
659            let variables_name = format!("{}Variables", query.name);
660            if format == "serde" {
661                code.push_str(&format!("#[derive(GraphQLQuery))]\n"));
662                code.push_str(&format!("#[graphql(\n"));
663                code.push_str(&format!("    schema_path = \"schema.json\",\n"));
664                code.push_str(
665                    &format!(
666                        "    query_path = \"{}.graphql\",\n", query.name.to_lowercase()
667                    ),
668                );
669                code.push_str(&format!("    response_derives = Clone\n"));
670                code.push_str(&format!(")]\n"));
671            }
672            code.push_str(&format!("pub struct {};\n\n", query_name));
673            if !query.args.is_empty() {
674                code.push_str(&format!("pub struct {} {{\n", variables_name));
675                for arg in &query.args {
676                    code.push_str(&format!("    pub {}: {},\n", arg.name, arg.ty));
677                }
678                code.push_str("}\n\n");
679            }
680        }
681        Ok(code)
682    }
683    fn generate_grpc_client(&self, schema: &ProtoSchema) -> Result<String> {
684        let mut code = "// Generated gRPC client code\n\n".to_string();
685        code.push_str("use tonic::transport::Channel;\n");
686        code.push_str("use tonic::{Request, Response, Status};\n\n");
687        for service in &schema.services {
688            let client_name = format!("{}Client", service.name);
689            code.push_str("#[derive(Debug, Clone)]\n");
690            code.push_str(&format!("pub struct {} {{\n", client_name));
691            code.push_str("    client: Box<dyn ");
692            code.push_str(&service.name);
693            code.push_str(">,\n");
694            code.push_str("}\n\n");
695            code.push_str(&format!("impl {} {{\n", client_name));
696            code.push_str(
697                "    pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>\n",
698            );
699            code.push_str("    where\n");
700            code.push_str(
701                "        D: std::convert::TryInto<tonic::transport::Endpoint>,\n",
702            );
703            code.push_str(
704                "        D::Error: Into<Box<dyn std::error::Error + Send + Sync>>,\n",
705            );
706            code.push_str("    {\n");
707            code.push_str(
708                "        let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;\n",
709            );
710            code.push_str("        Ok(Self {\n");
711            code.push_str("            client: Box::new(\n");
712            code.push_str("                // Initialize your service client here\n");
713            code.push_str("                todo!()\n");
714            code.push_str("            ),\n");
715            code.push_str("        })\n");
716            code.push_str("    }\n\n");
717            for method in &service.methods {
718                let method_name = method.name.to_lowercase();
719                code.push_str(&format!("    pub async fn {}(\n", method_name));
720                code.push_str("        &mut self,\n");
721                code.push_str(&format!("        request: {},\n", method.input_type));
722                code.push_str(
723                    &format!("    ) -> Result<{}, Status> {{\n", method.output_type),
724                );
725                code.push_str("        self.client.");
726                code.push_str(&method_name);
727                code.push_str("(request).await\n");
728                code.push_str("    }\n\n");
729            }
730            code.push_str("}\n\n");
731        }
732        Ok(code)
733    }
734    fn proto_type_to_rust(&self, proto_type: &str, repeated: bool) -> String {
735        let base_type = match proto_type {
736            "string" => "String",
737            "int32" => "i32",
738            "int64" => "i64",
739            "uint32" => "u32",
740            "uint64" => "u64",
741            "sint32" => "i32",
742            "sint64" => "i64",
743            "fixed32" => "u32",
744            "fixed64" => "u64",
745            "sfixed32" => "i32",
746            "sfixed64" => "i64",
747            "bool" => "bool",
748            "float" => "f32",
749            "double" => "f64",
750            "bytes" => "Vec<u8>",
751            _ => proto_type,
752        };
753        if repeated { format!("Vec<{}>", base_type) } else { base_type.to_string() }
754    }
755    fn get_default_value(&self, proto_type: &str, repeated: bool) -> String {
756        if repeated {
757            "Vec::new()".to_string()
758        } else {
759            match proto_type {
760                "string" => "String::new()".to_string(),
761                "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64" | "fixed32"
762                | "fixed64" | "sfixed32" | "sfixed64" => "0".to_string(),
763                "bool" => "false".to_string(),
764                "float" | "double" => "0.0".to_string(),
765                "bytes" => "Vec::new()".to_string(),
766                _ => format!("{}::new()", proto_type),
767            }
768        }
769    }
770}
771#[derive(Debug)]
772enum SchemaType {
773    Proto(ProtoSchema),
774    OpenAPI(OpenAPISchema),
775    GraphQL(GraphQLSchema),
776}
777impl Tool for ProtoBindTool {
778    fn name(&self) -> &'static str {
779        "proto-bind"
780    }
781    fn description(&self) -> &'static str {
782        "Generate Rust bindings from Protocol Buffers, OpenAPI specs, or GraphQL schemas"
783    }
784    fn command(&self) -> Command {
785        Command::new(self.name())
786            .about(self.description())
787            .long_about(
788                "Auto-generate Rust bindings from various schema formats including Protocol Buffers (.proto), OpenAPI/Swagger specs (JSON/YAML), and GraphQL schemas (.graphql). Supports Serde derives and gRPC client generation.",
789            )
790            .args(
791                &[
792                    Arg::new("input")
793                        .long("input")
794                        .short('i')
795                        .help("Input schema file (.proto, .json, .yaml, .graphql)")
796                        .required(true),
797                    Arg::new("format")
798                        .long("format")
799                        .short('f')
800                        .help("Input format: proto, openapi, graphql")
801                        .required(true),
802                    Arg::new("output")
803                        .long("output")
804                        .short('o')
805                        .help("Output file for generated bindings")
806                        .default_value("generated/bindings.rs"),
807                    Arg::new("grpc")
808                        .long("grpc")
809                        .help("Generate gRPC client code (proto only)")
810                        .action(clap::ArgAction::SetTrue),
811                    Arg::new("serde")
812                        .long("serde")
813                        .help("Add Serde derive macros")
814                        .action(clap::ArgAction::SetTrue),
815                    Arg::new("package")
816                        .long("package")
817                        .short('p')
818                        .help("Rust package name for generated code"),
819                ],
820            )
821            .args(&common_options())
822    }
823    fn execute(&self, matches: &ArgMatches) -> Result<()> {
824        let input = matches.get_one::<String>("input").unwrap();
825        let format = matches.get_one::<String>("format").unwrap();
826        let output = matches.get_one::<String>("output").unwrap();
827        let grpc = matches.get_flag("grpc");
828        let serde = matches.get_flag("serde");
829        let package = matches.get_one::<String>("package");
830        let dry_run = matches.get_flag("dry-run");
831        let verbose = matches.get_flag("verbose");
832        let output_format = parse_output_format(matches);
833        println!(
834            "šŸ”§ {} - {}", "CargoMate ProtoBind".bold().blue(), self.description()
835            .cyan()
836        );
837        if !Path::new(input).exists() {
838            return Err(
839                ToolError::InvalidArguments(format!("Input file not found: {}", input)),
840            );
841        }
842        let schema = match format.as_str() {
843            "proto" => {
844                let proto_schema = self.parse_proto_file(input)?;
845                if verbose {
846                    println!(
847                        "   šŸ“„ Found {} messages and {} services", proto_schema
848                        .messages.len(), proto_schema.services.len()
849                    );
850                }
851                SchemaType::Proto(proto_schema)
852            }
853            "openapi" => {
854                let openapi_schema = self.parse_openapi_spec(input)?;
855                if verbose {
856                    println!(
857                        "   šŸ“„ Found {} schemas and {} paths", openapi_schema
858                        .components.schemas.len(), openapi_schema.paths.len()
859                    );
860                }
861                SchemaType::OpenAPI(openapi_schema)
862            }
863            "graphql" => {
864                let graphql_schema = self.parse_graphql_schema(input)?;
865                if verbose {
866                    println!(
867                        "   šŸ“„ Found {} types, {} queries, and {} mutations",
868                        graphql_schema.types.len(), graphql_schema.queries.len(),
869                        graphql_schema.mutations.len()
870                    );
871                }
872                SchemaType::GraphQL(graphql_schema)
873            }
874            _ => {
875                return Err(
876                    ToolError::InvalidArguments(
877                        format!("Unsupported format: {}", format),
878                    ),
879                );
880            }
881        };
882        let format_option = if serde { "serde" } else { "plain" };
883        let mut rust_code = self.generate_rust_bindings(&schema, format_option)?;
884        if let Some(pkg) = package {
885            let package_decl = format!("// Package: {}\n", pkg);
886            rust_code.insert_str(0, &package_decl);
887        }
888        let mut grpc_code = String::new();
889        if grpc && matches!(schema, SchemaType::Proto(_)) {
890            if let SchemaType::Proto(proto_schema) = &schema {
891                grpc_code = self.generate_grpc_client(proto_schema)?;
892            }
893        }
894        if !grpc_code.is_empty() {
895            rust_code.push_str("\n\n");
896            rust_code.push_str(&grpc_code);
897        }
898        match output_format {
899            OutputFormat::Human => {
900                println!("  āœ… Generated Rust bindings for {} format", format.bold());
901                println!("     → {}", output.cyan());
902                if grpc && !grpc_code.is_empty() {
903                    println!("  āœ… Generated gRPC client code");
904                }
905                if serde {
906                    println!("  āœ… Added Serde derive macros");
907                }
908                if dry_run {
909                    println!("   šŸ“‹ {}", "Generated code preview:".bold());
910                    println!("   {}", "─".repeat(50));
911                    for (i, line) in rust_code.lines().take(20).enumerate() {
912                        if i < 19 {
913                            println!("   {}", line);
914                        } else {
915                            println!("   ... (truncated)");
916                            break;
917                        }
918                    }
919                } else {
920                    if let Some(parent) = Path::new(output).parent() {
921                        fs::create_dir_all(parent)
922                            .map_err(|e| ToolError::ExecutionFailed(
923                                format!("Failed to create output directory: {}", e),
924                            ))?;
925                    }
926                    fs::write(output, rust_code)
927                        .map_err(|e| ToolError::ExecutionFailed(
928                            format!("Failed to write {}: {}", output, e),
929                        ))?;
930                    println!("  šŸ’¾ File written successfully");
931                }
932            }
933            OutputFormat::Json => {
934                let result = serde_json::json!(
935                    { "format" : format, "input" : input, "output" : output,
936                    "grpc_generated" : grpc && ! grpc_code.is_empty(), "serde_enabled" :
937                    serde, "code_preview" : rust_code.lines().take(10).collect::< Vec < _
938                    >> ().join("\n") }
939                );
940                println!("{}", serde_json::to_string_pretty(& result).unwrap());
941            }
942            OutputFormat::Table => {
943                println!(
944                    "{:<15} {:<10} {:<8} {:<8}", "Format", "Input", "gRPC", "Serde"
945                );
946                println!("{}", "─".repeat(50));
947                println!(
948                    "{:<15} {:<10} {:<8} {:<8}", format, Path::new(input).file_name()
949                    .unwrap_or_default().to_string_lossy(), if grpc { "Yes" } else { "No"
950                    }, if serde { "Yes" } else { "No" }
951                );
952            }
953        }
954        println!("\nšŸŽ‰ Binding generation completed!");
955        Ok(())
956    }
957}
958impl Default for ProtoBindTool {
959    fn default() -> Self {
960        Self::new()
961    }
962}