use thiserror::Error;
use crate::model::{DataType, Id};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCode {
InvalidMagicOrVersion,
IndexOutOfBounds,
InvalidSignature,
InvalidUtf8,
MalformedEncoding,
}
impl ErrorCode {
pub fn code(&self) -> &'static str {
match self {
ErrorCode::InvalidMagicOrVersion => "E001",
ErrorCode::IndexOutOfBounds => "E002",
ErrorCode::InvalidSignature => "E003",
ErrorCode::InvalidUtf8 => "E004",
ErrorCode::MalformedEncoding => "E005",
}
}
}
#[derive(Debug, Clone, PartialEq, Error)]
pub enum DecodeError {
#[error("[E001] invalid magic bytes: expected GRC2 or GRC2Z, found {found:?}")]
InvalidMagic { found: [u8; 4] },
#[error("[E001] unsupported version: {version}")]
UnsupportedVersion { version: u8 },
#[error("[E002] {dict} index {index} out of bounds (size: {size})")]
IndexOutOfBounds {
dict: &'static str,
index: usize,
size: usize,
},
#[error("[E004] invalid UTF-8 in {field}")]
InvalidUtf8 { field: &'static str },
#[error("[E005] unexpected end of input while reading {context}")]
UnexpectedEof { context: &'static str },
#[error("[E005] varint exceeds maximum length (10 bytes)")]
VarintTooLong,
#[error("[E005] varint overflow (value exceeds u64)")]
VarintOverflow,
#[error("[E005] {field} length {len} exceeds maximum {max}")]
LengthExceedsLimit {
field: &'static str,
len: usize,
max: usize,
},
#[error("[E005] invalid op type: {op_type}")]
InvalidOpType { op_type: u8 },
#[error("[E005] invalid data type: {data_type}")]
InvalidDataType { data_type: u8 },
#[error("[E005] invalid embedding sub-type: {sub_type}")]
InvalidEmbeddingSubType { sub_type: u8 },
#[error("[E005] invalid boolean value: {value} (expected 0x00 or 0x01)")]
InvalidBoolean { value: u8 },
#[error("[E005] reserved bits are non-zero in {context}")]
ReservedBitsSet { context: &'static str },
#[error("[E005] POINT latitude {lat} out of range [-90, +90]")]
LatitudeOutOfRange { lat: f64 },
#[error("[E005] POINT longitude {lon} out of range [-180, +180]")]
LongitudeOutOfRange { lon: f64 },
#[error("[E005] position string contains invalid character: {char:?}")]
InvalidPositionChar { char: char },
#[error("[E005] position string length {len} exceeds maximum 64")]
PositionTooLong { len: usize },
#[error("[E005] embedding data length {actual} doesn't match expected {expected} for {dims} dims")]
EmbeddingDataMismatch {
dims: usize,
expected: usize,
actual: usize,
},
#[error("[E005] DECIMAL has trailing zeros in mantissa (not normalized)")]
DecimalNotNormalized,
#[error("[E005] DECIMAL mantissa bytes are not minimal")]
DecimalMantissaNotMinimal,
#[error("[E005] float value is NaN")]
FloatIsNan,
#[error("[E005] malformed encoding: {context}")]
MalformedEncoding { context: &'static str },
#[error("[E005] zstd decompression failed: {0}")]
DecompressionFailed(String),
#[error("[E005] decompressed size {actual} doesn't match declared {declared}")]
UncompressedSizeMismatch { declared: usize, actual: usize },
#[error("[E005] duplicate ID in {dict} dictionary: {id:?}")]
DuplicateDictionaryEntry { dict: &'static str, id: Id },
}
impl DecodeError {
pub fn code(&self) -> ErrorCode {
match self {
DecodeError::InvalidMagic { .. } | DecodeError::UnsupportedVersion { .. } => {
ErrorCode::InvalidMagicOrVersion
}
DecodeError::IndexOutOfBounds { .. } => ErrorCode::IndexOutOfBounds,
DecodeError::InvalidUtf8 { .. } => ErrorCode::InvalidUtf8,
_ => ErrorCode::MalformedEncoding,
}
}
}
#[derive(Debug, Clone, PartialEq, Error)]
pub enum EncodeError {
#[error("{field} length {len} exceeds maximum {max}")]
LengthExceedsLimit {
field: &'static str,
len: usize,
max: usize,
},
#[error("embedding data length {data_len} doesn't match {dims} dims for sub-type {sub_type:?}")]
EmbeddingDimensionMismatch {
sub_type: u8,
dims: usize,
data_len: usize,
},
#[error("zstd compression failed: {0}")]
CompressionFailed(String),
#[error("DECIMAL value is not normalized (has trailing zeros)")]
DecimalNotNormalized,
#[error("float value is NaN")]
FloatIsNan,
#[error("POINT latitude {lat} out of range [-90, +90]")]
LatitudeOutOfRange { lat: f64 },
#[error("POINT longitude {lon} out of range [-180, +180]")]
LongitudeOutOfRange { lon: f64 },
#[error("position string contains invalid character")]
InvalidPositionChar,
#[error("position string length exceeds maximum 64")]
PositionTooLong,
#[error("DATE string is not valid ISO 8601: {reason}")]
InvalidDate { reason: &'static str },
#[error("batch entity has {actual} values but schema requires {expected}")]
BatchEntityValueCountMismatch { expected: usize, actual: usize },
#[error("invalid input: {context}")]
InvalidInput { context: &'static str },
#[error("duplicate author ID in canonical mode: {id:?}")]
DuplicateAuthor { id: Id },
#[error("duplicate value (property={property:?}, language={language:?}) in canonical mode")]
DuplicateValue { property: Id, language: Option<Id> },
#[error("duplicate unset property (property={property:?}, language={language:?}) in canonical mode")]
DuplicateUnset { property: Id, language: Option<Id> },
}
#[derive(Debug, Clone, PartialEq, Error)]
pub enum ValidationError {
#[error("value type mismatch for property {property:?}: expected {expected:?}")]
TypeMismatch { property: Id, expected: DataType },
#[error("entity {entity:?} is dead (tombstoned)")]
EntityIsDead { entity: Id },
#[error("relation {relation:?} is dead (tombstoned)")]
RelationIsDead { relation: Id },
#[error("property {property:?} not found in schema")]
PropertyNotFound { property: Id },
#[error("data type mismatch for property {property:?}: schema says {schema:?}, edit declares {declared:?}")]
DataTypeInconsistent {
property: Id,
schema: DataType,
declared: DataType,
},
}