Skip to main content

oxiproto_reflect/native/
text.rs

1//! Protobuf text format encoding/decoding for native [`DynamicMessage`].
2//!
3//! The text format is a human-readable serialisation of protobuf messages:
4//!
5//! ```text
6//! field_name: value
7//! nested_field {
8//!   sub_field: 42
9//! }
10//! repeated_field: 1
11//! repeated_field: 2
12//! ```
13//!
14//! Rules implemented:
15//! - Singular scalar fields: `name: value`
16//! - String fields: `name: "escaped_string"`
17//! - Bytes fields: `name: "<base64_or_escaped>"` — we use hex-escaped bytes
18//!   (`\xNN`) for binary content.
19//! - Enum fields: `name: VALUE_NAME` (or integer for unknown values).
20//! - Nested messages: `name { ... }` with 2-space indentation.
21//! - Repeated fields: one entry per value, same name repeated.
22//! - Map fields: expanded as repeated synthetic entries `name { key: K value: V }`.
23//! - Proto3 default-valued singular fields are omitted on output.
24//! - `float`/`double` NaN → `nan`, Inf → `inf`, -Inf → `-inf`.
25//! - 64-bit integer fields use the integer literal (no quotes).
26//!
27//! Parsing supports:
28//! - `name: value` and `name { ... }` (brace-delimited message) syntaxes.
29//! - Quoted string values (double-quotes, with `\n\r\t\\\"` escapes).
30//! - Integer, float, boolean (`true`/`false`), `nan`/`inf`/`-inf` literals.
31//! - Unquoted identifiers for enum names.
32//! - Comment lines starting with `#` are skipped.
33//! - Unknown field names are silently skipped.
34
35use std::sync::Arc;
36
37use super::descriptor::{Cardinality, FieldDescriptor, Kind, MessageDescriptor};
38use super::dynamic::{is_field_value_default, DynamicMessage};
39use super::value::{MapKey, Value};
40
41// ---------------------------------------------------------------------------
42// Public error type
43// ---------------------------------------------------------------------------
44
45/// Errors produced during protobuf text-format conversion.
46#[derive(Debug)]
47pub enum TextError {
48    /// Malformed text input.
49    Parse(String),
50    /// Schema mismatch between the text and the message descriptor.
51    Schema(String),
52}
53
54impl std::fmt::Display for TextError {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        match self {
57            TextError::Parse(s) => write!(f, "text format parse error: {s}"),
58            TextError::Schema(s) => write!(f, "schema error: {s}"),
59        }
60    }
61}
62
63impl std::error::Error for TextError {}
64
65impl From<TextError> for crate::ReflectError {
66    fn from(e: TextError) -> Self {
67        crate::ReflectError::Field(e.to_string())
68    }
69}
70
71// ---------------------------------------------------------------------------
72// Public API on DynamicMessage
73// ---------------------------------------------------------------------------
74
75impl DynamicMessage {
76    /// Encode this message to the protobuf text format.
77    ///
78    /// Proto3 default-valued singular fields are omitted.
79    ///
80    /// # Errors
81    ///
82    /// Returns [`TextError`] if encoding fails (e.g. an unsupported group
83    /// field is encountered).
84    pub fn to_text(&self) -> Result<String, TextError> {
85        let mut out = String::new();
86        encode_message(self, &mut out, 0)?;
87        Ok(out)
88    }
89
90    /// Decode a protobuf text-format string into a new [`DynamicMessage`] of
91    /// the given descriptor.
92    ///
93    /// Unknown fields are silently skipped. Comments (lines starting with `#`)
94    /// are ignored.
95    ///
96    /// # Errors
97    ///
98    /// Returns [`TextError`] if the input cannot be parsed or does not match
99    /// the schema.
100    pub fn from_text(desc: MessageDescriptor, text: &str) -> Result<Self, TextError> {
101        let mut parser = Parser::new(text);
102        parser.parse_message(desc)
103    }
104}
105
106// ---------------------------------------------------------------------------
107// Encoding
108// ---------------------------------------------------------------------------
109
110const INDENT: &str = "  ";
111
112fn encode_message(msg: &DynamicMessage, out: &mut String, depth: usize) -> Result<(), TextError> {
113    let desc = msg.descriptor();
114    let prefix = INDENT.repeat(depth);
115
116    for field in desc.fields() {
117        let value = msg.get_field(&field);
118        if is_field_value_default(&field, &value) {
119            continue;
120        }
121        encode_field_value(&field, &value, &prefix, out, depth)?;
122    }
123    Ok(())
124}
125
126fn encode_field_value(
127    field: &FieldDescriptor,
128    value: &Value,
129    prefix: &str,
130    out: &mut String,
131    depth: usize,
132) -> Result<(), TextError> {
133    if field.is_map() {
134        return encode_map_field(field, value, prefix, out, depth);
135    }
136    if matches!(field.cardinality(), Cardinality::Repeated) {
137        return encode_repeated_field(field, value, prefix, out, depth);
138    }
139    encode_singular_field(field, value, prefix, out, depth)
140}
141
142fn encode_repeated_field(
143    field: &FieldDescriptor,
144    value: &Value,
145    prefix: &str,
146    out: &mut String,
147    depth: usize,
148) -> Result<(), TextError> {
149    if let Value::List(items) = value {
150        for item in items {
151            encode_singular_field(field, item, prefix, out, depth)?;
152        }
153        Ok(())
154    } else {
155        Err(TextError::Schema(format!(
156            "expected list for repeated field '{}'",
157            field.name()
158        )))
159    }
160}
161
162fn encode_map_field(
163    field: &FieldDescriptor,
164    value: &Value,
165    prefix: &str,
166    out: &mut String,
167    depth: usize,
168) -> Result<(), TextError> {
169    let val_field = field.map_entry_value_field().ok_or_else(|| {
170        TextError::Schema(format!(
171            "map field '{}' missing value field descriptor",
172            field.name()
173        ))
174    })?;
175    let key_field = field.map_entry_key_field().ok_or_else(|| {
176        TextError::Schema(format!(
177            "map field '{}' missing key field descriptor",
178            field.name()
179        ))
180    })?;
181
182    if let Value::Map(entries) = value {
183        let mut sorted: Vec<_> = entries.iter().collect();
184        sorted.sort_by_key(|(k, _)| map_key_sort_key(k));
185        for (k, v) in sorted {
186            // Each entry is a sub-message: `field_name { key: K  value: V }`
187            out.push_str(prefix);
188            out.push_str(field.name());
189            out.push_str(" {\n");
190            let inner_prefix = format!("{prefix}{INDENT}");
191            // key
192            encode_singular_field(&key_field, &k.to_value(), &inner_prefix, out, depth + 1)?;
193            // value
194            encode_singular_field(&val_field, v, &inner_prefix, out, depth + 1)?;
195            out.push_str(prefix);
196            out.push_str("}\n");
197        }
198        Ok(())
199    } else {
200        Err(TextError::Schema(format!(
201            "expected map for map field '{}'",
202            field.name()
203        )))
204    }
205}
206
207fn encode_singular_field(
208    field: &FieldDescriptor,
209    value: &Value,
210    prefix: &str,
211    out: &mut String,
212    depth: usize,
213) -> Result<(), TextError> {
214    match value {
215        Value::Message(m) => {
216            out.push_str(prefix);
217            out.push_str(field.name());
218            out.push_str(" {\n");
219            encode_message(m, out, depth + 1)?;
220            out.push_str(prefix);
221            out.push_str("}\n");
222        }
223        other => {
224            out.push_str(prefix);
225            out.push_str(field.name());
226            out.push_str(": ");
227            encode_scalar_value(other, field, out)?;
228            out.push('\n');
229        }
230    }
231    Ok(())
232}
233
234fn encode_scalar_value(
235    value: &Value,
236    field: &FieldDescriptor,
237    out: &mut String,
238) -> Result<(), TextError> {
239    match value {
240        Value::F64(v) => {
241            if v.is_nan() {
242                out.push_str("nan");
243            } else if *v == f64::INFINITY {
244                out.push_str("inf");
245            } else if *v == f64::NEG_INFINITY {
246                out.push_str("-inf");
247            } else {
248                out.push_str(&format!("{v}"));
249            }
250        }
251        Value::F32(v) => {
252            let v64 = f64::from(*v);
253            if v64.is_nan() {
254                out.push_str("nan");
255            } else if v64 == f64::INFINITY {
256                out.push_str("inf");
257            } else if v64 == f64::NEG_INFINITY {
258                out.push_str("-inf");
259            } else {
260                out.push_str(&format!("{v}"));
261            }
262        }
263        Value::I32(v) => out.push_str(&v.to_string()),
264        Value::I64(v) => out.push_str(&v.to_string()),
265        Value::U32(v) => out.push_str(&v.to_string()),
266        Value::U64(v) => out.push_str(&v.to_string()),
267        Value::Bool(v) => out.push_str(if *v { "true" } else { "false" }),
268        Value::String(s) => {
269            out.push('"');
270            for c in s.chars() {
271                match c {
272                    '"' => out.push_str("\\\""),
273                    '\\' => out.push_str("\\\\"),
274                    '\n' => out.push_str("\\n"),
275                    '\r' => out.push_str("\\r"),
276                    '\t' => out.push_str("\\t"),
277                    '\0' => out.push_str("\\0"),
278                    other => out.push(other),
279                }
280            }
281            out.push('"');
282        }
283        Value::Bytes(b) => {
284            out.push('"');
285            for byte in b {
286                out.push_str(&format!("\\x{byte:02x}"));
287            }
288            out.push('"');
289        }
290        Value::EnumNumber(n) => {
291            // Prefer the enum value name for readability.
292            if let Some(enum_desc) = field.enum_type() {
293                if let Some(val_desc) = enum_desc.get_value(*n) {
294                    out.push_str(val_desc.name());
295                    return Ok(());
296                }
297            }
298            out.push_str(&n.to_string());
299        }
300        Value::Message(_) | Value::List(_) | Value::Map(_) => {
301            return Err(TextError::Schema(
302                "unexpected nested structure in scalar context".to_owned(),
303            ));
304        }
305    }
306    Ok(())
307}
308
309fn map_key_sort_key(k: &MapKey) -> String {
310    match k {
311        MapKey::String(s) => format!("s{s}"),
312        MapKey::I32(v) => format!("n{:020}", v),
313        MapKey::I64(v) => format!("n{:020}", v),
314        MapKey::U32(v) => format!("n{:020}", v),
315        MapKey::U64(v) => format!("n{:020}", v),
316        MapKey::Bool(v) => format!("b{}", if *v { 1 } else { 0 }),
317    }
318}
319
320// ---------------------------------------------------------------------------
321// Parsing
322// ---------------------------------------------------------------------------
323
324struct Parser<'a> {
325    input: &'a str,
326    pos: usize,
327}
328
329impl<'a> Parser<'a> {
330    fn new(input: &'a str) -> Self {
331        Self { input, pos: 0 }
332    }
333
334    fn is_empty(&self) -> bool {
335        self.pos >= self.input.len()
336    }
337
338    /// Skip whitespace (spaces, tabs, newlines) and `#` comment lines.
339    fn skip_ws(&mut self) {
340        loop {
341            while self.pos < self.input.len()
342                && matches!(
343                    self.input.as_bytes()[self.pos],
344                    b' ' | b'\t' | b'\n' | b'\r'
345                )
346            {
347                self.pos += 1;
348            }
349            // Skip comment lines.
350            if self.pos < self.input.len() && self.input.as_bytes()[self.pos] == b'#' {
351                while self.pos < self.input.len() && self.input.as_bytes()[self.pos] != b'\n' {
352                    self.pos += 1;
353                }
354            } else {
355                break;
356            }
357        }
358    }
359
360    /// Peek at the next non-whitespace byte.
361    fn peek(&mut self) -> Option<u8> {
362        self.skip_ws();
363        self.input.as_bytes().get(self.pos).copied()
364    }
365
366    /// Expect a specific character; error if not found.
367    fn expect(&mut self, ch: char) -> Result<(), TextError> {
368        self.skip_ws();
369        let bytes = self.input.as_bytes();
370        if self.pos < bytes.len() && bytes[self.pos] == ch as u8 {
371            self.pos += 1;
372            Ok(())
373        } else {
374            let got = if self.pos < bytes.len() {
375                format!("'{}'", bytes[self.pos] as char)
376            } else {
377                "EOF".to_owned()
378            };
379            Err(TextError::Parse(format!("expected '{ch}', got {got}")))
380        }
381    }
382
383    /// Read a bare token (identifier, number, or keyword) up to whitespace,
384    /// `:`, `{`, `}`, `;`.
385    fn read_token(&mut self) -> Result<String, TextError> {
386        self.skip_ws();
387        let start = self.pos;
388        while self.pos < self.input.len() {
389            match self.input.as_bytes()[self.pos] {
390                b' ' | b'\t' | b'\n' | b'\r' | b':' | b'{' | b'}' | b';' | b',' => break,
391                _ => self.pos += 1,
392            }
393        }
394        if self.pos == start {
395            return Err(TextError::Parse("expected token, got empty".to_owned()));
396        }
397        Ok(self.input[start..self.pos].to_owned())
398    }
399
400    /// Read a double-quoted string value, handling common escapes.
401    /// Returns the decoded string as a `String` for string fields.
402    fn read_string(&mut self) -> Result<String, TextError> {
403        let bytes = self.read_string_as_bytes()?;
404        String::from_utf8(bytes)
405            .map_err(|e| TextError::Parse(format!("string field contains invalid UTF-8: {e}")))
406    }
407
408    /// Read a double-quoted value, returning the raw decoded bytes.
409    /// This handles `\xNN` escapes as actual byte values (not Unicode code
410    /// points), which is the protobuf text format rule for `bytes` fields.
411    fn read_string_as_bytes(&mut self) -> Result<Vec<u8>, TextError> {
412        self.expect('"')?;
413        let mut out: Vec<u8> = Vec::new();
414        loop {
415            if self.pos >= self.input.len() {
416                return Err(TextError::Parse("unterminated string literal".to_owned()));
417            }
418            let b = self.input.as_bytes()[self.pos];
419            self.pos += 1;
420            match b {
421                b'"' => break,
422                b'\\' => {
423                    if self.pos >= self.input.len() {
424                        return Err(TextError::Parse("unterminated escape".to_owned()));
425                    }
426                    let esc = self.input.as_bytes()[self.pos];
427                    self.pos += 1;
428                    match esc {
429                        b'n' => out.push(b'\n'),
430                        b'r' => out.push(b'\r'),
431                        b't' => out.push(b'\t'),
432                        b'\\' => out.push(b'\\'),
433                        b'"' => out.push(b'"'),
434                        b'0' => out.push(0),
435                        b'x' | b'X' => {
436                            // Hex escape \xNN: a raw byte value (not a char).
437                            let h1 = self.read_hex_digit()?;
438                            let h2 = self.read_hex_digit()?;
439                            out.push(h1 * 16 + h2);
440                        }
441                        other => {
442                            out.push(b'\\');
443                            out.push(other);
444                        }
445                    }
446                }
447                other => out.push(other),
448            }
449        }
450        Ok(out)
451    }
452
453    fn read_hex_digit(&mut self) -> Result<u8, TextError> {
454        if self.pos >= self.input.len() {
455            return Err(TextError::Parse("unexpected EOF in hex escape".to_owned()));
456        }
457        let b = self.input.as_bytes()[self.pos];
458        self.pos += 1;
459        match b {
460            b'0'..=b'9' => Ok(b - b'0'),
461            b'a'..=b'f' => Ok(b - b'a' + 10),
462            b'A'..=b'F' => Ok(b - b'A' + 10),
463            other => Err(TextError::Parse(format!(
464                "invalid hex digit: '{}'",
465                other as char
466            ))),
467        }
468    }
469
470    fn parse_message(&mut self, desc: MessageDescriptor) -> Result<DynamicMessage, TextError> {
471        let mut msg = DynamicMessage::new(desc.clone());
472        loop {
473            self.skip_ws();
474            if self.is_empty() {
475                break;
476            }
477            // Check for end-of-block (closing brace).
478            if self.peek() == Some(b'}') {
479                break;
480            }
481
482            // Read field name.
483            let field_name = self.read_token()?;
484            self.skip_ws();
485
486            // Look up field by name or json_name.
487            let field = desc
488                .get_field_by_name(&field_name)
489                .or_else(|| desc.get_field_by_json_name(&field_name));
490
491            let field = match field {
492                Some(f) => f,
493                None => {
494                    // Unknown field — skip its value.
495                    self.skip_field_value()?;
496                    // Optional trailing semicolons.
497                    if self.peek() == Some(b';') {
498                        self.pos += 1;
499                    }
500                    continue;
501                }
502            };
503
504            // Determine delimiter: `:` for scalars, `{` for messages or map entries.
505            let value =
506                if matches!(field.kind(), Kind::Message(_) | Kind::Group(_)) || field.is_map() {
507                    // May have optional `:` or `<` before `{`.
508                    self.skip_ws();
509                    if self.peek() == Some(b':') {
510                        self.pos += 1; // consume ':'
511                        self.skip_ws();
512                    }
513                    if self.peek() == Some(b'<') {
514                        // Angle-bracket message syntax (alternative to braces).
515                        self.parse_angle_message(field.clone())?
516                    } else {
517                        self.parse_brace_message(field.clone())?
518                    }
519                } else {
520                    self.expect(':')?;
521                    self.parse_scalar_value(&field)?
522                };
523
524            // For repeated fields, append to the list; for singular, set directly.
525            if matches!(field.cardinality(), Cardinality::Repeated) && !field.is_map() {
526                // Append to the existing list.
527                let existing = msg
528                    .fields
529                    .entry(field.number())
530                    .or_insert(Value::List(Vec::new()));
531                if let Value::List(list) = existing {
532                    list.push(value);
533                }
534            } else if field.is_map() {
535                // Map entry comes in as a Value::Map directly from parse_brace_message.
536                // Merge into the existing map.
537                match value {
538                    Value::Map(new_entries) => {
539                        let existing = msg
540                            .fields
541                            .entry(field.number())
542                            .or_insert(Value::Map(std::collections::HashMap::new()));
543                        if let Value::Map(map) = existing {
544                            map.extend(new_entries);
545                        }
546                    }
547                    other => {
548                        return Err(TextError::Schema(format!(
549                            "expected map for map field '{}', got {:?}",
550                            field.name(),
551                            other
552                        )));
553                    }
554                }
555            } else {
556                msg.set_field(&field, value);
557            }
558
559            // Optional trailing semicolons or commas.
560            self.skip_ws();
561            if matches!(self.peek(), Some(b';') | Some(b',')) {
562                self.pos += 1;
563            }
564        }
565        Ok(msg)
566    }
567
568    /// Parse a brace-delimited sub-message: `{ ... }`.
569    fn parse_brace_message(&mut self, field: FieldDescriptor) -> Result<Value, TextError> {
570        if field.is_map() {
571            return self.parse_map_entry(field);
572        }
573        if let Kind::Message(msg_index) = field.kind() {
574            self.expect('{')?;
575            let msg_desc = MessageDescriptor {
576                pool: Arc::clone(&field.pool),
577                index: msg_index,
578            };
579            let nested = self.parse_message(msg_desc)?;
580            self.expect('}')?;
581            Ok(Value::Message(Box::new(nested)))
582        } else {
583            Err(TextError::Schema(format!(
584                "field '{}' is not a message kind",
585                field.name()
586            )))
587        }
588    }
589
590    /// Parse an angle-bracket sub-message: `< ... >`.
591    fn parse_angle_message(&mut self, field: FieldDescriptor) -> Result<Value, TextError> {
592        if let Kind::Message(msg_index) = field.kind() {
593            self.expect('<')?;
594            let msg_desc = MessageDescriptor {
595                pool: Arc::clone(&field.pool),
596                index: msg_index,
597            };
598            let nested = self.parse_message(msg_desc)?;
599            self.expect('>')?;
600            Ok(Value::Message(Box::new(nested)))
601        } else {
602            Err(TextError::Schema(format!(
603                "field '{}' is not a message kind",
604                field.name()
605            )))
606        }
607    }
608
609    /// Parse a map entry `{ key: K  value: V }` and return a single-entry
610    /// `Value::Map`.
611    fn parse_map_entry(&mut self, field: FieldDescriptor) -> Result<Value, TextError> {
612        let key_field = field.map_entry_key_field().ok_or_else(|| {
613            TextError::Schema(format!(
614                "map field '{}' missing key descriptor",
615                field.name()
616            ))
617        })?;
618        let val_field = field.map_entry_value_field().ok_or_else(|| {
619            TextError::Schema(format!(
620                "map field '{}' missing value descriptor",
621                field.name()
622            ))
623        })?;
624
625        self.expect('{')?;
626
627        let mut key_val: Option<Value> = None;
628        let mut entry_val: Option<Value> = None;
629
630        loop {
631            self.skip_ws();
632            if self.peek() == Some(b'}') {
633                break;
634            }
635            let fname = self.read_token()?;
636            match fname.as_str() {
637                "key" => {
638                    self.expect(':')?;
639                    key_val = Some(self.parse_scalar_value(&key_field)?);
640                }
641                "value" => {
642                    let v = if matches!(val_field.kind(), Kind::Message(_)) {
643                        self.skip_ws();
644                        if self.peek() == Some(b':') {
645                            self.pos += 1;
646                            self.skip_ws();
647                        }
648                        self.parse_brace_message(val_field.clone())?
649                    } else {
650                        self.expect(':')?;
651                        self.parse_scalar_value(&val_field)?
652                    };
653                    entry_val = Some(v);
654                }
655                _ => {
656                    self.skip_field_value()?;
657                }
658            }
659            self.skip_ws();
660            if matches!(self.peek(), Some(b';') | Some(b',')) {
661                self.pos += 1;
662            }
663        }
664
665        self.expect('}')?;
666
667        let key = key_val.ok_or_else(|| {
668            TextError::Parse(format!("map entry for '{}' missing 'key'", field.name()))
669        })?;
670        let val = entry_val.ok_or_else(|| {
671            TextError::Parse(format!("map entry for '{}' missing 'value'", field.name()))
672        })?;
673
674        let map_key = value_to_map_key(key)?;
675        let mut map = std::collections::HashMap::new();
676        map.insert(map_key, val);
677        Ok(Value::Map(map))
678    }
679
680    /// Parse a scalar value for the given field.
681    fn parse_scalar_value(&mut self, field: &FieldDescriptor) -> Result<Value, TextError> {
682        self.skip_ws();
683        let next = self
684            .peek()
685            .ok_or_else(|| TextError::Parse("unexpected EOF".to_owned()))?;
686
687        if next == b'"' {
688            // Quoted string — use raw bytes for bytes fields to preserve the
689            // exact byte values of \xNN escapes (not re-encoded as UTF-8).
690            return match field.kind() {
691                Kind::Bytes => {
692                    let raw = self.read_string_as_bytes()?;
693                    Ok(Value::Bytes(raw))
694                }
695                _ => {
696                    let s = self.read_string()?;
697                    parse_string_for_kind(s, field)
698                }
699            };
700        }
701
702        // Read bare token.
703        let token = self.read_token()?;
704        parse_token_for_field(&token, field)
705    }
706
707    /// Skip an unknown field value (either a quoted string, a bare token, or
708    /// a brace-delimited block).
709    fn skip_field_value(&mut self) -> Result<(), TextError> {
710        self.skip_ws();
711        let next = self.peek();
712        match next {
713            Some(b':') => {
714                self.pos += 1;
715                self.skip_ws();
716                if self.peek() == Some(b'"') {
717                    let _ = self.read_string()?;
718                } else if self.peek() == Some(b'{') {
719                    self.skip_block()?;
720                } else {
721                    let _ = self.read_token()?;
722                }
723            }
724            Some(b'{') => {
725                self.skip_block()?;
726            }
727            _ => {
728                let _ = self.read_token()?;
729            }
730        }
731        Ok(())
732    }
733
734    /// Skip a `{ ... }` block, handling nested braces.
735    fn skip_block(&mut self) -> Result<(), TextError> {
736        self.expect('{')?;
737        let mut depth = 1usize;
738        while self.pos < self.input.len() && depth > 0 {
739            match self.input.as_bytes()[self.pos] {
740                b'{' => {
741                    depth += 1;
742                    self.pos += 1;
743                }
744                b'}' => {
745                    depth -= 1;
746                    self.pos += 1;
747                }
748                b'"' => {
749                    // Skip string literals to avoid confusing braces inside them.
750                    self.pos += 1;
751                    while self.pos < self.input.len() {
752                        match self.input.as_bytes()[self.pos] {
753                            b'"' => {
754                                self.pos += 1;
755                                break;
756                            }
757                            b'\\' => {
758                                self.pos += 2; // skip escaped char
759                            }
760                            _ => {
761                                self.pos += 1;
762                            }
763                        }
764                    }
765                }
766                _ => {
767                    self.pos += 1;
768                }
769            }
770        }
771        if depth != 0 {
772            return Err(TextError::Parse("unmatched '{' in text format".to_owned()));
773        }
774        Ok(())
775    }
776}
777
778// ---------------------------------------------------------------------------
779// Token-to-value helpers
780// ---------------------------------------------------------------------------
781
782fn parse_string_for_kind(s: String, field: &FieldDescriptor) -> Result<Value, TextError> {
783    match field.kind() {
784        Kind::String => Ok(Value::String(s)),
785        Kind::Bytes => {
786            // Raw bytes in the string (escape sequences already decoded by read_string).
787            Ok(Value::Bytes(s.into_bytes()))
788        }
789        other => Err(TextError::Schema(format!(
790            "field '{}' has kind {:?} but got a quoted string",
791            field.name(),
792            other
793        ))),
794    }
795}
796
797fn parse_token_for_field(token: &str, field: &FieldDescriptor) -> Result<Value, TextError> {
798    match field.kind() {
799        Kind::Bool => parse_bool(token),
800        Kind::Double => parse_f64(token).map(Value::F64),
801        Kind::Float => parse_f32(token).map(Value::F32),
802        Kind::Int32 | Kind::Sint32 | Kind::Sfixed32 => token
803            .parse::<i32>()
804            .map(Value::I32)
805            .map_err(|_| TextError::Parse(format!("invalid i32: {token}"))),
806        Kind::Int64 | Kind::Sint64 | Kind::Sfixed64 => token
807            .parse::<i64>()
808            .map(Value::I64)
809            .map_err(|_| TextError::Parse(format!("invalid i64: {token}"))),
810        Kind::Uint32 | Kind::Fixed32 => token
811            .parse::<u32>()
812            .map(Value::U32)
813            .map_err(|_| TextError::Parse(format!("invalid u32: {token}"))),
814        Kind::Uint64 | Kind::Fixed64 => token
815            .parse::<u64>()
816            .map(Value::U64)
817            .map_err(|_| TextError::Parse(format!("invalid u64: {token}"))),
818        Kind::String => Ok(Value::String(token.to_owned())),
819        Kind::Bytes => Ok(Value::Bytes(token.as_bytes().to_vec())),
820        Kind::Enum(_) => {
821            // Try by-name first; fall back to integer.
822            if let Some(enum_desc) = field.enum_type() {
823                if let Some(val_desc) = enum_desc.get_value_by_name(token) {
824                    return Ok(Value::EnumNumber(val_desc.number()));
825                }
826            }
827            // Try as an integer.
828            token.parse::<i32>().map(Value::EnumNumber).map_err(|_| {
829                TextError::Parse(format!(
830                    "unknown enum value for '{}': {token}",
831                    field.name()
832                ))
833            })
834        }
835        Kind::Message(_) | Kind::Group(_) => Err(TextError::Parse(format!(
836            "field '{}' is a message kind; expected '{{' delimiter, got bare token '{token}'",
837            field.name()
838        ))),
839    }
840}
841
842fn parse_bool(token: &str) -> Result<Value, TextError> {
843    match token {
844        "true" | "True" | "1" => Ok(Value::Bool(true)),
845        "false" | "False" | "0" => Ok(Value::Bool(false)),
846        other => Err(TextError::Parse(format!("invalid bool: {other}"))),
847    }
848}
849
850fn parse_f64(token: &str) -> Result<f64, TextError> {
851    match token {
852        "nan" | "NaN" => Ok(f64::NAN),
853        "inf" | "Inf" | "infinity" | "Infinity" => Ok(f64::INFINITY),
854        "-inf" | "-Inf" | "-infinity" | "-Infinity" => Ok(f64::NEG_INFINITY),
855        other => other
856            .parse::<f64>()
857            .map_err(|_| TextError::Parse(format!("invalid f64: {other}"))),
858    }
859}
860
861fn parse_f32(token: &str) -> Result<f32, TextError> {
862    match token {
863        "nan" | "NaN" => Ok(f32::NAN),
864        "inf" | "Inf" | "infinity" | "Infinity" => Ok(f32::INFINITY),
865        "-inf" | "-Inf" | "-infinity" | "-Infinity" => Ok(f32::NEG_INFINITY),
866        other => other
867            .parse::<f32>()
868            .map_err(|_| TextError::Parse(format!("invalid f32: {other}"))),
869    }
870}
871
872fn value_to_map_key(v: Value) -> Result<MapKey, TextError> {
873    match v {
874        Value::String(s) => Ok(MapKey::String(s)),
875        Value::I32(n) => Ok(MapKey::I32(n)),
876        Value::I64(n) => Ok(MapKey::I64(n)),
877        Value::U32(n) => Ok(MapKey::U32(n)),
878        Value::U64(n) => Ok(MapKey::U64(n)),
879        Value::Bool(b) => Ok(MapKey::Bool(b)),
880        other => Err(TextError::Schema(format!(
881            "invalid map key type: {other:?}"
882        ))),
883    }
884}