1mod writer;
20
21use crate::error::{Error, Result};
22use crate::MAX_FIELD_NUMBER;
23use prost::Message;
24use prost_reflect::{DescriptorPool, FileDescriptor};
25use prost_types::FileDescriptorProto;
26use std::fmt::Write as FmtWrite;
27
28pub use writer::{NullWriter, ProtoWriter, StatsWriter};
29
30#[derive(Debug, Clone)]
32pub struct ReconstructorConfig {
33 pub indent_str: String,
35 pub include_comments: bool,
37 pub sort_fields: bool,
39}
40
41impl Default for ReconstructorConfig {
42 fn default() -> Self {
43 Self {
44 indent_str: " ".to_string(),
45 include_comments: true,
46 sort_fields: false,
47 }
48 }
49}
50
51impl ReconstructorConfig {
52 pub fn new() -> Self {
54 Self::default()
55 }
56
57 pub fn indent_str(mut self, s: impl Into<String>) -> Self {
59 self.indent_str = s.into();
60 self
61 }
62
63 pub fn include_comments(mut self, include: bool) -> Self {
65 self.include_comments = include;
66 self
67 }
68
69 pub fn sort_fields(mut self, sort: bool) -> Self {
71 self.sort_fields = sort;
72 self
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum ProtoSyntax {
79 Proto2,
81 Proto3,
83}
84
85impl ProtoSyntax {
86 pub fn as_str(&self) -> &'static str {
88 match self {
89 ProtoSyntax::Proto2 => "proto2",
90 ProtoSyntax::Proto3 => "proto3",
91 }
92 }
93}
94
95impl TryFrom<&str> for ProtoSyntax {
96 type Error = Error;
97
98 fn try_from(value: &str) -> Result<Self> {
99 match value {
100 "" | "proto2" => Ok(ProtoSyntax::Proto2),
101 "proto3" => Ok(ProtoSyntax::Proto3),
102 _ => Err(Error::UnsupportedSyntax {
103 syntax: value.to_string(),
104 }),
105 }
106 }
107}
108
109#[derive(Debug)]
111pub struct ProtoReconstructor {
112 proto: FileDescriptorProto,
114 descriptor: Option<FileDescriptor>,
116 config: ReconstructorConfig,
118}
119
120impl ProtoReconstructor {
121 pub fn from_bytes(data: &[u8]) -> Result<Self> {
123 let proto = FileDescriptorProto::decode(data)?;
124 Self::from_proto(proto)
125 }
126
127 pub fn from_proto(proto: FileDescriptorProto) -> Result<Self> {
129 let descriptor = Self::build_descriptor(&proto).ok();
131
132 Ok(Self {
133 proto,
134 descriptor,
135 config: ReconstructorConfig::default(),
136 })
137 }
138
139 pub fn with_config(mut self, config: ReconstructorConfig) -> Self {
141 self.config = config;
142 self
143 }
144
145 fn build_descriptor(proto: &FileDescriptorProto) -> Result<FileDescriptor> {
147 let fds = prost_types::FileDescriptorSet {
149 file: vec![proto.clone()],
150 };
151
152 let mut fds_bytes = Vec::new();
153 fds.encode(&mut fds_bytes).map_err(|e| {
154 Error::descriptor_build(format!("failed to encode descriptor set: {}", e))
155 })?;
156
157 let pool = DescriptorPool::decode(fds_bytes.as_slice()).map_err(|e| {
158 Error::descriptor_build(format!("failed to decode descriptor pool: {}", e))
159 })?;
160
161 pool.get_file_by_name(proto.name())
163 .ok_or_else(|| Error::descriptor_build("file not found in pool"))
164 }
165
166 pub fn filename(&self) -> &str {
168 self.proto.name()
169 }
170
171 pub fn output_filename(&self) -> String {
175 if let Some(opts) = &self.proto.options {
176 if let Some(go_package) = &opts.go_package {
177 if let Some(idx) = go_package.find(';') {
179 let import_path = &go_package[..idx];
180 let base = std::path::Path::new(self.proto.name())
181 .file_name()
182 .and_then(|n| n.to_str())
183 .unwrap_or(self.proto.name());
184 return format!("{}/{}", import_path, base);
185 }
186 }
187 }
188 self.proto.name().to_string()
189 }
190
191 pub fn syntax(&self) -> ProtoSyntax {
193 ProtoSyntax::try_from(self.proto.syntax()).unwrap_or(ProtoSyntax::Proto2)
194 }
195
196 pub fn file_descriptor(&self) -> Option<&FileDescriptor> {
200 self.descriptor.as_ref()
201 }
202
203 pub fn proto(&self) -> &FileDescriptorProto {
205 &self.proto
206 }
207
208 pub fn reconstruct(&self) -> String {
210 let mut output = String::new();
211 self.write_to(&mut output).expect("String write cannot fail");
212 output
213 }
214
215 pub fn write_to(&self, w: &mut impl FmtWrite) -> std::fmt::Result {
217 let mut writer = DefaultProtoWriter::new(w, &self.config);
218 writer.write_file(&self.proto, self.syntax())
219 }
220}
221
222struct DefaultProtoWriter<'a, W: FmtWrite> {
224 writer: &'a mut W,
225 config: &'a ReconstructorConfig,
226 indent_level: usize,
227}
228
229impl<'a, W: FmtWrite> DefaultProtoWriter<'a, W> {
230 fn new(writer: &'a mut W, config: &'a ReconstructorConfig) -> Self {
231 Self {
232 writer,
233 config,
234 indent_level: 0,
235 }
236 }
237
238 fn indent(&mut self) {
239 self.indent_level += 1;
240 }
241
242 fn dedent(&mut self) {
243 self.indent_level = self.indent_level.saturating_sub(1);
244 }
245
246 fn write_indent(&mut self) -> std::fmt::Result {
247 for _ in 0..self.indent_level {
248 write!(self.writer, "{}", self.config.indent_str)?;
249 }
250 Ok(())
251 }
252
253 fn writeln(&mut self, s: &str) -> std::fmt::Result {
254 self.write_indent()?;
255 writeln!(self.writer, "{}", s)
256 }
257
258 fn write_file(
259 &mut self,
260 proto: &FileDescriptorProto,
261 syntax: ProtoSyntax,
262 ) -> std::fmt::Result {
263 writeln!(self.writer, "syntax = \"{}\";", syntax.as_str())?;
265 writeln!(self.writer)?;
266
267 if !proto.package().is_empty() {
269 writeln!(self.writer, "package {};", proto.package())?;
270 writeln!(self.writer)?;
271 }
272
273 self.write_file_options(proto)?;
275
276 self.write_imports(proto)?;
278
279 for service in &proto.service {
281 self.write_service(service)?;
282 }
283
284 for message in &proto.message_type {
286 self.write_message(message, syntax)?;
287 }
288
289 for enum_type in &proto.enum_type {
291 self.write_enum(enum_type)?;
292 }
293
294 for extension in &proto.extension {
296 self.write_extension(extension, syntax)?;
297 }
298
299 Ok(())
300 }
301
302 fn write_file_options(&mut self, proto: &FileDescriptorProto) -> std::fmt::Result {
303 let Some(opts) = &proto.options else {
304 return Ok(());
305 };
306
307 let mut wrote_option = false;
308
309 macro_rules! write_string_option {
311 ($name:expr, $value:expr) => {
312 if let Some(v) = $value {
313 if !v.is_empty() {
314 writeln!(self.writer, "option {} = \"{}\";", $name, escape_string(v))?;
315 wrote_option = true;
316 }
317 }
318 };
319 }
320
321 macro_rules! write_bool_option {
322 ($name:expr, $value:expr) => {
323 if let Some(v) = $value {
324 writeln!(self.writer, "option {} = {};", $name, v)?;
325 wrote_option = true;
326 }
327 };
328 }
329
330 write_string_option!("java_package", opts.java_package.as_ref());
331 write_string_option!("java_outer_classname", opts.java_outer_classname.as_ref());
332 write_bool_option!("java_multiple_files", opts.java_multiple_files);
333 write_bool_option!("java_string_check_utf8", opts.java_string_check_utf8);
334 write_string_option!("go_package", opts.go_package.as_ref());
335 write_bool_option!("cc_enable_arenas", opts.cc_enable_arenas);
336 write_string_option!("objc_class_prefix", opts.objc_class_prefix.as_ref());
337 write_string_option!("csharp_namespace", opts.csharp_namespace.as_ref());
338 write_string_option!("swift_prefix", opts.swift_prefix.as_ref());
339 write_string_option!("php_class_prefix", opts.php_class_prefix.as_ref());
340 write_string_option!("php_namespace", opts.php_namespace.as_ref());
341 write_string_option!("php_metadata_namespace", opts.php_metadata_namespace.as_ref());
342 write_string_option!("ruby_package", opts.ruby_package.as_ref());
343
344 if wrote_option {
345 writeln!(self.writer)?;
346 }
347
348 Ok(())
349 }
350
351 fn write_imports(&mut self, proto: &FileDescriptorProto) -> std::fmt::Result {
352 if proto.dependency.is_empty() {
353 return Ok(());
354 }
355
356 let public_deps: std::collections::HashSet<_> =
358 proto.public_dependency.iter().map(|&i| i as usize).collect();
359 let weak_deps: std::collections::HashSet<_> =
360 proto.weak_dependency.iter().map(|&i| i as usize).collect();
361
362 for (i, dep) in proto.dependency.iter().enumerate() {
363 let modifier = if public_deps.contains(&i) {
364 "public "
365 } else if weak_deps.contains(&i) {
366 "weak "
367 } else {
368 ""
369 };
370 writeln!(self.writer, "import {}\"{}\";", modifier, dep)?;
371 }
372
373 writeln!(self.writer)?;
374 Ok(())
375 }
376
377 fn write_service(&mut self, service: &prost_types::ServiceDescriptorProto) -> std::fmt::Result {
378 writeln!(self.writer, "service {} {{", service.name())?;
379 self.indent();
380
381 for method in &service.method {
382 self.write_method(method)?;
383 }
384
385 self.dedent();
386 writeln!(self.writer, "}}")?;
387 writeln!(self.writer)?;
388 Ok(())
389 }
390
391 fn write_method(&mut self, method: &prost_types::MethodDescriptorProto) -> std::fmt::Result {
392 let client_streaming = method.client_streaming.unwrap_or(false);
393 let server_streaming = method.server_streaming.unwrap_or(false);
394
395 let input = if client_streaming {
396 format!("stream {}", method.input_type())
397 } else {
398 method.input_type().to_string()
399 };
400
401 let output = if server_streaming {
402 format!("stream {}", method.output_type())
403 } else {
404 method.output_type().to_string()
405 };
406
407 self.write_indent()?;
408 writeln!(
409 self.writer,
410 "rpc {}({}) returns ({});",
411 method.name(),
412 input,
413 output
414 )?;
415
416 Ok(())
417 }
418
419 fn write_message(
420 &mut self,
421 message: &prost_types::DescriptorProto,
422 syntax: ProtoSyntax,
423 ) -> std::fmt::Result {
424 writeln!(self.writer, "message {} {{", message.name())?;
425 self.indent();
426
427 self.write_reserved(message)?;
429
430 for nested in &message.nested_type {
432 if nested.options.as_ref().map_or(false, |o| o.map_entry.unwrap_or(false)) {
434 continue;
435 }
436 self.write_message(nested, syntax)?;
437 }
438
439 for enum_type in &message.enum_type {
441 self.write_enum(enum_type)?;
442 }
443
444 let mut oneof_fields: std::collections::HashMap<i32, Vec<&prost_types::FieldDescriptorProto>> =
446 std::collections::HashMap::new();
447
448 for field in &message.field {
449 if let Some(oneof_index) = field.oneof_index {
450 if !Self::is_proto3_optional(field, message) {
452 oneof_fields
453 .entry(oneof_index)
454 .or_default()
455 .push(field);
456 }
457 }
458 }
459
460 for (i, oneof) in message.oneof_decl.iter().enumerate() {
462 if let Some(fields) = oneof_fields.get(&(i as i32)) {
463 if !fields.is_empty() {
464 self.write_oneof(oneof, fields, syntax)?;
465 }
466 }
467 }
468
469 for field in &message.field {
471 let in_real_oneof = field.oneof_index.is_some()
472 && !Self::is_proto3_optional(field, message)
473 && oneof_fields.contains_key(&field.oneof_index.unwrap());
474
475 if !in_real_oneof {
476 self.write_field(field, syntax, message)?;
477 }
478 }
479
480 for extension in &message.extension {
482 self.write_extension(extension, syntax)?;
483 }
484
485 for range in &message.extension_range {
487 self.write_indent()?;
488 let end = if range.end() == MAX_FIELD_NUMBER as i32 + 1 {
489 "max".to_string()
490 } else {
491 (range.end() - 1).to_string()
492 };
493 writeln!(self.writer, "extensions {} to {};", range.start(), end)?;
494 }
495
496 self.dedent();
497 self.writeln("}")?;
498 writeln!(self.writer)?;
499
500 Ok(())
501 }
502
503 fn is_proto3_optional(
504 field: &prost_types::FieldDescriptorProto,
505 message: &prost_types::DescriptorProto,
506 ) -> bool {
507 if let Some(oneof_index) = field.oneof_index {
509 if let Some(oneof) = message.oneof_decl.get(oneof_index as usize) {
510 return oneof.name().starts_with('_');
512 }
513 }
514 false
515 }
516
517 fn write_reserved(&mut self, message: &prost_types::DescriptorProto) -> std::fmt::Result {
518 if !message.reserved_name.is_empty() {
520 self.write_indent()?;
521 write!(self.writer, "reserved ")?;
522 for (i, name) in message.reserved_name.iter().enumerate() {
523 if i > 0 {
524 write!(self.writer, ", ")?;
525 }
526 write!(self.writer, "\"{}\"", name)?;
527 }
528 writeln!(self.writer, ";")?;
529 }
530
531 if !message.reserved_range.is_empty() {
533 self.write_indent()?;
534 write!(self.writer, "reserved ")?;
535 for (i, range) in message.reserved_range.iter().enumerate() {
536 if i > 0 {
537 write!(self.writer, ", ")?;
538 }
539 if range.start() == range.end() - 1 {
540 write!(self.writer, "{}", range.start())?;
541 } else {
542 let end = if range.end() == MAX_FIELD_NUMBER as i32 + 1 {
543 "max".to_string()
544 } else {
545 (range.end() - 1).to_string()
546 };
547 write!(self.writer, "{} to {}", range.start(), end)?;
548 }
549 }
550 writeln!(self.writer, ";")?;
551 }
552
553 Ok(())
554 }
555
556 fn write_oneof(
557 &mut self,
558 oneof: &prost_types::OneofDescriptorProto,
559 fields: &[&prost_types::FieldDescriptorProto],
560 _syntax: ProtoSyntax,
561 ) -> std::fmt::Result {
562 self.write_indent()?;
563 writeln!(self.writer, "oneof {} {{", oneof.name())?;
564 self.indent();
565
566 for field in fields {
567 self.write_oneof_field(field)?;
568 }
569
570 self.dedent();
571 self.writeln("}")?;
572
573 Ok(())
574 }
575
576 fn write_oneof_field(&mut self, field: &prost_types::FieldDescriptorProto) -> std::fmt::Result {
577 self.write_indent()?;
578 writeln!(
579 self.writer,
580 "{} {} = {};",
581 self.field_type_name(field),
582 field.name(),
583 field.number()
584 )?;
585 Ok(())
586 }
587
588 fn write_field(
589 &mut self,
590 field: &prost_types::FieldDescriptorProto,
591 syntax: ProtoSyntax,
592 message: &prost_types::DescriptorProto,
593 ) -> std::fmt::Result {
594 self.write_indent()?;
595
596 let label = self.field_label(field, syntax, message);
598 if !label.is_empty() {
599 write!(self.writer, "{} ", label)?;
600 }
601
602 if self.is_map_field(field, message) {
604 self.write_map_field(field, message)?;
605 } else {
606 write!(
608 self.writer,
609 "{} {} = {}",
610 self.field_type_name(field),
611 field.name(),
612 field.number()
613 )?;
614
615 self.write_field_options(field, syntax)?;
617
618 writeln!(self.writer, ";")?;
619 }
620
621 Ok(())
622 }
623
624 fn is_map_field(
625 &self,
626 field: &prost_types::FieldDescriptorProto,
627 message: &prost_types::DescriptorProto,
628 ) -> bool {
629 if field.label() != prost_types::field_descriptor_proto::Label::Repeated {
630 return false;
631 }
632 if field.r#type() != prost_types::field_descriptor_proto::Type::Message {
633 return false;
634 }
635
636 let type_name = field.type_name();
638 for nested in &message.nested_type {
639 let expected_name = format!(".{}", nested.name());
640 if type_name.ends_with(&expected_name) || type_name == nested.name() {
641 return nested.options.as_ref().map_or(false, |o| o.map_entry.unwrap_or(false));
642 }
643 }
644
645 false
646 }
647
648 fn write_map_field(
649 &mut self,
650 field: &prost_types::FieldDescriptorProto,
651 message: &prost_types::DescriptorProto,
652 ) -> std::fmt::Result {
653 let type_name = field.type_name();
655 for nested in &message.nested_type {
656 let expected_name = format!(".{}", nested.name());
657 if type_name.ends_with(&expected_name) || type_name == nested.name() {
658 if nested.options.as_ref().map_or(false, |o| o.map_entry.unwrap_or(false)) {
659 let key_field = nested.field.iter().find(|f| f.number() == 1);
661 let value_field = nested.field.iter().find(|f| f.number() == 2);
662
663 if let (Some(key), Some(value)) = (key_field, value_field) {
664 writeln!(
665 self.writer,
666 "map<{}, {}> {} = {};",
667 self.field_type_name(key),
668 self.field_type_name(value),
669 field.name(),
670 field.number()
671 )?;
672 return Ok(());
673 }
674 }
675 }
676 }
677
678 writeln!(
680 self.writer,
681 "{} {} = {};",
682 self.field_type_name(field),
683 field.name(),
684 field.number()
685 )?;
686
687 Ok(())
688 }
689
690 fn field_label(
691 &self,
692 field: &prost_types::FieldDescriptorProto,
693 syntax: ProtoSyntax,
694 message: &prost_types::DescriptorProto,
695 ) -> &'static str {
696 use prost_types::field_descriptor_proto::Label;
697
698 match field.label() {
699 Label::Repeated => {
700 if self.is_map_field(field, message) {
702 ""
703 } else {
704 "repeated"
705 }
706 }
707 Label::Required => "required",
708 Label::Optional => {
709 match syntax {
710 ProtoSyntax::Proto2 => "optional",
711 ProtoSyntax::Proto3 => {
712 if Self::is_proto3_optional(field, message) {
714 "optional"
715 } else {
716 ""
717 }
718 }
719 }
720 }
721 }
722 }
723
724 fn field_type_name(&self, field: &prost_types::FieldDescriptorProto) -> String {
725 use prost_types::field_descriptor_proto::Type;
726
727 match field.r#type() {
728 Type::Double => "double".to_string(),
729 Type::Float => "float".to_string(),
730 Type::Int64 => "int64".to_string(),
731 Type::Uint64 => "uint64".to_string(),
732 Type::Int32 => "int32".to_string(),
733 Type::Fixed64 => "fixed64".to_string(),
734 Type::Fixed32 => "fixed32".to_string(),
735 Type::Bool => "bool".to_string(),
736 Type::String => "string".to_string(),
737 Type::Bytes => "bytes".to_string(),
738 Type::Uint32 => "uint32".to_string(),
739 Type::Sfixed32 => "sfixed32".to_string(),
740 Type::Sfixed64 => "sfixed64".to_string(),
741 Type::Sint32 => "sint32".to_string(),
742 Type::Sint64 => "sint64".to_string(),
743 Type::Group => "group".to_string(),
744 Type::Message | Type::Enum => {
745 field.type_name().to_string()
747 }
748 }
749 }
750
751 fn write_field_options(
752 &mut self,
753 field: &prost_types::FieldDescriptorProto,
754 syntax: ProtoSyntax,
755 ) -> std::fmt::Result {
756 let mut options = Vec::new();
757
758 if syntax == ProtoSyntax::Proto2 {
760 if let Some(default) = &field.default_value {
761 use prost_types::field_descriptor_proto::Type;
762 let formatted = match field.r#type() {
763 Type::String => format!("\"{}\"", escape_string(default)),
764 Type::Bytes => format!("\"{}\"", escape_string(default)),
765 Type::Enum => default.clone(),
766 Type::Bool => default.clone(),
767 _ => default.clone(),
768 };
769 options.push(format!("default = {}", formatted));
770 }
771 }
772
773 if let Some(json_name) = &field.json_name {
775 let default_json_name = to_lower_camel_case(field.name());
776 if json_name != &default_json_name {
777 options.push(format!("json_name = \"{}\"", json_name));
778 }
779 }
780
781 if let Some(opts) = &field.options {
783 if let Some(packed) = opts.packed {
784 options.push(format!("packed = {}", packed));
785 }
786 if let Some(deprecated) = opts.deprecated {
787 if deprecated {
788 options.push("deprecated = true".to_string());
789 }
790 }
791 }
792
793 if !options.is_empty() {
794 write!(self.writer, " [{}]", options.join(", "))?;
795 }
796
797 Ok(())
798 }
799
800 fn write_enum(&mut self, enum_type: &prost_types::EnumDescriptorProto) -> std::fmt::Result {
801 self.write_indent()?;
802 writeln!(self.writer, "enum {} {{", enum_type.name())?;
803 self.indent();
804
805 if let Some(opts) = &enum_type.options {
807 if opts.allow_alias.unwrap_or(false) {
808 self.writeln("option allow_alias = true;")?;
809 }
810 }
811
812 if !enum_type.reserved_range.is_empty() {
814 self.write_indent()?;
815 write!(self.writer, "reserved ")?;
816 for (i, range) in enum_type.reserved_range.iter().enumerate() {
817 if i > 0 {
818 write!(self.writer, ", ")?;
819 }
820 if range.start() == range.end() {
821 write!(self.writer, "{}", range.start())?;
822 } else {
823 let end = if range.end() == i32::MAX {
824 "max".to_string()
825 } else {
826 range.end().to_string()
827 };
828 write!(self.writer, "{} to {}", range.start(), end)?;
829 }
830 }
831 writeln!(self.writer, ";")?;
832 }
833
834 if !enum_type.reserved_name.is_empty() {
836 self.write_indent()?;
837 write!(self.writer, "reserved ")?;
838 for (i, name) in enum_type.reserved_name.iter().enumerate() {
839 if i > 0 {
840 write!(self.writer, ", ")?;
841 }
842 write!(self.writer, "\"{}\"", name)?;
843 }
844 writeln!(self.writer, ";")?;
845 }
846
847 for value in &enum_type.value {
849 self.write_indent()?;
850 write!(self.writer, "{} = {}", value.name(), value.number())?;
851
852 if let Some(opts) = &value.options {
854 if opts.deprecated.unwrap_or(false) {
855 write!(self.writer, " [deprecated = true]")?;
856 }
857 }
858
859 writeln!(self.writer, ";")?;
860 }
861
862 self.dedent();
863 self.writeln("}")?;
864 writeln!(self.writer)?;
865
866 Ok(())
867 }
868
869 fn write_extension(
870 &mut self,
871 extension: &prost_types::FieldDescriptorProto,
872 syntax: ProtoSyntax,
873 ) -> std::fmt::Result {
874 self.write_indent()?;
875 writeln!(self.writer, "extend {} {{", extension.extendee())?;
876 self.indent();
877
878 self.write_indent()?;
879
880 use prost_types::field_descriptor_proto::Label;
882 match extension.label() {
883 Label::Repeated => write!(self.writer, "repeated ")?,
884 Label::Required => write!(self.writer, "required ")?,
885 Label::Optional => {
886 if syntax == ProtoSyntax::Proto2 {
887 write!(self.writer, "optional ")?;
888 }
889 }
890 }
891
892 writeln!(
893 self.writer,
894 "{} {} = {};",
895 self.field_type_name(extension),
896 extension.name(),
897 extension.number()
898 )?;
899
900 self.dedent();
901 self.writeln("}")?;
902 writeln!(self.writer)?;
903
904 Ok(())
905 }
906}
907
908fn escape_string(s: &str) -> String {
910 let mut result = String::with_capacity(s.len());
911 for c in s.chars() {
912 match c {
913 '\\' => result.push_str("\\\\"),
914 '"' => result.push_str("\\\""),
915 '\n' => result.push_str("\\n"),
916 '\r' => result.push_str("\\r"),
917 '\t' => result.push_str("\\t"),
918 _ if c.is_ascii_control() => {
919 result.push_str(&format!("\\x{:02x}", c as u8));
920 }
921 _ => result.push(c),
922 }
923 }
924 result
925}
926
927fn to_lower_camel_case(s: &str) -> String {
929 let mut result = String::with_capacity(s.len());
930 let mut capitalize_next = false;
931
932 for c in s.chars() {
933 if c == '_' {
934 capitalize_next = true;
935 } else if capitalize_next {
936 result.push(c.to_ascii_uppercase());
937 capitalize_next = false;
938 } else {
939 result.push(c);
940 }
941 }
942
943 result
944}
945
946#[cfg(test)]
947mod tests {
948 use super::*;
949
950 #[test]
951 fn test_escape_string() {
952 assert_eq!(escape_string("hello"), "hello");
953 assert_eq!(escape_string("hello\\world"), "hello\\\\world");
954 assert_eq!(escape_string("hello\"world"), "hello\\\"world");
955 assert_eq!(escape_string("hello\nworld"), "hello\\nworld");
956 }
957
958 #[test]
959 fn test_to_lower_camel_case() {
960 assert_eq!(to_lower_camel_case("hello_world"), "helloWorld");
961 assert_eq!(to_lower_camel_case("my_field_name"), "myFieldName");
962 assert_eq!(to_lower_camel_case("simple"), "simple");
963 }
964
965 #[test]
966 fn test_proto_syntax() {
967 assert_eq!(ProtoSyntax::try_from("").unwrap(), ProtoSyntax::Proto2);
968 assert_eq!(ProtoSyntax::try_from("proto2").unwrap(), ProtoSyntax::Proto2);
969 assert_eq!(ProtoSyntax::try_from("proto3").unwrap(), ProtoSyntax::Proto3);
970 assert!(ProtoSyntax::try_from("proto4").is_err());
971 }
972}