pcapsql_core/schema/
field.rs

1//! Field descriptor for protocol schemas.
2
3use super::DataKind;
4
5/// Engine-agnostic field definition.
6///
7/// This replaces `arrow::datatypes::Field` in the Protocol trait,
8/// allowing protocol parsers to be used with any SQL backend.
9#[derive(Debug, Clone, PartialEq)]
10pub struct FieldDescriptor {
11    /// Field name (snake_case, e.g., "src_port")
12    pub name: &'static str,
13
14    /// Data type
15    pub kind: DataKind,
16
17    /// Whether the field can be NULL
18    pub nullable: bool,
19
20    /// Optional description for documentation
21    pub description: Option<&'static str>,
22}
23
24impl FieldDescriptor {
25    /// Create a new non-nullable field.
26    pub const fn new(name: &'static str, kind: DataKind) -> Self {
27        Self {
28            name,
29            kind,
30            nullable: false,
31            description: None,
32        }
33    }
34
35    /// Create a new nullable field.
36    pub const fn nullable(name: &'static str, kind: DataKind) -> Self {
37        Self {
38            name,
39            kind,
40            nullable: true,
41            description: None,
42        }
43    }
44
45    /// Add a description to the field.
46    pub const fn with_description(mut self, desc: &'static str) -> Self {
47        self.description = Some(desc);
48        self
49    }
50
51    /// Builder: set nullability.
52    pub const fn set_nullable(mut self, nullable: bool) -> Self {
53        self.nullable = nullable;
54        self
55    }
56}
57
58/// Helper macros for common field patterns.
59impl FieldDescriptor {
60    /// Frame number field (present in all protocol tables).
61    pub const fn frame_number() -> Self {
62        Self::new("frame_number", DataKind::UInt64).with_description("Unique packet identifier")
63    }
64
65    /// Timestamp field.
66    pub const fn timestamp() -> Self {
67        Self::new("timestamp", DataKind::TimestampMicros)
68            .with_description("Packet capture time (UTC)")
69    }
70
71    /// Source port field.
72    pub const fn src_port() -> Self {
73        Self::new("src_port", DataKind::UInt16).with_description("Source port number")
74    }
75
76    /// Destination port field.
77    pub const fn dst_port() -> Self {
78        Self::new("dst_port", DataKind::UInt16).with_description("Destination port number")
79    }
80
81    /// IPv4 address field (stored as UInt32).
82    pub const fn ipv4_field(name: &'static str) -> Self {
83        Self::new(name, DataKind::UInt32)
84    }
85
86    /// IPv6 address field (stored as 16-byte binary).
87    pub const fn ipv6_field(name: &'static str) -> Self {
88        Self::new(name, DataKind::FixedBinary(16))
89    }
90
91    /// MAC address field (stored as 6-byte binary).
92    pub const fn mac_field(name: &'static str) -> Self {
93        Self::new(name, DataKind::FixedBinary(6))
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_field_creation() {
103        let field = FieldDescriptor::new("test", DataKind::UInt32);
104        assert_eq!(field.name, "test");
105        assert_eq!(field.kind, DataKind::UInt32);
106        assert!(!field.nullable);
107        assert!(field.description.is_none());
108    }
109
110    #[test]
111    fn test_nullable_field() {
112        let field = FieldDescriptor::nullable("optional", DataKind::String);
113        assert!(field.nullable);
114    }
115
116    #[test]
117    fn test_builder_pattern() {
118        let field = FieldDescriptor::new("count", DataKind::UInt64)
119            .set_nullable(true)
120            .with_description("Packet count");
121
122        assert!(field.nullable);
123        assert_eq!(field.description, Some("Packet count"));
124    }
125
126    #[test]
127    fn test_common_fields() {
128        let frame = FieldDescriptor::frame_number();
129        assert_eq!(frame.name, "frame_number");
130        assert_eq!(frame.kind, DataKind::UInt64);
131
132        let mac = FieldDescriptor::mac_field("src_mac");
133        assert_eq!(mac.kind, DataKind::FixedBinary(6));
134    }
135}