Skip to main content

triplox_client/
protocol.rs

1//! Shared protocol constants and types used by the storage codec, the
2//! HTTP/2 server, and the msgpack wire codec.
3//!
4//! The actual wire encoding lives in [`crate::msgpack_codec`]; this module
5//! holds the data-type tag taxonomy (also used by the storage codec to keep
6//! a single set of type identifiers across storage and wire), the error code
7//! enum, and the column description used in query response schemas.
8
9use anyhow::{anyhow, bail, Result};
10use chrono::{DateTime, TimeZone, Utc};
11
12use crate::ops::DataType;
13
14// ---------------------------------------------------------------------------
15// Helpers
16// ---------------------------------------------------------------------------
17
18/// Convert microseconds since Unix epoch to a `DateTime<Utc>`.
19///
20/// Uses `div_euclid`/`rem_euclid` so that pre-epoch (negative) timestamps
21/// are decoded correctly — Rust's `/` truncates toward zero which gives
22/// wrong results for negative values.
23pub fn micros_to_datetime(micros: i64) -> Result<DateTime<Utc>> {
24    let secs = micros.div_euclid(1_000_000);
25    let nanos = (micros.rem_euclid(1_000_000) as u32) * 1000;
26    Utc.timestamp_opt(secs, nanos)
27        .single()
28        .ok_or_else(|| anyhow!("Invalid timestamp: {} micros", micros))
29}
30
31// ---------------------------------------------------------------------------
32// Constants
33// ---------------------------------------------------------------------------
34
35/// Default maximum HTTP body size (64 MB).
36pub const DEFAULT_MAX_MESSAGE_SIZE: u32 = 64 * 1024 * 1024;
37
38/// `ErrorResponse` severity byte for non-fatal errors.
39pub const SEVERITY_ERROR: u8 = b'E';
40/// `ErrorResponse` severity byte for fatal errors.
41pub const SEVERITY_FATAL: u8 = b'F';
42
43// DataType tag bytes — shared between the storage codec (`crate::codec`)
44// and column descriptions in query responses.
45pub const TAG_BIG_INT: u8 = 1;
46pub const TAG_BOOLEAN: u8 = 2;
47pub const TAG_BYTES: u8 = 3;
48pub const TAG_DOUBLE: u8 = 4;
49pub const TAG_FLOAT: u8 = 5;
50pub const TAG_INSTANT: u8 = 6;
51pub const TAG_LONG: u8 = 7;
52/// Reserved for a future `DataType::Ref` variant.
53pub const TAG_REF: u8 = 8;
54pub const TAG_STRING: u8 = 9;
55pub const TAG_UUID: u8 = 10;
56pub const TAG_VECTOR: u8 = 11;
57pub const TAG_MAP: u8 = 12;
58pub const TAG_KEYWORD: u8 = 13;
59/// Column-description-only tag for unions of concrete types.
60pub const TAG_UNION: u8 = 127;
61/// Column-description-only tag for indeterminate column types.
62pub const TAG_UNKNOWN: u8 = 255;
63
64// ---------------------------------------------------------------------------
65// Error Codes
66// ---------------------------------------------------------------------------
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69#[repr(u16)]
70pub enum ErrorCode {
71    // Connection errors (1xxx)
72    ProtocolVersionMismatch = 1000,
73    InvalidStartup = 1001,
74    // Query errors (2xxx)
75    ParseError = 2000,
76    QueryError = 2001,
77    InvalidQuery = 2002,
78    EmptyQuery = 2003,
79    // Transaction errors (3xxx)
80    TxError = 3000,
81    TxAborted = 3001,
82    TxNotIndexed = 3002,
83    // Internal/protocol errors (4xxx)
84    InternalError = 4000,
85    MessageTooLarge = 4001,
86    InvalidMessageType = 4002,
87    QueryCancelled = 4003,
88    ServerShuttingDown = 4004,
89}
90
91impl ErrorCode {
92    pub fn from_u16(code: u16) -> Result<Self> {
93        match code {
94            1000 => Ok(ErrorCode::ProtocolVersionMismatch),
95            1001 => Ok(ErrorCode::InvalidStartup),
96            2000 => Ok(ErrorCode::ParseError),
97            2001 => Ok(ErrorCode::QueryError),
98            2002 => Ok(ErrorCode::InvalidQuery),
99            2003 => Ok(ErrorCode::EmptyQuery),
100            3000 => Ok(ErrorCode::TxError),
101            3001 => Ok(ErrorCode::TxAborted),
102            3002 => Ok(ErrorCode::TxNotIndexed),
103            4000 => Ok(ErrorCode::InternalError),
104            4001 => Ok(ErrorCode::MessageTooLarge),
105            4002 => Ok(ErrorCode::InvalidMessageType),
106            4003 => Ok(ErrorCode::QueryCancelled),
107            4004 => Ok(ErrorCode::ServerShuttingDown),
108            _ => bail!("Unknown error code: {}", code),
109        }
110    }
111
112    pub fn as_u16(self) -> u16 {
113        self as u16
114    }
115}
116
117// ---------------------------------------------------------------------------
118// Column Description
119// ---------------------------------------------------------------------------
120
121#[derive(Debug, Clone, PartialEq)]
122pub struct ColumnDescription {
123    pub name: String,
124    pub data_type: u8,
125    /// For Union columns (`data_type == TAG_UNION`), the concrete member type
126    /// tags. `None` for concrete types and `TAG_UNKNOWN`.
127    pub members: Option<Vec<u8>>,
128}
129
130// ---------------------------------------------------------------------------
131// DataType tag dispatch
132// ---------------------------------------------------------------------------
133
134/// Returns the DataType tag byte for a value (see `TAG_*` constants).
135pub fn data_type_tag(dt: &DataType) -> u8 {
136    match dt {
137        DataType::BigInt(_) => TAG_BIG_INT,
138        DataType::Boolean(_) => TAG_BOOLEAN,
139        DataType::Bytes(_) => TAG_BYTES,
140        DataType::Double(_) => TAG_DOUBLE,
141        DataType::Float(_) => TAG_FLOAT,
142        DataType::Instant(_) => TAG_INSTANT,
143        DataType::Keyword(_) => TAG_KEYWORD,
144        DataType::Long(_) => TAG_LONG,
145        DataType::String(_) => TAG_STRING,
146        DataType::Uuid(_) => TAG_UUID,
147        DataType::Vector(_) => TAG_VECTOR,
148        DataType::Map(_) => TAG_MAP,
149    }
150}