Skip to main content

tealeaf/
types.rs

1//! Core types for TeaLeaf
2
3use std::collections::HashMap;
4use std::fmt;
5use std::io;
6use indexmap::IndexMap;
7
8/// Ordered map type for object fields — preserves insertion order.
9pub type ObjectMap<K, V> = IndexMap<K, V>;
10
11// =============================================================================
12// Constants
13// =============================================================================
14
15pub const MAGIC: [u8; 4] = *b"TLBX";
16/// Binary format version (major) - for compatibility checks
17pub const VERSION_MAJOR: u16 = 2;
18/// Binary format version (minor) - for compatibility checks
19pub const VERSION_MINOR: u16 = 0;
20/// Library version string (beta/RFC stage)
21pub const VERSION: &str = "2.0.0-beta.4";
22pub const HEADER_SIZE: usize = 64;
23/// Maximum length of a string in the string table (u32 encoding limit)
24pub const MAX_STRING_LENGTH: usize = u32::MAX as usize;
25/// Maximum number of fields in an object/struct (u16 encoding limit)
26pub const MAX_OBJECT_FIELDS: usize = u16::MAX as usize;
27/// Maximum number of elements in an array (u32 encoding limit)
28pub const MAX_ARRAY_LENGTH: usize = u32::MAX as usize;
29
30// =============================================================================
31// Error Type
32// =============================================================================
33
34#[derive(Debug)]
35pub enum Error {
36    Io(io::Error),
37    InvalidMagic,
38    InvalidVersion { major: u16, minor: u16 },
39    InvalidType(u8),
40    InvalidUtf8,
41    UnexpectedToken { expected: String, got: String },
42    UnexpectedEof,
43    UnknownStruct(String),
44    MissingField(String),
45    ParseError(String),
46    ValueOutOfRange(String),
47}
48
49impl fmt::Display for Error {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        match self {
52            Error::Io(e) => write!(f, "IO error: {}", e),
53            Error::InvalidMagic => write!(f, "Invalid TeaLeaf magic bytes"),
54            Error::InvalidVersion { major, minor } => {
55                write!(f, "Unsupported version: {}.{}", major, minor)
56            }
57            Error::InvalidType(t) => write!(f, "Invalid type code: 0x{:02X}", t),
58            Error::InvalidUtf8 => write!(f, "Invalid UTF-8"),
59            Error::UnexpectedToken { expected, got } => {
60                write!(f, "Expected {}, got {}", expected, got)
61            }
62            Error::UnexpectedEof => write!(f, "Unexpected end of input"),
63            Error::UnknownStruct(s) => write!(f, "Unknown struct: {}", s),
64            Error::MissingField(s) => write!(f, "Missing field: {}", s),
65            Error::ParseError(s) => write!(f, "Parse error: {}", s),
66            Error::ValueOutOfRange(s) => write!(f, "Value out of range: {}", s),
67        }
68    }
69}
70
71impl std::error::Error for Error {}
72
73impl From<io::Error> for Error {
74    fn from(e: io::Error) -> Self {
75        Error::Io(e)
76    }
77}
78
79pub type Result<T> = std::result::Result<T, Error>;
80
81// =============================================================================
82// Type Codes
83// =============================================================================
84
85#[repr(u8)]
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
87pub enum TLType {
88    Null = 0x00,
89    Bool = 0x01,
90    Int8 = 0x02,
91    Int16 = 0x03,
92    Int32 = 0x04,
93    Int64 = 0x05,
94    UInt8 = 0x06,
95    UInt16 = 0x07,
96    UInt32 = 0x08,
97    UInt64 = 0x09,
98    Float32 = 0x0A,
99    Float64 = 0x0B,
100    String = 0x10,
101    Bytes = 0x11,
102    JsonNumber = 0x12,
103    Array = 0x20,
104    Object = 0x21,
105    Struct = 0x22,
106    Map = 0x23,
107    Tuple = 0x24,
108    Ref = 0x30,
109    Tagged = 0x31,
110    Timestamp = 0x32,
111}
112
113impl TryFrom<u8> for TLType {
114    type Error = Error;
115
116    fn try_from(v: u8) -> Result<Self> {
117        match v {
118            0x00 => Ok(Self::Null),
119            0x01 => Ok(Self::Bool),
120            0x02 => Ok(Self::Int8),
121            0x03 => Ok(Self::Int16),
122            0x04 => Ok(Self::Int32),
123            0x05 => Ok(Self::Int64),
124            0x06 => Ok(Self::UInt8),
125            0x07 => Ok(Self::UInt16),
126            0x08 => Ok(Self::UInt32),
127            0x09 => Ok(Self::UInt64),
128            0x0A => Ok(Self::Float32),
129            0x0B => Ok(Self::Float64),
130            0x10 => Ok(Self::String),
131            0x11 => Ok(Self::Bytes),
132            0x12 => Ok(Self::JsonNumber),
133            0x20 => Ok(Self::Array),
134            0x21 => Ok(Self::Object),
135            0x22 => Ok(Self::Struct),
136            0x23 => Ok(Self::Map),
137            0x24 => Ok(Self::Tuple),
138            0x30 => Ok(Self::Ref),
139            0x31 => Ok(Self::Tagged),
140            0x32 => Ok(Self::Timestamp),
141            _ => Err(Error::InvalidType(v)),
142        }
143    }
144}
145
146// =============================================================================
147// Field Type
148// =============================================================================
149
150#[derive(Debug, Clone, PartialEq)]
151pub struct FieldType {
152    pub base: String,
153    pub nullable: bool,
154    pub is_array: bool,
155}
156
157impl FieldType {
158    pub fn new(base: impl Into<String>) -> Self {
159        Self {
160            base: base.into(),
161            nullable: false,
162            is_array: false,
163        }
164    }
165
166    pub fn nullable(mut self) -> Self {
167        self.nullable = true;
168        self
169    }
170
171    pub fn array(mut self) -> Self {
172        self.is_array = true;
173        self
174    }
175
176    pub fn parse(s: &str) -> Self {
177        let mut s = s.trim();
178        let mut nullable = false;
179        let mut is_array = false;
180
181        // Check nullable
182        if s.ends_with('?') {
183            nullable = true;
184            s = &s[..s.len() - 1];
185        }
186
187        // Check array
188        if s.starts_with("[]") {
189            is_array = true;
190            s = &s[2..];
191        }
192
193        Self {
194            base: s.to_string(),
195            nullable,
196            is_array,
197        }
198    }
199
200    pub fn to_tl_type(&self) -> TLType {
201        if self.is_array {
202            return TLType::Array;
203        }
204        match self.base.as_str() {
205            "bool" => TLType::Bool,
206            "int8" => TLType::Int8,
207            "int16" => TLType::Int16,
208            "int" | "int32" => TLType::Int32,
209            "int64" => TLType::Int64,
210            "uint8" => TLType::UInt8,
211            "uint16" => TLType::UInt16,
212            "uint" | "uint32" => TLType::UInt32,
213            "uint64" => TLType::UInt64,
214            "float32" => TLType::Float32,
215            "float" | "float64" => TLType::Float64,
216            "string" => TLType::String,
217            "bytes" => TLType::Bytes,
218            "timestamp" => TLType::Timestamp,
219            "object" => TLType::Object,
220            "tuple" => TLType::Tuple,
221            "map" => TLType::Map,
222            _ => TLType::Struct, // Assume struct reference
223        }
224    }
225
226    pub fn is_struct(&self) -> bool {
227        !self.is_array && self.to_tl_type() == TLType::Struct
228    }
229}
230
231impl fmt::Display for FieldType {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        if self.is_array {
234            write!(f, "[]")?;
235        }
236        write!(f, "{}", self.base)?;
237        if self.nullable {
238            write!(f, "?")?;
239        }
240        Ok(())
241    }
242}
243
244// =============================================================================
245// Schema
246// =============================================================================
247
248#[derive(Debug, Clone)]
249pub struct Field {
250    pub name: String,
251    pub field_type: FieldType,
252}
253
254impl Field {
255    pub fn new(name: impl Into<String>, field_type: FieldType) -> Self {
256        Self {
257            name: name.into(),
258            field_type,
259        }
260    }
261}
262
263#[derive(Debug, Clone)]
264pub struct Schema {
265    pub name: String,
266    pub fields: Vec<Field>,
267}
268
269impl Schema {
270    pub fn new(name: impl Into<String>) -> Self {
271        Self {
272            name: name.into(),
273            fields: Vec::new(),
274        }
275    }
276
277    pub fn field(mut self, name: impl Into<String>, field_type: FieldType) -> Self {
278        self.fields.push(Field::new(name, field_type));
279        self
280    }
281
282    pub fn add_field(&mut self, name: impl Into<String>, field_type: FieldType) {
283        self.fields.push(Field::new(name, field_type));
284    }
285
286    pub fn get_field(&self, name: &str) -> Option<&Field> {
287        self.fields.iter().find(|f| f.name == name)
288    }
289
290    pub fn field_index(&self, name: &str) -> Option<usize> {
291        self.fields.iter().position(|f| f.name == name)
292    }
293}
294
295// =============================================================================
296// Union (Discriminated Union)
297// =============================================================================
298
299/// A variant in a union type
300#[derive(Debug, Clone)]
301pub struct Variant {
302    pub name: String,
303    pub fields: Vec<Field>,
304}
305
306impl Variant {
307    pub fn new(name: impl Into<String>) -> Self {
308        Self {
309            name: name.into(),
310            fields: Vec::new(),
311        }
312    }
313
314    pub fn field(mut self, name: impl Into<String>, field_type: FieldType) -> Self {
315        self.fields.push(Field::new(name, field_type));
316        self
317    }
318}
319
320/// A discriminated union type
321#[derive(Debug, Clone)]
322pub struct Union {
323    pub name: String,
324    pub variants: Vec<Variant>,
325}
326
327impl Union {
328    pub fn new(name: impl Into<String>) -> Self {
329        Self {
330            name: name.into(),
331            variants: Vec::new(),
332        }
333    }
334
335    pub fn variant(mut self, variant: Variant) -> Self {
336        self.variants.push(variant);
337        self
338    }
339
340    pub fn add_variant(&mut self, variant: Variant) {
341        self.variants.push(variant);
342    }
343
344    pub fn get_variant(&self, name: &str) -> Option<&Variant> {
345        self.variants.iter().find(|v| v.name == name)
346    }
347}
348
349// =============================================================================
350// Value
351// =============================================================================
352
353#[derive(Debug, Clone, PartialEq)]
354pub enum Value {
355    Null,
356    Bool(bool),
357    Int(i64),
358    UInt(u64),
359    Float(f64),
360    String(String),
361    Bytes(Vec<u8>),
362    Array(Vec<Value>),
363    Object(ObjectMap<String, Value>),
364    Map(Vec<(Value, Value)>),  // Key-value pairs preserving order
365    Ref(String),
366    Tagged(String, Box<Value>),
367    Timestamp(i64, i16),  // Unix milliseconds, timezone offset in minutes
368    JsonNumber(String),  // Arbitrary-precision number (raw decimal string)
369}
370
371impl Value {
372    pub fn is_null(&self) -> bool {
373        matches!(self, Value::Null)
374    }
375
376    pub fn as_bool(&self) -> Option<bool> {
377        match self {
378            Value::Bool(b) => Some(*b),
379            _ => None,
380        }
381    }
382
383    pub fn as_int(&self) -> Option<i64> {
384        match self {
385            Value::Int(i) => Some(*i),
386            Value::UInt(u) if *u <= i64::MAX as u64 => Some(*u as i64),
387            Value::JsonNumber(s) => s.parse::<i64>().ok(),
388            _ => None,
389        }
390    }
391
392    pub fn as_int_checked(&self) -> Result<i64> {
393        match self {
394            Value::Int(i) => Ok(*i),
395            Value::UInt(u) if *u <= i64::MAX as u64 => Ok(*u as i64),
396            Value::UInt(u) => Err(Error::ValueOutOfRange(
397                format!("uint {} exceeds i64::MAX", u),
398            )),
399            Value::JsonNumber(s) => s.parse::<i64>().map_err(|_| Error::ValueOutOfRange(
400                format!("json number '{}' does not fit in i64", s),
401            )),
402            _ => Err(Error::ValueOutOfRange(
403                format!("cannot convert {:?} to i64", self),
404            )),
405        }
406    }
407
408    pub fn as_uint(&self) -> Option<u64> {
409        match self {
410            Value::UInt(u) => Some(*u),
411            Value::Int(i) if *i >= 0 => Some(*i as u64),
412            Value::JsonNumber(s) => s.parse::<u64>().ok(),
413            _ => None,
414        }
415    }
416
417    pub fn as_float(&self) -> Option<f64> {
418        match self {
419            Value::Float(f) => Some(*f),
420            Value::Int(i) => Some(*i as f64),
421            Value::UInt(u) => Some(*u as f64),
422            Value::JsonNumber(s) => s.parse::<f64>().ok(),
423            _ => None,
424        }
425    }
426
427    pub fn as_str(&self) -> Option<&str> {
428        match self {
429            Value::String(s) => Some(s),
430            Value::JsonNumber(s) => Some(s),
431            _ => None,
432        }
433    }
434
435    pub fn as_bytes(&self) -> Option<&[u8]> {
436        match self {
437            Value::Bytes(b) => Some(b),
438            _ => None,
439        }
440    }
441
442    pub fn as_array(&self) -> Option<&[Value]> {
443        match self {
444            Value::Array(arr) => Some(arr),
445            _ => None,
446        }
447    }
448
449    pub fn as_object(&self) -> Option<&ObjectMap<String, Value>> {
450        match self {
451            Value::Object(obj) => Some(obj),
452            _ => None,
453        }
454    }
455
456    pub fn get(&self, key: &str) -> Option<&Value> {
457        self.as_object()?.get(key)
458    }
459
460    pub fn index(&self, idx: usize) -> Option<&Value> {
461        self.as_array()?.get(idx)
462    }
463
464    pub fn tl_type(&self) -> TLType {
465        match self {
466            Value::Null => TLType::Null,
467            Value::Bool(_) => TLType::Bool,
468            Value::Int(i) => {
469                if *i >= i8::MIN as i64 && *i <= i8::MAX as i64 {
470                    TLType::Int8
471                } else if *i >= i16::MIN as i64 && *i <= i16::MAX as i64 {
472                    TLType::Int16
473                } else if *i >= i32::MIN as i64 && *i <= i32::MAX as i64 {
474                    TLType::Int32
475                } else {
476                    TLType::Int64
477                }
478            }
479            Value::UInt(u) => {
480                if *u <= u8::MAX as u64 {
481                    TLType::UInt8
482                } else if *u <= u16::MAX as u64 {
483                    TLType::UInt16
484                } else if *u <= u32::MAX as u64 {
485                    TLType::UInt32
486                } else {
487                    TLType::UInt64
488                }
489            }
490            Value::Float(_) => TLType::Float64,
491            Value::String(_) => TLType::String,
492            Value::Bytes(_) => TLType::Bytes,
493            Value::Array(_) => TLType::Array,
494            Value::Object(_) => TLType::Object,
495            Value::Map(_) => TLType::Map,
496            Value::Ref(_) => TLType::Ref,
497            Value::Tagged(_, _) => TLType::Tagged,
498            Value::Timestamp(_, _) => TLType::Timestamp,
499            Value::JsonNumber(_) => TLType::JsonNumber,
500        }
501    }
502
503    pub fn as_timestamp(&self) -> Option<(i64, i16)> {
504        match self {
505            Value::Timestamp(ts, tz) => Some((*ts, *tz)),
506            _ => None,
507        }
508    }
509
510    pub fn as_timestamp_millis(&self) -> Option<i64> {
511        match self {
512            Value::Timestamp(ts, _) => Some(*ts),
513            _ => None,
514        }
515    }
516
517    pub fn as_map(&self) -> Option<&[(Value, Value)]> {
518        match self {
519            Value::Map(m) => Some(m),
520            _ => None,
521        }
522    }
523
524    pub fn as_ref_name(&self) -> Option<&str> {
525        match self {
526            Value::Ref(name) => Some(name),
527            _ => None,
528        }
529    }
530
531    pub fn as_tagged(&self) -> Option<(&str, &Value)> {
532        match self {
533            Value::Tagged(tag, value) => Some((tag, value)),
534            _ => None,
535        }
536    }
537
538    pub fn as_json_number(&self) -> Option<&str> {
539        match self {
540            Value::JsonNumber(s) => Some(s),
541            _ => None,
542        }
543    }
544}
545
546impl Default for Value {
547    fn default() -> Self {
548        Value::Null
549    }
550}
551
552// Conversions
553impl From<bool> for Value {
554    fn from(b: bool) -> Self { Value::Bool(b) }
555}
556
557impl From<i32> for Value {
558    fn from(i: i32) -> Self { Value::Int(i as i64) }
559}
560
561impl From<i64> for Value {
562    fn from(i: i64) -> Self { Value::Int(i) }
563}
564
565impl From<u32> for Value {
566    fn from(u: u32) -> Self { Value::UInt(u as u64) }
567}
568
569impl From<u64> for Value {
570    fn from(u: u64) -> Self { Value::UInt(u) }
571}
572
573impl From<f64> for Value {
574    fn from(f: f64) -> Self { Value::Float(f) }
575}
576
577impl From<String> for Value {
578    fn from(s: String) -> Self { Value::String(s) }
579}
580
581impl From<&str> for Value {
582    fn from(s: &str) -> Self { Value::String(s.to_string()) }
583}
584
585impl<T: Into<Value>> From<Vec<T>> for Value {
586    fn from(v: Vec<T>) -> Self {
587        Value::Array(v.into_iter().map(Into::into).collect())
588    }
589}
590
591impl From<ObjectMap<String, Value>> for Value {
592    fn from(m: ObjectMap<String, Value>) -> Self {
593        Value::Object(m)
594    }
595}
596
597impl From<HashMap<String, Value>> for Value {
598    fn from(m: HashMap<String, Value>) -> Self {
599        Value::Object(m.into_iter().collect())
600    }
601}
602
603// =============================================================================
604// Tests
605// =============================================================================
606
607#[cfg(test)]
608mod tests {
609    use super::*;
610
611    // -------------------------------------------------------------------------
612    // TLType::try_from
613    // -------------------------------------------------------------------------
614
615    #[test]
616    fn test_tltype_try_from_all_valid() {
617        let cases: Vec<(u8, TLType)> = vec![
618            (0x00, TLType::Null),
619            (0x01, TLType::Bool),
620            (0x02, TLType::Int8),
621            (0x03, TLType::Int16),
622            (0x04, TLType::Int32),
623            (0x05, TLType::Int64),
624            (0x06, TLType::UInt8),
625            (0x07, TLType::UInt16),
626            (0x08, TLType::UInt32),
627            (0x09, TLType::UInt64),
628            (0x0A, TLType::Float32),
629            (0x0B, TLType::Float64),
630            (0x10, TLType::String),
631            (0x11, TLType::Bytes),
632            (0x20, TLType::Array),
633            (0x21, TLType::Object),
634            (0x22, TLType::Struct),
635            (0x23, TLType::Map),
636            (0x24, TLType::Tuple),
637            (0x30, TLType::Ref),
638            (0x31, TLType::Tagged),
639            (0x32, TLType::Timestamp),
640            (0x12, TLType::JsonNumber),
641        ];
642        for (byte, expected) in cases {
643            assert_eq!(TLType::try_from(byte).unwrap(), expected, "byte=0x{:02X}", byte);
644        }
645    }
646
647    #[test]
648    fn test_tltype_try_from_invalid() {
649        let err = TLType::try_from(0xFF).unwrap_err();
650        assert!(matches!(err, Error::InvalidType(0xFF)));
651
652        let err2 = TLType::try_from(0x0C).unwrap_err();
653        assert!(matches!(err2, Error::InvalidType(0x0C)));
654    }
655
656    // -------------------------------------------------------------------------
657    // FieldType
658    // -------------------------------------------------------------------------
659
660    #[test]
661    fn test_fieldtype_new() {
662        let ft = FieldType::new("int");
663        assert_eq!(ft.base, "int");
664        assert!(!ft.nullable);
665        assert!(!ft.is_array);
666    }
667
668    #[test]
669    fn test_fieldtype_nullable() {
670        let ft = FieldType::new("string").nullable();
671        assert!(ft.nullable);
672        assert!(!ft.is_array);
673    }
674
675    #[test]
676    fn test_fieldtype_array() {
677        let ft = FieldType::new("int").array();
678        assert!(ft.is_array);
679        assert!(!ft.nullable);
680    }
681
682    #[test]
683    fn test_fieldtype_parse_simple() {
684        let ft = FieldType::parse("int");
685        assert_eq!(ft.base, "int");
686        assert!(!ft.nullable);
687        assert!(!ft.is_array);
688    }
689
690    #[test]
691    fn test_fieldtype_parse_nullable() {
692        let ft = FieldType::parse("string?");
693        assert_eq!(ft.base, "string");
694        assert!(ft.nullable);
695        assert!(!ft.is_array);
696    }
697
698    #[test]
699    fn test_fieldtype_parse_array() {
700        let ft = FieldType::parse("[]int");
701        assert_eq!(ft.base, "int");
702        assert!(!ft.nullable);
703        assert!(ft.is_array);
704    }
705
706    #[test]
707    fn test_fieldtype_parse_array_nullable() {
708        let ft = FieldType::parse("[]string?");
709        assert_eq!(ft.base, "string");
710        assert!(ft.nullable);
711        assert!(ft.is_array);
712    }
713
714    #[test]
715    fn test_fieldtype_parse_with_whitespace() {
716        let ft = FieldType::parse("  int64  ");
717        assert_eq!(ft.base, "int64");
718    }
719
720    #[test]
721    fn test_fieldtype_display() {
722        assert_eq!(FieldType::new("int").to_string(), "int");
723        assert_eq!(FieldType::new("string").nullable().to_string(), "string?");
724        assert_eq!(FieldType::new("int").array().to_string(), "[]int");
725        assert_eq!(
726            FieldType::new("string").array().nullable().to_string(),
727            "[]string?"
728        );
729    }
730
731    #[test]
732    fn test_fieldtype_to_tl_type_all_bases() {
733        let cases = vec![
734            ("bool", TLType::Bool),
735            ("int8", TLType::Int8),
736            ("int16", TLType::Int16),
737            ("int", TLType::Int32),
738            ("int32", TLType::Int32),
739            ("int64", TLType::Int64),
740            ("uint8", TLType::UInt8),
741            ("uint16", TLType::UInt16),
742            ("uint", TLType::UInt32),
743            ("uint32", TLType::UInt32),
744            ("uint64", TLType::UInt64),
745            ("float32", TLType::Float32),
746            ("float", TLType::Float64),
747            ("float64", TLType::Float64),
748            ("string", TLType::String),
749            ("bytes", TLType::Bytes),
750            ("timestamp", TLType::Timestamp),
751            ("object", TLType::Object),
752            ("tuple", TLType::Tuple),
753            ("map", TLType::Map),
754            ("MyStruct", TLType::Struct),
755            ("SomeUnknown", TLType::Struct),
756        ];
757        for (base, expected) in cases {
758            let ft = FieldType::new(base);
759            assert_eq!(ft.to_tl_type(), expected, "base={}", base);
760        }
761    }
762
763    #[test]
764    fn test_fieldtype_array_overrides_base() {
765        let ft = FieldType::new("int").array();
766        assert_eq!(ft.to_tl_type(), TLType::Array);
767    }
768
769    #[test]
770    fn test_fieldtype_is_struct() {
771        assert!(FieldType::new("MyStruct").is_struct());
772        assert!(!FieldType::new("int").is_struct());
773        assert!(!FieldType::new("string").is_struct());
774        assert!(!FieldType::new("int").array().is_struct()); // array is not struct
775    }
776
777    // -------------------------------------------------------------------------
778    // Schema
779    // -------------------------------------------------------------------------
780
781    #[test]
782    fn test_schema_builder() {
783        let schema = Schema::new("User")
784            .field("id", FieldType::new("int64"))
785            .field("name", FieldType::new("string"));
786        assert_eq!(schema.name, "User");
787        assert_eq!(schema.fields.len(), 2);
788        assert_eq!(schema.fields[0].name, "id");
789        assert_eq!(schema.fields[1].name, "name");
790    }
791
792    #[test]
793    fn test_schema_add_field() {
794        let mut schema = Schema::new("Event");
795        schema.add_field("ts", FieldType::new("timestamp"));
796        assert_eq!(schema.fields.len(), 1);
797        assert_eq!(schema.fields[0].name, "ts");
798    }
799
800    #[test]
801    fn test_schema_get_field_found() {
802        let schema = Schema::new("User")
803            .field("id", FieldType::new("int64"))
804            .field("name", FieldType::new("string"));
805        let f = schema.get_field("name").unwrap();
806        assert_eq!(f.name, "name");
807        assert_eq!(f.field_type.base, "string");
808    }
809
810    #[test]
811    fn test_schema_get_field_missing() {
812        let schema = Schema::new("User")
813            .field("id", FieldType::new("int64"));
814        assert!(schema.get_field("nonexistent").is_none());
815    }
816
817    #[test]
818    fn test_schema_field_index_found() {
819        let schema = Schema::new("User")
820            .field("id", FieldType::new("int64"))
821            .field("name", FieldType::new("string"));
822        assert_eq!(schema.field_index("id"), Some(0));
823        assert_eq!(schema.field_index("name"), Some(1));
824    }
825
826    #[test]
827    fn test_schema_field_index_missing() {
828        let schema = Schema::new("User")
829            .field("id", FieldType::new("int64"));
830        assert_eq!(schema.field_index("missing"), None);
831    }
832
833    // -------------------------------------------------------------------------
834    // Union / Variant
835    // -------------------------------------------------------------------------
836
837    #[test]
838    fn test_variant_builder() {
839        let v = Variant::new("Circle")
840            .field("radius", FieldType::new("float"));
841        assert_eq!(v.name, "Circle");
842        assert_eq!(v.fields.len(), 1);
843        assert_eq!(v.fields[0].name, "radius");
844    }
845
846    #[test]
847    fn test_union_builder() {
848        let u = Union::new("Shape")
849            .variant(Variant::new("Circle").field("radius", FieldType::new("float")))
850            .variant(Variant::new("Point"));
851        assert_eq!(u.name, "Shape");
852        assert_eq!(u.variants.len(), 2);
853    }
854
855    #[test]
856    fn test_union_add_variant() {
857        let mut u = Union::new("Shape");
858        u.add_variant(Variant::new("Circle"));
859        assert_eq!(u.variants.len(), 1);
860    }
861
862    #[test]
863    fn test_union_get_variant() {
864        let u = Union::new("Shape")
865            .variant(Variant::new("Circle").field("radius", FieldType::new("float")))
866            .variant(Variant::new("Point"));
867        assert!(u.get_variant("Circle").is_some());
868        assert!(u.get_variant("Point").is_some());
869        assert!(u.get_variant("Unknown").is_none());
870    }
871
872    // -------------------------------------------------------------------------
873    // Value accessors
874    // -------------------------------------------------------------------------
875
876    #[test]
877    fn test_value_is_null() {
878        assert!(Value::Null.is_null());
879        assert!(!Value::Bool(false).is_null());
880    }
881
882    #[test]
883    fn test_value_as_bool() {
884        assert_eq!(Value::Bool(true).as_bool(), Some(true));
885        assert_eq!(Value::Int(1).as_bool(), None);
886    }
887
888    #[test]
889    fn test_value_as_int_from_uint() {
890        // UInt coercion to i64
891        assert_eq!(Value::UInt(42).as_int(), Some(42));
892    }
893
894    #[test]
895    fn test_value_as_int_overflow_returns_none() {
896        // UInt > i64::MAX should return None, not silently wrap
897        assert_eq!(Value::UInt(u64::MAX).as_int(), None);
898        assert_eq!(Value::UInt(i64::MAX as u64 + 1).as_int(), None);
899        // Boundary: exactly i64::MAX should succeed
900        assert_eq!(Value::UInt(i64::MAX as u64).as_int(), Some(i64::MAX));
901    }
902
903    #[test]
904    fn test_value_as_int_checked_success() {
905        assert_eq!(Value::Int(42).as_int_checked().unwrap(), 42);
906        assert_eq!(Value::UInt(42).as_int_checked().unwrap(), 42);
907        assert_eq!(Value::UInt(i64::MAX as u64).as_int_checked().unwrap(), i64::MAX);
908    }
909
910    #[test]
911    fn test_value_as_int_checked_overflow_error() {
912        let result = Value::UInt(u64::MAX).as_int_checked();
913        assert!(result.is_err());
914        assert!(matches!(result.unwrap_err(), Error::ValueOutOfRange(_)));
915    }
916
917    #[test]
918    fn test_value_as_int_checked_wrong_type_error() {
919        let result = Value::String("nope".into()).as_int_checked();
920        assert!(result.is_err());
921        assert!(matches!(result.unwrap_err(), Error::ValueOutOfRange(_)));
922    }
923
924    #[test]
925    fn test_value_as_int_from_non_numeric() {
926        assert_eq!(Value::String("nope".into()).as_int(), None);
927    }
928
929    #[test]
930    fn test_value_as_uint_from_positive_int() {
931        // Positive Int coercion to u64
932        assert_eq!(Value::Int(42).as_uint(), Some(42));
933    }
934
935    #[test]
936    fn test_value_as_uint_from_negative_int() {
937        // Negative Int should not coerce to u64
938        assert_eq!(Value::Int(-1).as_uint(), None);
939    }
940
941    #[test]
942    fn test_value_as_uint_from_non_numeric() {
943        assert_eq!(Value::Bool(true).as_uint(), None);
944    }
945
946    #[test]
947    fn test_value_as_float_from_int() {
948        assert_eq!(Value::Int(42).as_float(), Some(42.0));
949    }
950
951    #[test]
952    fn test_value_as_float_from_uint() {
953        assert_eq!(Value::UInt(100).as_float(), Some(100.0));
954    }
955
956    #[test]
957    fn test_value_as_float_from_non_numeric() {
958        assert_eq!(Value::String("no".into()).as_float(), None);
959    }
960
961    #[test]
962    fn test_value_as_str() {
963        assert_eq!(Value::String("hello".into()).as_str(), Some("hello"));
964        assert_eq!(Value::Int(1).as_str(), None);
965    }
966
967    #[test]
968    fn test_value_as_bytes() {
969        let data = vec![1u8, 2, 3];
970        assert_eq!(Value::Bytes(data.clone()).as_bytes(), Some(data.as_slice()));
971        assert_eq!(Value::Null.as_bytes(), None);
972    }
973
974    #[test]
975    fn test_value_as_array() {
976        let arr = vec![Value::Int(1), Value::Int(2)];
977        assert_eq!(
978            Value::Array(arr.clone()).as_array(),
979            Some(arr.as_slice())
980        );
981        assert_eq!(Value::Null.as_array(), None);
982    }
983
984    #[test]
985    fn test_value_as_object() {
986        let mut obj = ObjectMap::new();
987        obj.insert("k".to_string(), Value::Int(1));
988        assert!(Value::Object(obj).as_object().is_some());
989        assert!(Value::Null.as_object().is_none());
990    }
991
992    #[test]
993    fn test_value_get() {
994        let mut obj = ObjectMap::new();
995        obj.insert("key".to_string(), Value::Int(42));
996        let val = Value::Object(obj);
997        assert_eq!(val.get("key"), Some(&Value::Int(42)));
998        assert_eq!(val.get("missing"), None);
999        assert_eq!(Value::Int(1).get("x"), None);
1000    }
1001
1002    #[test]
1003    fn test_value_index() {
1004        let val = Value::Array(vec![Value::Int(10), Value::Int(20)]);
1005        assert_eq!(val.index(0), Some(&Value::Int(10)));
1006        assert_eq!(val.index(1), Some(&Value::Int(20)));
1007        assert_eq!(val.index(5), None);
1008        assert_eq!(Value::Int(1).index(0), None);
1009    }
1010
1011    #[test]
1012    fn test_value_as_timestamp() {
1013        assert_eq!(Value::Timestamp(1700000000, 0).as_timestamp(), Some((1700000000, 0)));
1014        assert_eq!(Value::Int(42).as_timestamp(), None);
1015    }
1016
1017    #[test]
1018    fn test_value_as_map() {
1019        let pairs = vec![(Value::String("k".into()), Value::Int(1))];
1020        assert_eq!(
1021            Value::Map(pairs.clone()).as_map(),
1022            Some(pairs.as_slice())
1023        );
1024        assert_eq!(Value::Null.as_map(), None);
1025    }
1026
1027    #[test]
1028    fn test_value_as_ref_name() {
1029        assert_eq!(Value::Ref("MyRef".into()).as_ref_name(), Some("MyRef"));
1030        assert_eq!(Value::Null.as_ref_name(), None);
1031    }
1032
1033    #[test]
1034    fn test_value_as_tagged() {
1035        let val = Value::Tagged("tag".into(), Box::new(Value::Int(1)));
1036        let (tag, inner) = val.as_tagged().unwrap();
1037        assert_eq!(tag, "tag");
1038        assert_eq!(inner, &Value::Int(1));
1039        assert_eq!(Value::Null.as_tagged(), None);
1040    }
1041
1042    #[test]
1043    fn test_value_default() {
1044        assert_eq!(Value::default(), Value::Null);
1045    }
1046
1047    // -------------------------------------------------------------------------
1048    // Value::tl_type() boundary values
1049    // -------------------------------------------------------------------------
1050
1051    #[test]
1052    fn test_value_tl_type_int_boundaries() {
1053        // i8 range
1054        assert_eq!(Value::Int(0).tl_type(), TLType::Int8);
1055        assert_eq!(Value::Int(127).tl_type(), TLType::Int8);
1056        assert_eq!(Value::Int(-128).tl_type(), TLType::Int8);
1057
1058        // i16 range
1059        assert_eq!(Value::Int(128).tl_type(), TLType::Int16);
1060        assert_eq!(Value::Int(-129).tl_type(), TLType::Int16);
1061        assert_eq!(Value::Int(32767).tl_type(), TLType::Int16);
1062        assert_eq!(Value::Int(-32768).tl_type(), TLType::Int16);
1063
1064        // i32 range
1065        assert_eq!(Value::Int(32768).tl_type(), TLType::Int32);
1066        assert_eq!(Value::Int(-32769).tl_type(), TLType::Int32);
1067        assert_eq!(Value::Int(i32::MAX as i64).tl_type(), TLType::Int32);
1068        assert_eq!(Value::Int(i32::MIN as i64).tl_type(), TLType::Int32);
1069
1070        // i64 range
1071        assert_eq!(Value::Int(i32::MAX as i64 + 1).tl_type(), TLType::Int64);
1072        assert_eq!(Value::Int(i32::MIN as i64 - 1).tl_type(), TLType::Int64);
1073        assert_eq!(Value::Int(i64::MAX).tl_type(), TLType::Int64);
1074        assert_eq!(Value::Int(i64::MIN).tl_type(), TLType::Int64);
1075    }
1076
1077    #[test]
1078    fn test_value_tl_type_uint_boundaries() {
1079        // u8 range
1080        assert_eq!(Value::UInt(0).tl_type(), TLType::UInt8);
1081        assert_eq!(Value::UInt(255).tl_type(), TLType::UInt8);
1082
1083        // u16 range
1084        assert_eq!(Value::UInt(256).tl_type(), TLType::UInt16);
1085        assert_eq!(Value::UInt(65535).tl_type(), TLType::UInt16);
1086
1087        // u32 range
1088        assert_eq!(Value::UInt(65536).tl_type(), TLType::UInt32);
1089        assert_eq!(Value::UInt(u32::MAX as u64).tl_type(), TLType::UInt32);
1090
1091        // u64 range
1092        assert_eq!(Value::UInt(u32::MAX as u64 + 1).tl_type(), TLType::UInt64);
1093        assert_eq!(Value::UInt(u64::MAX).tl_type(), TLType::UInt64);
1094    }
1095
1096    #[test]
1097    fn test_value_tl_type_other_variants() {
1098        assert_eq!(Value::Null.tl_type(), TLType::Null);
1099        assert_eq!(Value::Bool(true).tl_type(), TLType::Bool);
1100        assert_eq!(Value::Float(1.0).tl_type(), TLType::Float64);
1101        assert_eq!(Value::String("s".into()).tl_type(), TLType::String);
1102        assert_eq!(Value::Bytes(vec![]).tl_type(), TLType::Bytes);
1103        assert_eq!(Value::Array(vec![]).tl_type(), TLType::Array);
1104        assert_eq!(Value::Object(ObjectMap::new()).tl_type(), TLType::Object);
1105        assert_eq!(Value::Map(vec![]).tl_type(), TLType::Map);
1106        assert_eq!(Value::Ref("r".into()).tl_type(), TLType::Ref);
1107        assert_eq!(
1108            Value::Tagged("t".into(), Box::new(Value::Null)).tl_type(),
1109            TLType::Tagged
1110        );
1111        assert_eq!(Value::Timestamp(0, 0).tl_type(), TLType::Timestamp);
1112        assert_eq!(
1113            Value::JsonNumber("123.456".into()).tl_type(),
1114            TLType::JsonNumber
1115        );
1116    }
1117
1118    // -------------------------------------------------------------------------
1119    // Value::From impls
1120    // -------------------------------------------------------------------------
1121
1122    #[test]
1123    fn test_value_from_bool() {
1124        assert_eq!(Value::from(true), Value::Bool(true));
1125        assert_eq!(Value::from(false), Value::Bool(false));
1126    }
1127
1128    #[test]
1129    fn test_value_from_i32() {
1130        assert_eq!(Value::from(42i32), Value::Int(42));
1131        assert_eq!(Value::from(-1i32), Value::Int(-1));
1132    }
1133
1134    #[test]
1135    fn test_value_from_i64() {
1136        assert_eq!(Value::from(42i64), Value::Int(42));
1137    }
1138
1139    #[test]
1140    fn test_value_from_u32() {
1141        assert_eq!(Value::from(42u32), Value::UInt(42));
1142    }
1143
1144    #[test]
1145    fn test_value_from_u64() {
1146        assert_eq!(Value::from(42u64), Value::UInt(42));
1147    }
1148
1149    #[test]
1150    fn test_value_from_f64() {
1151        assert_eq!(Value::from(3.14f64), Value::Float(3.14));
1152    }
1153
1154    #[test]
1155    fn test_value_from_string() {
1156        assert_eq!(Value::from("hello".to_string()), Value::String("hello".into()));
1157    }
1158
1159    #[test]
1160    fn test_value_from_str() {
1161        assert_eq!(Value::from("hello"), Value::String("hello".into()));
1162    }
1163
1164    #[test]
1165    fn test_value_from_vec() {
1166        let v: Vec<i32> = vec![1, 2, 3];
1167        assert_eq!(
1168            Value::from(v),
1169            Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
1170        );
1171    }
1172
1173    #[test]
1174    fn test_value_from_objectmap() {
1175        let mut m = ObjectMap::new();
1176        m.insert("key".to_string(), Value::Int(42));
1177        let val = Value::from(m.clone());
1178        assert_eq!(val, Value::Object(m));
1179    }
1180
1181    #[test]
1182    fn test_value_from_hashmap() {
1183        let mut m = HashMap::new();
1184        m.insert("key".to_string(), Value::Int(42));
1185        let val = Value::from(m);
1186        assert!(matches!(val, Value::Object(_)));
1187        assert_eq!(val.as_object().unwrap().get("key").unwrap().as_int(), Some(42));
1188    }
1189
1190    // -------------------------------------------------------------------------
1191    // Error Display
1192    // -------------------------------------------------------------------------
1193
1194    #[test]
1195    fn test_error_display_all_variants() {
1196        assert_eq!(
1197            Error::Io(io::Error::new(io::ErrorKind::NotFound, "gone")).to_string(),
1198            "IO error: gone"
1199        );
1200        assert_eq!(Error::InvalidMagic.to_string(), "Invalid TeaLeaf magic bytes");
1201        assert_eq!(
1202            Error::InvalidVersion { major: 99, minor: 1 }.to_string(),
1203            "Unsupported version: 99.1"
1204        );
1205        assert_eq!(
1206            Error::InvalidType(0xFF).to_string(),
1207            "Invalid type code: 0xFF"
1208        );
1209        assert_eq!(Error::InvalidUtf8.to_string(), "Invalid UTF-8");
1210        assert_eq!(
1211            Error::UnexpectedToken {
1212                expected: "number".into(),
1213                got: "string".into()
1214            }
1215            .to_string(),
1216            "Expected number, got string"
1217        );
1218        assert_eq!(Error::UnexpectedEof.to_string(), "Unexpected end of input");
1219        assert_eq!(
1220            Error::UnknownStruct("Foo".into()).to_string(),
1221            "Unknown struct: Foo"
1222        );
1223        assert_eq!(
1224            Error::MissingField("bar".into()).to_string(),
1225            "Missing field: bar"
1226        );
1227        assert_eq!(
1228            Error::ParseError("bad input".into()).to_string(),
1229            "Parse error: bad input"
1230        );
1231        assert_eq!(
1232            Error::ValueOutOfRange("too big".into()).to_string(),
1233            "Value out of range: too big"
1234        );
1235    }
1236
1237    #[test]
1238    fn test_error_from_io() {
1239        let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
1240        let tl_err = Error::from(io_err);
1241        assert!(matches!(tl_err, Error::Io(_)));
1242        assert!(tl_err.to_string().contains("denied"));
1243    }
1244
1245    // -------------------------------------------------------------------------
1246    // Field
1247    // -------------------------------------------------------------------------
1248
1249    #[test]
1250    fn test_field_new() {
1251        let f = Field::new("age", FieldType::new("int"));
1252        assert_eq!(f.name, "age");
1253        assert_eq!(f.field_type.base, "int");
1254    }
1255}