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}