Skip to main content

icydb_core/db/schema/
errors.rs

1//! Module: db::schema::errors
2//! Responsibility: schema validation error taxonomy for runtime schema contracts.
3//! Does not own: predicate AST or planning policy logic.
4//! Boundary: error surface for schema construction and predicate-schema validation.
5
6use crate::db::predicate::{CoercionId, CompareOp};
7use std::fmt;
8
9/// Compact predicate operator identity for schema validation diagnostics.
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub enum SchemaValidationOperator {
12    Compare(CompareOp),
13    CompareField { op: CompareOp, right_field: String },
14    IsEmpty,
15    IsNotEmpty,
16    TextContains,
17    TextContainsCi,
18}
19
20impl SchemaValidationOperator {
21    pub(crate) const fn compare(op: CompareOp) -> Self {
22        Self::Compare(op)
23    }
24
25    pub(crate) fn compare_field(op: CompareOp, right_field: &str) -> Self {
26        Self::CompareField {
27            op,
28            right_field: right_field.to_string(),
29        }
30    }
31}
32
33impl fmt::Display for SchemaValidationOperator {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            Self::Compare(op) => write!(f, "{op:?}"),
37            Self::CompareField { op, right_field } => {
38                write!(f, "{op:?} against field '{right_field}'")
39            }
40            Self::IsEmpty => f.write_str("is_empty"),
41            Self::IsNotEmpty => f.write_str("is_not_empty"),
42            Self::TextContains => f.write_str("text_contains"),
43            Self::TextContainsCi => f.write_str("text_contains_ci"),
44        }
45    }
46}
47
48/// Compact literal validation reason for schema validation diagnostics.
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub enum SchemaLiteralValidationReason {
51    ExpectedList,
52    ExpectedText,
53    ExpectedScalar,
54    LiteralTypeMismatch,
55    ListElementTypeMismatch,
56    EnumPathMismatch,
57    UnknownEnumVariant,
58    EnumBodyMismatch,
59}
60
61impl fmt::Display for SchemaLiteralValidationReason {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            Self::ExpectedList => f.write_str("expected list literal"),
65            Self::ExpectedText => f.write_str("expected text literal"),
66            Self::ExpectedScalar => f.write_str("expected scalar literal"),
67            Self::LiteralTypeMismatch => f.write_str("literal type does not match field type"),
68            Self::ListElementTypeMismatch => {
69                f.write_str("list literal does not match field element type")
70            }
71            Self::EnumPathMismatch => f.write_str("enum path does not match field enum type"),
72            Self::UnknownEnumVariant => f.write_str("enum variant is not accepted for field type"),
73            Self::EnumBodyMismatch => {
74                f.write_str("enum payload does not match the accepted variant contract")
75            }
76        }
77    }
78}
79
80/// Predicate/schema validation failures, including invalid model contracts.
81#[derive(Debug, thiserror::Error)]
82pub enum ValidateError {
83    #[error("unknown field '{field}'")]
84    UnknownField { field: String },
85
86    #[error("field '{field}' is not queryable")]
87    NonQueryableFieldType { field: String },
88
89    #[error("duplicate field '{field}'")]
90    DuplicateField { field: String },
91
92    #[error("map predicates are unsupported for field '{field}'")]
93    MapPredicateUnsupported { field: String },
94
95    #[error("primary key '{field}' not present in entity fields")]
96    InvalidPrimaryKey { field: String },
97
98    #[error("primary key '{field}' has a non-keyable type")]
99    InvalidPrimaryKeyType { field: String },
100
101    #[error("duplicate index name '{name}'")]
102    DuplicateIndexName { name: String },
103
104    #[error("operator {operator} is not valid for field '{field}'")]
105    InvalidOperator {
106        field: String,
107        operator: SchemaValidationOperator,
108    },
109
110    #[error("coercion {coercion:?} is not valid for field '{field}'")]
111    InvalidCoercion { field: String, coercion: CoercionId },
112
113    #[error("invalid literal for field '{field}': {reason}")]
114    InvalidLiteral {
115        field: String,
116        reason: SchemaLiteralValidationReason,
117    },
118}
119
120impl ValidateError {
121    pub(crate) fn invalid_operator(field: &str, operator: SchemaValidationOperator) -> Self {
122        Self::InvalidOperator {
123            field: field.to_string(),
124            operator,
125        }
126    }
127
128    pub(crate) fn invalid_literal(field: &str, reason: SchemaLiteralValidationReason) -> Self {
129        Self::InvalidLiteral {
130            field: field.to_string(),
131            reason,
132        }
133    }
134}