1use crate::{
7 db::predicate::{CoercionId, CompareOp, UnsupportedQueryFeature},
8 model::index::{IndexExpression, IndexModel},
9};
10use std::fmt;
11
12#[derive(Clone, Debug, Eq, PartialEq)]
14pub enum SchemaValidationOperator {
15 Compare(CompareOp),
16 CompareField { op: CompareOp, right_field: String },
17 IsEmpty,
18 IsNotEmpty,
19 TextContains,
20 TextContainsCi,
21}
22
23impl SchemaValidationOperator {
24 pub(crate) const fn compare(op: CompareOp) -> Self {
25 Self::Compare(op)
26 }
27
28 pub(crate) fn compare_field(op: CompareOp, right_field: &str) -> Self {
29 Self::CompareField {
30 op,
31 right_field: right_field.to_string(),
32 }
33 }
34}
35
36impl fmt::Display for SchemaValidationOperator {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 match self {
39 Self::Compare(op) => write!(f, "{op:?}"),
40 Self::CompareField { op, right_field } => {
41 write!(f, "{op:?} against field '{right_field}'")
42 }
43 Self::IsEmpty => f.write_str("is_empty"),
44 Self::IsNotEmpty => f.write_str("is_not_empty"),
45 Self::TextContains => f.write_str("text_contains"),
46 Self::TextContainsCi => f.write_str("text_contains_ci"),
47 }
48 }
49}
50
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
53pub enum SchemaLiteralValidationReason {
54 ExpectedList,
55 ExpectedText,
56 ExpectedScalar,
57 LiteralTypeMismatch,
58 ListElementTypeMismatch,
59 EnumPathMismatch,
60 UnknownEnumVariant,
61 EnumBodyMismatch,
62}
63
64impl fmt::Display for SchemaLiteralValidationReason {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 Self::ExpectedList => f.write_str("expected list literal"),
68 Self::ExpectedText => f.write_str("expected text literal"),
69 Self::ExpectedScalar => f.write_str("expected scalar literal"),
70 Self::LiteralTypeMismatch => f.write_str("literal type does not match field type"),
71 Self::ListElementTypeMismatch => {
72 f.write_str("list literal does not match field element type")
73 }
74 Self::EnumPathMismatch => f.write_str("enum path does not match field enum type"),
75 Self::UnknownEnumVariant => f.write_str("enum variant is not accepted for field type"),
76 Self::EnumBodyMismatch => {
77 f.write_str("enum payload does not match the accepted variant contract")
78 }
79 }
80 }
81}
82
83#[derive(Debug, thiserror::Error)]
85pub enum ValidateError {
86 #[error("unknown field '{field}'")]
87 UnknownField { field: String },
88
89 #[error("field '{field}' is not queryable")]
90 NonQueryableFieldType { field: String },
91
92 #[error("duplicate field '{field}'")]
93 DuplicateField { field: String },
94
95 #[error("unsupported query feature")]
96 UnsupportedQueryFeature(UnsupportedQueryFeature),
97
98 #[error("primary key '{field}' not present in entity fields")]
99 InvalidPrimaryKey { field: String },
100
101 #[error("primary key '{field}' has a non-keyable type")]
102 InvalidPrimaryKeyType { field: String },
103
104 #[error("index '{index}' references unknown field '{field}'")]
105 IndexFieldUnknown {
106 index: Box<IndexModel>,
107 field: String,
108 },
109
110 #[error("index '{index}' references non-queryable field '{field}'")]
111 IndexFieldNotQueryable {
112 index: Box<IndexModel>,
113 field: String,
114 },
115
116 #[error(
117 "index '{index}' references map field '{field}'; map fields are not queryable in icydb 0.7"
118 )]
119 IndexFieldMapNotQueryable {
120 index: Box<IndexModel>,
121 field: String,
122 },
123
124 #[error("index '{index}' repeats field '{field}'")]
125 IndexFieldDuplicate {
126 index: Box<IndexModel>,
127 field: String,
128 },
129
130 #[error("index '{index}' expression key item '{expression}' requires {expected}")]
131 IndexExpressionFieldTypeInvalid {
132 index: &'static str,
133 expression: IndexExpression,
134 expected: &'static str,
135 },
136
137 #[error("duplicate index name '{name}'")]
138 DuplicateIndexName { name: String },
139
140 #[error("index '{index}' predicate '{predicate}' has invalid SQL syntax")]
141 InvalidIndexPredicateSyntax {
142 index: Box<IndexModel>,
143 predicate: &'static str,
144 },
145
146 #[error("index '{index}' predicate '{predicate}' is invalid for schema")]
147 InvalidIndexPredicateSchema {
148 index: Box<IndexModel>,
149 predicate: &'static str,
150 },
151
152 #[error("operator {operator} is not valid for field '{field}'")]
153 InvalidOperator {
154 field: String,
155 operator: SchemaValidationOperator,
156 },
157
158 #[error("coercion {coercion:?} is not valid for field '{field}'")]
159 InvalidCoercion { field: String, coercion: CoercionId },
160
161 #[error("invalid literal for field '{field}': {reason}")]
162 InvalidLiteral {
163 field: String,
164 reason: SchemaLiteralValidationReason,
165 },
166}
167
168impl From<UnsupportedQueryFeature> for ValidateError {
169 fn from(err: UnsupportedQueryFeature) -> Self {
170 Self::UnsupportedQueryFeature(err)
171 }
172}
173
174impl ValidateError {
175 pub(crate) fn invalid_operator(field: &str, operator: SchemaValidationOperator) -> Self {
176 Self::InvalidOperator {
177 field: field.to_string(),
178 operator,
179 }
180 }
181
182 pub(crate) fn invalid_literal(field: &str, reason: SchemaLiteralValidationReason) -> Self {
183 Self::InvalidLiteral {
184 field: field.to_string(),
185 reason,
186 }
187 }
188}