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