Skip to main content

reifydb_type/error/
diagnostic.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2025 ReifyDB
3
4use super::{Diagnostic, IntoDiagnostic, util::value_range};
5use crate::{
6	error::{
7		AstErrorKind, AuthErrorKind, BinaryOp, BlobEncodingKind, ConstraintKind, FunctionErrorKind, LogicalOp,
8		NetworkErrorKind, OperandCategory, ProcedureErrorKind, RuntimeErrorKind, TemporalKind, TypeError,
9	},
10	fragment::Fragment,
11	value::r#type::Type,
12};
13
14fn temporal_unit_name(unit: char) -> &'static str {
15	match unit {
16		'Y' => "year",
17		'M' => "month/minute",
18		'W' => "week",
19		'D' => "day",
20		'H' => "hour",
21		'S' => "second",
22		_ => "unit",
23	}
24}
25
26impl IntoDiagnostic for TypeError {
27	fn into_diagnostic(self) -> Diagnostic {
28		match self {
29			TypeError::LogicalOperatorNotApplicable {
30				operator,
31				operand_category,
32				fragment,
33			} => {
34				let code = match (&operator, &operand_category) {
35					(LogicalOp::Not, OperandCategory::Number) => "OPERATOR_001",
36					(LogicalOp::Not, OperandCategory::Text) => "OPERATOR_002",
37					(LogicalOp::Not, OperandCategory::Temporal) => "OPERATOR_003",
38					(LogicalOp::Not, OperandCategory::Uuid) => "OPERATOR_004",
39					(LogicalOp::And, OperandCategory::Number) => "OPERATOR_005",
40					(LogicalOp::And, OperandCategory::Text) => "OPERATOR_006",
41					(LogicalOp::And, OperandCategory::Temporal) => "OPERATOR_007",
42					(LogicalOp::And, OperandCategory::Uuid) => "OPERATOR_008",
43					(LogicalOp::Or, OperandCategory::Number) => "OPERATOR_009",
44					(LogicalOp::Or, OperandCategory::Text) => "OPERATOR_010",
45					(LogicalOp::Or, OperandCategory::Temporal) => "OPERATOR_011",
46					(LogicalOp::Or, OperandCategory::Uuid) => "OPERATOR_012",
47					(LogicalOp::Xor, OperandCategory::Number) => "OPERATOR_013",
48					(LogicalOp::Xor, OperandCategory::Text) => "OPERATOR_014",
49					(LogicalOp::Xor, OperandCategory::Temporal) => "OPERATOR_015",
50					(LogicalOp::Xor, OperandCategory::Uuid) => "OPERATOR_016",
51				};
52
53				let message = format!("Cannot apply {} operator to {}", operator, operand_category);
54				let label = Some(format!("logical operator on {} type", match &operand_category {
55					OperandCategory::Number => "numeric",
56					OperandCategory::Text => "text",
57					OperandCategory::Temporal => "temporal",
58					OperandCategory::Uuid => "UUID",
59				}));
60
61				let help = format!(
62					"The {} operator can only be applied to boolean values. Consider using comparison operators{}first",
63					operator,
64					match &operator {
65						LogicalOp::Not => " or casting to boolean ",
66						_ => " ",
67					}
68				);
69
70				let mut notes = vec![];
71				match &operator {
72					LogicalOp::Not => {
73						notes.push(
74							"NOT is a logical operator that inverts boolean values (true becomes false, false becomes true)"
75								.to_string(),
76						);
77					}
78					LogicalOp::And => {
79						notes.push("AND is a logical operator that combines boolean values".to_string());
80					}
81					LogicalOp::Or => {
82						notes.push("OR is a logical operator that combines boolean values".to_string());
83					}
84					LogicalOp::Xor => {
85						notes.push(
86							"XOR is a logical operator that performs exclusive or on boolean values".to_string(),
87						);
88					}
89				}
90
91				match (&operator, &operand_category) {
92					(LogicalOp::Not, OperandCategory::Number) => {
93						notes.push("For numeric negation, use the minus (-) operator instead".to_string());
94						notes.push("To convert numbers to boolean, use comparison operators like: value != 0".to_string());
95					}
96					(LogicalOp::Not, OperandCategory::Text) => {
97						notes.push("To convert text to boolean, use comparison operators like: text != '...'".to_string());
98						notes.push("For string operations, use appropriate string functions instead".to_string());
99					}
100					(LogicalOp::Not, OperandCategory::Temporal) => {
101						notes.push("To convert temporal values to boolean, use comparison operators like: date > '2023-01-01'".to_string());
102						notes.push("Temporal types include Date, DateTime, Time, and Duration".to_string());
103					}
104					(LogicalOp::Not, OperandCategory::Uuid) => {
105						notes.push("To convert UUIDs to boolean, use comparison operators like: uuid == '...'".to_string());
106						notes.push("UUID types include Uuid4 and Uuid7".to_string());
107					}
108					(_, OperandCategory::Number) => {
109						notes.push("To convert numbers to boolean, use comparison operators like: value != 0".to_string());
110						match &operator {
111							LogicalOp::And => notes.push("For bitwise operations on integers, use the bitwise AND (&) operator instead".to_string()),
112							LogicalOp::Or => notes.push("For bitwise operations on integers, use the bitwise OR (|) operator instead".to_string()),
113							LogicalOp::Xor => notes.push("For bitwise operations on integers, use the bitwise XOR (^) operator instead".to_string()),
114							_ => {}
115						}
116					}
117					(_, OperandCategory::Text) => {
118						notes.push("To convert text to boolean, use comparison operators like: text != '...'".to_string());
119						match &operator {
120							LogicalOp::And => notes.push("For text concatenation, use the string concatenation operator (||) instead".to_string()),
121							LogicalOp::Or => notes.push("For text concatenation, use the string concatenation operator (+) instead".to_string()),
122							LogicalOp::Xor => notes.push("XOR returns true when exactly one operand is true".to_string()),
123							_ => {}
124						}
125					}
126					(_, OperandCategory::Temporal) => {
127						notes.push("To convert temporal values to boolean, use comparison operators like: date > '2023-01-01'".to_string());
128						notes.push("Temporal types include Date, DateTime, Time, and Duration".to_string());
129					}
130					(_, OperandCategory::Uuid) => {
131						notes.push("To convert UUIDs to boolean, use comparison operators like: uuid == '...'".to_string());
132						if matches!(&operator, LogicalOp::Xor) {
133							// no extra note
134						}
135						notes.push("UUID types include Uuid4 and Uuid7".to_string());
136					}
137				}
138
139				Diagnostic {
140					code: code.to_string(),
141					statement: None,
142					message,
143					column: None,
144					fragment,
145					label,
146					help: Some(help),
147					notes,
148					cause: None,
149					operator_chain: None,
150				}
151			}
152
153			TypeError::BinaryOperatorNotApplicable {
154				operator,
155				left,
156				right,
157				fragment,
158			} => {
159				let code = match &operator {
160					BinaryOp::Add => "OPERATOR_017",
161					BinaryOp::Sub => "OPERATOR_018",
162					BinaryOp::Mul => "OPERATOR_019",
163					BinaryOp::Div => "OPERATOR_020",
164					BinaryOp::Rem => "OPERATOR_021",
165					BinaryOp::Equal => "OPERATOR_022",
166					BinaryOp::NotEqual => "OPERATOR_023",
167					BinaryOp::LessThan => "OPERATOR_024",
168					BinaryOp::LessThanEqual => "OPERATOR_025",
169					BinaryOp::GreaterThan => "OPERATOR_026",
170					BinaryOp::GreaterThanEqual => "OPERATOR_027",
171					BinaryOp::Between => "OPERATOR_028",
172				};
173
174				let sym = operator.symbol();
175				let message = if matches!(&operator, BinaryOp::Between) {
176					format!("Cannot apply '{}' operator to {} with range of {}", sym, left, right)
177				} else {
178					format!("Cannot apply '{}' operator to {} and {}", sym, left, right)
179				};
180				let label = Some(format!("'{}' operator on incompatible types", sym));
181
182				let mut notes = if matches!(&operator, BinaryOp::Between) {
183					vec![
184						format!("Value is of type: {}", left),
185						format!("Range bounds are of type: {}", right),
186					]
187				} else {
188					vec![
189						format!("Left operand is of type: {}", left),
190						format!("Right operand is of type: {}", right),
191					]
192				};
193
194				let comparison_note = match &operator {
195					BinaryOp::Add
196					| BinaryOp::Sub
197					| BinaryOp::Mul
198					| BinaryOp::Div
199					| BinaryOp::Rem => {
200						Some("Consider converting operands to compatible numeric types first".to_string())
201					}
202					BinaryOp::Equal => {
203						Some("Equality comparison is only supported between compatible types".to_string())
204					}
205					BinaryOp::NotEqual => {
206						Some("Inequality comparison is only supported between compatible types".to_string())
207					}
208					BinaryOp::LessThan => {
209						Some("Less than comparison is only supported between compatible types".to_string())
210					}
211					BinaryOp::LessThanEqual => {
212						Some("Less than or equal comparison is only supported between compatible types".to_string())
213					}
214					BinaryOp::GreaterThan => {
215						Some("Greater than comparison is only supported between compatible types".to_string())
216					}
217					BinaryOp::GreaterThanEqual => {
218						Some("Greater than or equal comparison is only supported between compatible types".to_string())
219					}
220					BinaryOp::Between => {
221						Some("BETWEEN comparison is only supported between compatible types".to_string())
222					}
223				};
224				if let Some(note) = comparison_note {
225					notes.push(note);
226				}
227
228				Diagnostic {
229					code: code.to_string(),
230					statement: None,
231					message,
232					column: None,
233					fragment,
234					label,
235					help: None,
236					notes,
237					cause: None,
238					operator_chain: None,
239				}
240			}
241
242			TypeError::UnsupportedCast { from, to, fragment } => {
243				let label = Some(format!("cannot cast {} of type {} to {}", fragment.text(), from, to));
244				Diagnostic {
245					code: "CAST_001".to_string(),
246					statement: None,
247					message: format!("unsupported cast from {} to {}", from, to),
248					fragment,
249					label,
250					help: Some("ensure the source and target types are compatible for casting".to_string()),
251					notes: vec![
252						"supported casts include: numeric to numeric, string to temporal, boolean to numeric"
253							.to_string(),
254					],
255					column: None,
256					cause: None,
257					operator_chain: None,
258				}
259			}
260
261			TypeError::CastToNumberFailed {
262				target,
263				fragment,
264				cause,
265			} => {
266				let label = Some(format!("failed to cast to {}", target));
267				Diagnostic {
268					code: "CAST_002".to_string(),
269					statement: None,
270					message: format!("failed to cast to {}", target),
271					fragment,
272					label,
273					help: None,
274					notes: vec![],
275					column: None,
276					cause: Some(Box::new((*cause).into_diagnostic())),
277					operator_chain: None,
278				}
279			}
280
281			TypeError::CastToTemporalFailed {
282				target,
283				fragment,
284				cause,
285			} => {
286				let label = Some(format!("failed to cast to {}", target));
287				Diagnostic {
288					code: "CAST_003".to_string(),
289					statement: None,
290					message: format!("failed to cast to {}", target),
291					fragment,
292					label,
293					help: None,
294					notes: vec![],
295					column: None,
296					cause: Some(Box::new((*cause).into_diagnostic())),
297					operator_chain: None,
298				}
299			}
300
301			TypeError::CastToBooleanFailed { fragment, cause } => {
302				let label = Some("failed to cast to bool".to_string());
303				Diagnostic {
304					code: "CAST_004".to_string(),
305					statement: None,
306					message: "failed to cast to bool".to_string(),
307					fragment,
308					label,
309					help: None,
310					notes: vec![],
311					column: None,
312					cause: Some(Box::new((*cause).into_diagnostic())),
313					operator_chain: None,
314				}
315			}
316
317			TypeError::CastToUuidFailed {
318				target,
319				fragment,
320				cause,
321			} => {
322				let label = Some(format!("failed to cast to {}", target));
323				Diagnostic {
324					code: "CAST_005".to_string(),
325					statement: None,
326					message: format!("failed to cast to {}", target),
327					fragment,
328					label,
329					help: None,
330					notes: vec![],
331					column: None,
332					cause: Some(Box::new((*cause).into_diagnostic())),
333					operator_chain: None,
334				}
335			}
336
337			TypeError::CastBlobToUtf8Failed { fragment, cause } => {
338				let label = Some("failed to cast BLOB to UTF8".to_string());
339				Diagnostic {
340					code: "CAST_006".to_string(),
341					statement: None,
342					message: "failed to cast BLOB to UTF8".to_string(),
343					fragment,
344					label,
345					help: Some(
346						"BLOB contains invalid UTF-8 bytes. Consider using to_utf8_lossy() function instead"
347							.to_string(),
348					),
349					notes: vec![],
350					column: None,
351					cause: Some(Box::new((*cause).into_diagnostic())),
352					operator_chain: None,
353				}
354			}
355
356			TypeError::ConstraintViolation {
357				kind,
358				message,
359				fragment,
360			} => {
361				let (code, help) = match &kind {
362					ConstraintKind::Utf8MaxBytes { max, .. } => (
363						"CONSTRAINT_001",
364						format!(
365							"The UTF8 field is constrained to a maximum of {} bytes. Consider shortening the text or increasing the constraint.",
366							max
367						),
368					),
369					ConstraintKind::BlobMaxBytes { max, .. } => (
370						"CONSTRAINT_002",
371						format!(
372							"The BLOB field is constrained to a maximum of {} bytes. Consider reducing the data size or increasing the constraint.",
373							max
374						),
375					),
376					ConstraintKind::IntMaxBytes { max, .. } => (
377						"CONSTRAINT_003",
378						format!(
379							"The INT field is constrained to a maximum of {} bytes. Consider using a smaller value or increasing the constraint.",
380							max
381						),
382					),
383					ConstraintKind::UintMaxBytes { max, .. } => (
384						"CONSTRAINT_004",
385						format!(
386							"The UINT field is constrained to a maximum of {} bytes. Consider using a smaller value or increasing the constraint.",
387							max
388						),
389					),
390					ConstraintKind::DecimalPrecision { max, .. } => (
391						"CONSTRAINT_005",
392						format!(
393							"The DECIMAL field is constrained to a maximum precision of {} digits. Consider using a smaller number or increasing the precision constraint.",
394							max
395						),
396					),
397					ConstraintKind::DecimalScale { max, .. } => (
398						"CONSTRAINT_006",
399						format!(
400							"The DECIMAL field is constrained to a maximum of {} decimal places. Consider rounding the value or increasing the scale constraint.",
401							max
402						),
403					),
404					ConstraintKind::NoneNotAllowed { column_type } => (
405						"CONSTRAINT_007",
406						format!(
407							"The column type is {} which does not accept none. Use Option({}) if the column should be nullable.",
408							column_type, column_type
409						),
410					),
411				};
412
413				Diagnostic {
414					code: code.to_string(),
415					statement: None,
416					message,
417					column: None,
418					fragment,
419					label: Some("constraint violation".to_string()),
420					help: Some(help),
421					notes: vec![],
422					cause: None,
423					operator_chain: None,
424				}
425			}
426
427			TypeError::InvalidNumberFormat { target, fragment } => {
428				let label = Some(format!("'{}' is not a valid {} number", fragment.text(), target));
429				let (help, notes) = match target {
430					Type::Float4 | Type::Float8 => (
431						"use decimal format (e.g., 123.45, -67.89, 1.23e-4)".to_string(),
432						vec![
433							"valid: 123.45".to_string(),
434							"valid: -67.89".to_string(),
435							"valid: 1.23e-4".to_string(),
436						],
437					),
438					Type::Int1
439					| Type::Int2
440					| Type::Int4
441					| Type::Int8
442					| Type::Int16
443					| Type::Uint1
444					| Type::Uint2
445					| Type::Uint4
446					| Type::Uint8
447					| Type::Uint16 => (
448						"use integer format (e.g., 123, -456) or decimal that can be truncated".to_string(),
449						vec![
450							"valid: 123".to_string(),
451							"valid: -456".to_string(),
452							"truncated: 123.7 → 123".to_string(),
453						],
454					),
455					_ => (
456						"ensure the value is a valid number".to_string(),
457						vec!["use a proper number format".to_string()],
458					),
459				};
460
461				Diagnostic {
462					code: "NUMBER_001".to_string(),
463					statement: None,
464					message: "invalid number format".to_string(),
465					fragment,
466					label,
467					help: Some(help),
468					notes,
469					column: None,
470					cause: None,
471					operator_chain: None,
472				}
473			}
474
475			TypeError::NumberOutOfRange {
476				target,
477				fragment,
478				descriptor,
479			} => {
480				let range = value_range(target.clone());
481
482				let label = if let Some(ref desc) = descriptor {
483					Some(format!(
484						"value '{}' exceeds the valid range for {} column {}",
485						fragment.text(),
486						desc.column_type.as_ref().unwrap_or(&target),
487						desc.location_string()
488					))
489				} else {
490					Some(format!(
491						"value '{}' exceeds the valid range for type {} ({})",
492						fragment.text(),
493						target,
494						range
495					))
496				};
497
498				let help = if let Some(ref desc) = descriptor {
499					if desc.namespace.is_some() && desc.table.is_some() {
500						Some(format!("use a value within range {} or modify column {}", range, desc.location_string()))
501					} else {
502						Some(format!("use a value within range {} or use a wider type", range))
503					}
504				} else {
505					Some(format!("use a value within range {} or use a wider type", range))
506				};
507
508				Diagnostic {
509					code: "NUMBER_002".to_string(),
510					statement: None,
511					message: "number out of range".to_string(),
512					fragment,
513					label,
514					help,
515					notes: vec![format!("valid range: {}", range)],
516					column: None,
517					cause: None,
518					operator_chain: None,
519				}
520			}
521
522			TypeError::NanNotAllowed => Diagnostic {
523				code: "NUMBER_003".to_string(),
524				statement: None,
525				message: "NaN not allowed".to_string(),
526				fragment: Fragment::None,
527				label: Some("NaN (Not a Number) values are not permitted".to_string()),
528				help: Some("use a finite number or none instead".to_string()),
529				notes: vec![],
530				column: None,
531				cause: None,
532				operator_chain: None,
533			},
534
535			TypeError::IntegerPrecisionLoss {
536				source_type,
537				target,
538				fragment,
539			} => {
540				let is_signed = source_type.is_signed_integer();
541				let (min_limit, max_limit) = match target {
542					Type::Float4 => {
543						if is_signed {
544							("-16_777_216 (-2^24)", "16_777_216 (2^24)")
545						} else {
546							("0", "16_777_216 (2^24)")
547						}
548					}
549					Type::Float8 => {
550						if is_signed {
551							("-9_007_199_254_740_992 (-2^53)", "9_007_199_254_740_992 (2^53)")
552						} else {
553							("0", "9_007_199_254_740_992 (2^53)")
554						}
555					}
556					_ => unreachable!("IntegerPrecisionLoss should only be used for float targets"),
557				};
558
559				let label = Some(format!(
560					"converting '{}' from {} to {} would lose precision",
561					fragment.text(),
562					source_type,
563					target
564				));
565
566				Diagnostic {
567					code: "NUMBER_004".to_string(),
568					statement: None,
569					message: "too large for precise float conversion".to_string(),
570					fragment,
571					label,
572					help: None,
573					notes: vec![
574						format!("{} can only represent from {} to {} precisely", target, min_limit, max_limit),
575						"consider using a different numeric type if exact precision is required".to_string(),
576					],
577					column: None,
578					cause: None,
579					operator_chain: None,
580				}
581			}
582
583			TypeError::DecimalScaleExceedsPrecision {
584				scale,
585				precision,
586				fragment,
587			} => {
588				let label = Some(format!("scale ({}) cannot be greater than precision ({})", scale, precision));
589				Diagnostic {
590					code: "NUMBER_005".to_string(),
591					statement: None,
592					message: "decimal scale exceeds precision".to_string(),
593					fragment,
594					label,
595					help: Some(format!("use a scale value between 0 and {} or increase precision", precision)),
596					notes: vec![
597						format!("current precision: {}", precision),
598						format!("current scale: {}", scale),
599						"scale represents the number of digits after the decimal point".to_string(),
600						"precision represents the total number of significant digits".to_string(),
601					],
602					column: None,
603					cause: None,
604					operator_chain: None,
605				}
606			}
607
608			TypeError::DecimalPrecisionInvalid { precision } => {
609				let label = Some(format!("precision ({}) must be at least 1", precision));
610				Diagnostic {
611					code: "NUMBER_006".to_string(),
612					statement: None,
613					message: "invalid decimal precision".to_string(),
614					fragment: Fragment::None,
615					label,
616					help: Some("use a precision value of at least 1".to_string()),
617					notes: vec![
618						format!("current precision: {}", precision),
619						"precision represents the total number of significant digits".to_string(),
620					],
621					column: None,
622					cause: None,
623					operator_chain: None,
624				}
625			}
626
627			TypeError::InvalidBooleanFormat { fragment } => {
628				let value = fragment.text().to_string();
629				let label = Some(format!("expected 'true' or 'false', found '{}'", value));
630				Diagnostic {
631					code: "BOOLEAN_001".to_string(),
632					statement: None,
633					message: "invalid boolean format".to_string(),
634					fragment,
635					label,
636					help: Some("use 'true' or 'false'".to_string()),
637					notes: vec!["valid: true, TRUE".to_string(), "valid: false, FALSE".to_string()],
638					column: None,
639					cause: None,
640					operator_chain: None,
641				}
642			}
643
644			TypeError::EmptyBooleanValue { fragment } => Diagnostic {
645				code: "BOOLEAN_002".to_string(),
646				statement: None,
647				message: "empty boolean value".to_string(),
648				fragment,
649				label: Some("boolean value cannot be empty".to_string()),
650				help: Some("provide either 'true' or 'false'".to_string()),
651				notes: vec!["valid: true".to_string(), "valid: false".to_string()],
652				column: None,
653				cause: None,
654				operator_chain: None,
655			},
656
657			TypeError::InvalidNumberBoolean { fragment } => {
658				let value = fragment.text().to_string();
659				let label =
660					Some(format!("number '{}' cannot be cast to boolean, only 1 or 0 are allowed", value));
661				Diagnostic {
662					code: "BOOLEAN_003".to_string(),
663					statement: None,
664					message: "invalid boolean".to_string(),
665					fragment,
666					label,
667					help: Some("use 1 for true or 0 for false".to_string()),
668					notes: vec![
669						"valid: 1 → true".to_string(),
670						"valid: 0 → false".to_string(),
671						"invalid: any other number".to_string(),
672					],
673					column: None,
674					cause: None,
675					operator_chain: None,
676				}
677			}
678
679			TypeError::Temporal {
680				kind,
681				message,
682				fragment,
683			} => {
684				let (code, label, help, notes) = match &kind {
685					TemporalKind::InvalidDateFormat => (
686						"TEMPORAL_001",
687						Some(format!("expected YYYY-MM-DD format, found '{}'", fragment.text())),
688						Some("use the format YYYY-MM-DD (e.g., 2024-03-15)".to_string()),
689						vec!["dates must have exactly 3 parts separated by hyphens".to_string()],
690					),
691					TemporalKind::InvalidDateTimeFormat => (
692						"TEMPORAL_002",
693						Some(format!("expected YYYY-MM-DDTHH:MM:SS format, found '{}'", fragment.text())),
694						Some("use the format YYYY-MM-DDTHH:MM:SS[.fff][Z|±HH:MM] (e.g., 2024-03-15T14:30:45)".to_string()),
695						vec!["datetime must contain 'T' separator between date and time parts".to_string()],
696					),
697					TemporalKind::InvalidTimeFormat => (
698						"TEMPORAL_003",
699						Some(format!("expected HH:MM:SS format, found '{}'", fragment.text())),
700						Some("use the format HH:MM:SS[.fff][Z|±HH:MM] (e.g., 14:30:45)".to_string()),
701						vec!["time must have exactly 3 parts separated by colons".to_string()],
702					),
703					TemporalKind::InvalidDurationFormat => (
704						"TEMPORAL_004",
705						Some(format!("expected P[n]Y[n]M[n]W[n]D[T[n]H[n]M[n]S] format, found '{}'", fragment.text())),
706						Some("use ISO 8601 duration format starting with 'P' (e.g., P1D, PT2H30M, P1Y2M3DT4H5M6S)".to_string()),
707						vec![
708							"duration must start with 'P' followed by duration components".to_string(),
709							"date part: P[n]Y[n]M[n]W[n]D (years, months, weeks, days)".to_string(),
710							"time part: T[n]H[n]M[n]S (hours, minutes, seconds)".to_string(),
711						],
712					),
713					TemporalKind::InvalidYear => (
714						"TEMPORAL_005",
715						Some(format!("year '{}' cannot be parsed as a number", fragment.text())),
716						Some("ensure the year is a valid 4-digit number".to_string()),
717						vec!["valid examples: 2024, 1999, 2000".to_string()],
718					),
719					TemporalKind::InvalidTimeComponentFormat { component } => (
720						"TEMPORAL_005",
721						Some(format!("{} '{}' must be exactly 2 digits", component, fragment.text())),
722						Some(format!("ensure the {} is exactly 2 digits (e.g., 09, 14, 23)", component)),
723						vec![format!("{} must be exactly 2 digits in HH:MM:SS format", component)],
724					),
725					TemporalKind::InvalidMonth => (
726						"TEMPORAL_006",
727						Some(format!("month '{}' cannot be parsed as a number (expected 1-12)", fragment.text())),
728						Some("ensure the month is a valid number between 1 and 12".to_string()),
729						vec!["valid examples: 01, 03, 12".to_string()],
730					),
731					TemporalKind::InvalidDay => (
732						"TEMPORAL_007",
733						Some(format!("day '{}' cannot be parsed as a number (expected 1-31)", fragment.text())),
734						Some("ensure the day is a valid number between 1 and 31".to_string()),
735						vec!["valid examples: 01, 15, 31".to_string()],
736					),
737					TemporalKind::InvalidHour => (
738						"TEMPORAL_008",
739						Some(format!("hour '{}' cannot be parsed as a number (expected 0-23)", fragment.text())),
740						Some("ensure the hour is a valid number between 0 and 23 (use 24-hour format)".to_string()),
741						vec![
742							"valid examples: 09, 14, 23".to_string(),
743							"hours must be in 24-hour format (00-23)".to_string(),
744						],
745					),
746					TemporalKind::InvalidMinute => (
747						"TEMPORAL_009",
748						Some(format!("minute '{}' cannot be parsed as a number (expected 0-59)", fragment.text())),
749						Some("ensure the minute is a valid number between 0 and 59".to_string()),
750						vec!["valid examples: 00, 30, 59".to_string()],
751					),
752					TemporalKind::InvalidSecond => (
753						"TEMPORAL_010",
754						Some(format!("second '{}' cannot be parsed as a number (expected 0-59)", fragment.text())),
755						Some("ensure the second is a valid number between 0 and 59".to_string()),
756						vec!["valid examples: 00, 30, 59".to_string()],
757					),
758					TemporalKind::InvalidFractionalSeconds => (
759						"TEMPORAL_011",
760						Some(format!("fractional seconds '{}' cannot be parsed as a number", fragment.text())),
761						Some("ensure fractional seconds contain only digits".to_string()),
762						vec!["valid examples: 123, 999999, 000001".to_string()],
763					),
764					TemporalKind::InvalidDateValues => (
765						"TEMPORAL_012",
766						Some(format!("date '{}' represents an invalid calendar date", fragment.text())),
767						Some("ensure the date exists in the calendar (e.g., no February 30)".to_string()),
768						vec![
769							"check month has correct number of days".to_string(),
770							"consider leap years for February 29".to_string(),
771						],
772					),
773					TemporalKind::InvalidTimeValues => (
774						"TEMPORAL_013",
775						Some(format!("time '{}' contains out-of-range values", fragment.text())),
776						Some("ensure hours are 0-23, minutes and seconds are 0-59".to_string()),
777						vec!["use 24-hour format for hours".to_string()],
778					),
779					TemporalKind::InvalidDurationCharacter => (
780						"TEMPORAL_014",
781						Some(format!("character '{}' is not valid in ISO 8601 duration", fragment.text())),
782						Some("use only valid duration units: Y, M, W, D, H, m, S".to_string()),
783						vec![
784							"date part units: Y (years), M (months), W (weeks), D (days)".to_string(),
785							"time part units: H (hours), m (minutes), S (seconds)".to_string(),
786						],
787					),
788					TemporalKind::IncompleteDurationSpecification => (
789						"TEMPORAL_015",
790						Some(format!("number '{}' is missing a unit specifier", fragment.text())),
791						Some("add a unit letter after the number (Y, M, W, D, H, M, or S)".to_string()),
792						vec!["example: P1D (not P1), PT2H (not PT2)".to_string()],
793					),
794					TemporalKind::InvalidUnitInContext { unit, in_time_part } => {
795						let context = if *in_time_part {
796							"time part (after T)"
797						} else {
798							"date part (before T)"
799						};
800						let allowed = if *in_time_part { "H, M, S" } else { "Y, M, W, D" };
801						(
802							"TEMPORAL_016",
803							Some(format!("unit '{}' is not allowed in the {}", unit, context)),
804							Some(format!("use only {} in the {}", allowed, context)),
805							vec![
806								"date part (before T): Y, M, W, D".to_string(),
807								"time part (after T): H, M, S".to_string(),
808							],
809						)
810					}
811					TemporalKind::InvalidDurationComponentValue { unit } => (
812						"TEMPORAL_017",
813						Some(format!("{} value '{}' cannot be parsed as a number", temporal_unit_name(*unit), fragment.text())),
814						Some(format!("ensure the {} value is a valid number", temporal_unit_name(*unit))),
815						vec![format!("valid examples: P1{}, P10{}", unit, unit)],
816					),
817					TemporalKind::UnrecognizedTemporalPattern => (
818						"TEMPORAL_018",
819						Some(format!("value '{}' does not match any temporal format", fragment.text())),
820						Some("use one of the supported formats: date (YYYY-MM-DD), time (HH:MM:SS), datetime (YYYY-MM-DDTHH:MM:SS), or duration (P...)".to_string()),
821						vec![
822							"date: 2024-03-15".to_string(),
823							"time: 14:30:45".to_string(),
824							"datetime: 2024-03-15T14:30:45".to_string(),
825							"duration: P1Y2M3DT4H5M6S".to_string(),
826						],
827					),
828					TemporalKind::EmptyDateComponent => (
829						"TEMPORAL_019",
830						Some(format!("date component '{}' is empty", fragment.text())),
831						Some("ensure all date parts (year, month, day) are provided".to_string()),
832						vec!["date format: YYYY-MM-DD (e.g., 2024-03-15)".to_string()],
833					),
834					TemporalKind::EmptyTimeComponent => (
835						"TEMPORAL_020",
836						Some(format!("time component '{}' is empty", fragment.text())),
837						Some("ensure all time parts (hour, minute, second) are provided".to_string()),
838						vec!["time format: HH:MM:SS (e.g., 14:30:45)".to_string()],
839					),
840					TemporalKind::DuplicateDurationComponent { component } => (
841						"TEMPORAL_021",
842						Some(format!("duration component '{}' appears multiple times", component)),
843						Some("each duration component (Y, M, W, D, H, M, S) can only appear once".to_string()),
844						vec!["valid: P1Y2M3D".to_string(), "invalid: P1Y2Y (duplicate Y)".to_string()],
845					),
846					TemporalKind::OutOfOrderDurationComponent { component } => (
847						"TEMPORAL_022",
848						Some(format!("duration component '{}' appears out of order", component)),
849						Some("duration components must appear in order: Y, M, W, D (before T), then H, M, S (after T)".to_string()),
850						vec![
851							"date part order: Y (years), M (months), W (weeks), D (days)".to_string(),
852							"time part order: H (hours), M (minutes), S (seconds)".to_string(),
853							"valid: P1Y2M3D, PT1H2M3S".to_string(),
854							"invalid: P1D1Y (D before Y), PT1S1H (S before H)".to_string(),
855						],
856					),
857				};
858
859				Diagnostic {
860					code: code.to_string(),
861					statement: None,
862					message,
863					fragment,
864					label,
865					help,
866					notes,
867					column: None,
868					cause: None,
869					operator_chain: None,
870				}
871			}
872
873			TypeError::InvalidUuid4Format { fragment } => {
874				let label = Some(format!("'{}' is not a valid UUID v4", fragment.text()));
875				Diagnostic {
876					code: "UUID_001".to_string(),
877					statement: None,
878					message: "invalid UUID v4 format".to_string(),
879					fragment,
880					label,
881					help: Some("use UUID v4 format (e.g., 550e8400-e29b-41d4-a716-446655440000)".to_string()),
882					notes: vec![
883						"valid: 550e8400-e29b-41d4-a716-446655440000".to_string(),
884						"valid: f47ac10b-58cc-4372-a567-0e02b2c3d479".to_string(),
885						"UUID v4 uses random or pseudo-random numbers".to_string(),
886					],
887					column: None,
888					cause: None,
889					operator_chain: None,
890				}
891			}
892
893			TypeError::InvalidUuid7Format { fragment } => {
894				let label = Some(format!("'{}' is not a valid UUID v7", fragment.text()));
895				Diagnostic {
896					code: "UUID_002".to_string(),
897					statement: None,
898					message: "invalid UUID v7 format".to_string(),
899					fragment,
900					label,
901					help: Some("use UUID v7 format (e.g., 017f22e2-79b0-7cc3-98c4-dc0c0c07398f)".to_string()),
902					notes: vec![
903						"valid: 017f22e2-79b0-7cc3-98c4-dc0c0c07398f".to_string(),
904						"valid: 01854d6e-bd60-7b28-a3c7-6b4ad2c4e2e8".to_string(),
905						"UUID v7 uses timestamp-based generation".to_string(),
906					],
907					column: None,
908					cause: None,
909					operator_chain: None,
910				}
911			}
912
913			TypeError::BlobEncoding {
914				kind,
915				message,
916				fragment,
917			} => {
918				let (code, label, help) = match &kind {
919					BlobEncodingKind::InvalidHex => (
920						"BLOB_001",
921						Some("Invalid hex characters found".to_string()),
922						Some("Hex strings should only contain 0-9, a-f, A-F characters".to_string()),
923					),
924					BlobEncodingKind::InvalidBase64 => (
925						"BLOB_002",
926						Some("Invalid base64 encoding found".to_string()),
927						Some("Base64 strings should only contain A-Z, a-z, 0-9, +, / and = padding".to_string()),
928					),
929					BlobEncodingKind::InvalidBase64Url => (
930						"BLOB_003",
931						Some("Invalid base64url encoding found".to_string()),
932						Some("Base64url strings should only contain A-Z, a-z, 0-9, -, _ characters".to_string()),
933					),
934					BlobEncodingKind::InvalidBase58 => (
935						"BLOB_005",
936						Some("Invalid base58 encoding found".to_string()),
937						Some("Base58 strings should only contain 1-9, A-H, J-N, P-Z, a-k, m-z characters".to_string()),
938					),
939					BlobEncodingKind::InvalidUtf8Sequence { .. } => (
940						"BLOB_004",
941						Some("BLOB contains invalid UTF-8 bytes".to_string()),
942						Some("Use to_utf8_lossy() if you want to replace invalid sequences with replacement characters".to_string()),
943					),
944				};
945
946				Diagnostic {
947					code: code.to_string(),
948					statement: None,
949					message,
950					column: None,
951					fragment,
952					label,
953					help,
954					notes: vec![],
955					cause: None,
956					operator_chain: None,
957				}
958			}
959
960			TypeError::SerdeDeserialize { message } => Diagnostic {
961				code: "SERDE_001".to_string(),
962				statement: None,
963				message: format!("Serde deserialization error: {}", message),
964				column: None,
965				fragment: Fragment::None,
966				label: None,
967				help: Some("Check data format and structure".to_string()),
968				notes: vec![],
969				cause: None,
970				operator_chain: None,
971			},
972
973			TypeError::SerdeSerialize { message } => Diagnostic {
974				code: "SERDE_002".to_string(),
975				statement: None,
976				message: format!("Serde serialization error: {}", message),
977				column: None,
978				fragment: Fragment::None,
979				label: None,
980				help: Some("Check data format and structure".to_string()),
981				notes: vec![],
982				cause: None,
983				operator_chain: None,
984			},
985
986			TypeError::SerdeKeycode { message } => Diagnostic {
987				code: "SERDE_003".to_string(),
988				statement: None,
989				message: format!("Keycode serialization error: {}", message),
990				column: None,
991				fragment: Fragment::None,
992				label: None,
993				help: Some("Check keycode data and format".to_string()),
994				notes: vec![],
995				cause: None,
996				operator_chain: None,
997			},
998
999			TypeError::ArrayConversion { message } => Diagnostic {
1000				code: "CONV_001".to_string(),
1001				statement: None,
1002				message: format!("Array conversion error: {}", message),
1003				column: None,
1004				fragment: Fragment::None,
1005				label: None,
1006				help: Some("Check array size requirements".to_string()),
1007				notes: vec![],
1008				cause: None,
1009				operator_chain: None,
1010			},
1011
1012			TypeError::Utf8Conversion { message } => Diagnostic {
1013				code: "CONV_002".to_string(),
1014				statement: None,
1015				message: format!("UTF-8 conversion error: {}", message),
1016				column: None,
1017				fragment: Fragment::None,
1018				label: None,
1019				help: Some("Check string encoding".to_string()),
1020				notes: vec![],
1021				cause: None,
1022				operator_chain: None,
1023			},
1024
1025			TypeError::IntegerConversion { message } => Diagnostic {
1026				code: "CONV_003".to_string(),
1027				statement: None,
1028				message: format!("Integer conversion error: {}", message),
1029				column: None,
1030				fragment: Fragment::None,
1031				label: None,
1032				help: Some("Check integer range limits".to_string()),
1033				notes: vec![],
1034				cause: None,
1035				operator_chain: None,
1036			},
1037
1038			TypeError::Network { kind, message } => {
1039				let (code, help) = match &kind {
1040					NetworkErrorKind::Connection { .. } => {
1041						("NET_001", Some("Check network connectivity and server status".to_string()))
1042					}
1043					NetworkErrorKind::Engine { .. } => ("NET_002", None),
1044					NetworkErrorKind::Transport { .. } => {
1045						("NET_003", Some("Check network connectivity".to_string()))
1046					}
1047					NetworkErrorKind::Status { .. } => {
1048						("NET_004", Some("Check gRPC service status".to_string()))
1049					}
1050				};
1051
1052				Diagnostic {
1053					code: code.to_string(),
1054					statement: None,
1055					message,
1056					column: None,
1057					fragment: Fragment::None,
1058					label: None,
1059					help,
1060					notes: vec![],
1061					cause: None,
1062					operator_chain: None,
1063				}
1064			}
1065
1066			TypeError::Auth { kind, message } => {
1067				let (code, help) = match &kind {
1068					AuthErrorKind::AuthenticationFailed { .. } => {
1069						("ASVTH_001", Some("Check your credentials and try again".to_string()))
1070					}
1071					AuthErrorKind::AuthorizationDenied { .. } => {
1072						("ASVTH_002", Some("Check your permissions for this resource".to_string()))
1073					}
1074					AuthErrorKind::TokenExpired => {
1075						("ASVTH_003", Some("Refresh your authentication token".to_string()))
1076					}
1077					AuthErrorKind::InvalidToken => {
1078						("ASVTH_004", Some("Provide a valid authentication token".to_string()))
1079					}
1080				};
1081
1082				Diagnostic {
1083					code: code.to_string(),
1084					statement: None,
1085					message,
1086					column: None,
1087					fragment: Fragment::None,
1088					label: None,
1089					help,
1090					notes: vec![],
1091					cause: None,
1092					operator_chain: None,
1093				}
1094			}
1095
1096			TypeError::DictionaryCapacityExceeded {
1097				id_type,
1098				value,
1099				max_value,
1100			} => Diagnostic {
1101				code: "DICT_001".to_string(),
1102				statement: None,
1103				message: format!("dictionary entry ID {} exceeds maximum {} for type {}", value, max_value, id_type),
1104				column: None,
1105				fragment: Fragment::None,
1106				label: Some(format!("{} capacity exceeded", id_type)),
1107				help: Some(
1108					"use a larger ID type (e.g., Uint2 instead of Uint1) when creating the dictionary".to_string(),
1109				),
1110				notes: vec![],
1111				cause: None,
1112				operator_chain: None,
1113			},
1114
1115			TypeError::AssertionFailed {
1116				fragment,
1117				message,
1118				expression,
1119			} => {
1120				let label = expression
1121					.as_ref()
1122					.map(|expr| format!("this expression is false: {}", expr))
1123					.or_else(|| Some("assertion failed".to_string()));
1124
1125				Diagnostic {
1126					code: "ASSERT".to_string(),
1127					statement: None,
1128					message,
1129					fragment,
1130					label,
1131					help: None,
1132					notes: vec![],
1133					column: None,
1134					cause: None,
1135					operator_chain: None,
1136				}
1137			}
1138
1139			TypeError::Function {
1140				kind,
1141				message,
1142				fragment,
1143			} => {
1144				let (code, label, help) = match &kind {
1145					FunctionErrorKind::UnknownFunction => (
1146						"FUNCTION_001",
1147						Some("unknown function".to_string()),
1148						Some("Check the function name and available functions".to_string()),
1149					),
1150					FunctionErrorKind::ArityMismatch { expected, .. } => (
1151						"FUNCTION_002",
1152						Some("wrong number of arguments".to_string()),
1153						Some(format!("Provide exactly {} arguments to function {}", expected, fragment.text())),
1154					),
1155					FunctionErrorKind::TooManyArguments { max_args, .. } => (
1156						"FUNCTION_003",
1157						Some("too many arguments".to_string()),
1158						Some(format!("Provide at most {} arguments to function {}", max_args, fragment.text())),
1159					),
1160					FunctionErrorKind::InvalidArgumentType { expected, .. } => {
1161						let expected_types =
1162							expected.iter().map(|t| format!("{:?}", t)).collect::<Vec<_>>().join(", ");
1163						(
1164							"FUNCTION_004",
1165							Some("invalid argument type".to_string()),
1166							Some(format!("Provide an argument of type: {}", expected_types)),
1167						)
1168					}
1169					FunctionErrorKind::UndefinedArgument { .. } => (
1170						"FUNCTION_005",
1171						Some("none argument".to_string()),
1172						Some("Provide a defined value for this argument".to_string()),
1173					),
1174					FunctionErrorKind::MissingInput => (
1175						"FUNCTION_006",
1176						Some("missing input".to_string()),
1177						Some("Provide input data to the function".to_string()),
1178					),
1179					FunctionErrorKind::ExecutionFailed { .. } => (
1180						"FUNCTION_007",
1181						Some("execution failed".to_string()),
1182						Some("Check function arguments and data".to_string()),
1183					),
1184					FunctionErrorKind::InternalError { .. } => (
1185						"FUNCTION_008",
1186						Some("internal error".to_string()),
1187						Some("This is an internal error - please report this issue".to_string()),
1188					),
1189					FunctionErrorKind::GeneratorNotFound => (
1190						"FUNCTION_009",
1191						Some("unknown generator function".to_string()),
1192						Some("Check the generator function name and ensure it is registered".to_string()),
1193					),
1194				};
1195
1196				Diagnostic {
1197					code: code.to_string(),
1198					statement: None,
1199					message,
1200					column: None,
1201					fragment,
1202					label,
1203					help,
1204					notes: vec![],
1205					cause: None,
1206					operator_chain: None,
1207				}
1208			}
1209
1210			TypeError::Ast {
1211				kind,
1212				message,
1213				fragment,
1214			} => {
1215				let (code, label, help) = match &kind {
1216					AstErrorKind::TokenizeError { .. } => (
1217						"AST_001",
1218						None,
1219						Some("Check syntax and token format".to_string()),
1220					),
1221					AstErrorKind::UnexpectedEof => (
1222						"AST_002",
1223						None,
1224						Some("Complete the statement".to_string()),
1225					),
1226					AstErrorKind::ExpectedIdentifier => (
1227						"AST_003",
1228						Some(format!("found `{}`", fragment.text())),
1229						Some("expected token of type `identifier`".to_string()),
1230					),
1231					AstErrorKind::InvalidColumnProperty => (
1232						"AST_011",
1233						Some(format!("found `{}`", fragment.text())),
1234						Some("Expected one of: auto_increment, dictionary, saturation, default".to_string()),
1235					),
1236					AstErrorKind::InvalidPolicy => (
1237						"AST_004",
1238						Some(format!("found `{}`", fragment.text())),
1239						Some("Expected a valid policy identifier".to_string()),
1240					),
1241					AstErrorKind::UnexpectedToken { expected } => (
1242						"AST_005",
1243						Some(format!("found `{}`", fragment.text())),
1244						Some(format!("Use {} instead", expected)),
1245					),
1246					AstErrorKind::UnsupportedToken => (
1247						"AST_006",
1248						Some(format!("found `{}`", fragment.text())),
1249						Some("This token is not supported in this context".to_string()),
1250					),
1251					AstErrorKind::MultipleExpressionsWithoutBraces => {
1252						let keyword = fragment.text().to_string();
1253						(
1254							"AST_007",
1255							Some("missing `{ … }` around expressions".to_string()),
1256							Some(format!("wrap the expressions in curly braces:\n    {} {{ expr1, expr2, … }}", keyword)),
1257						)
1258					}
1259					AstErrorKind::UnrecognizedType => (
1260						"AST_008",
1261						Some("type not found".to_string()),
1262						None,
1263					),
1264					AstErrorKind::UnsupportedAstNode { .. } => (
1265						"AST_009",
1266						Some("not supported in this context".to_string()),
1267						Some("This syntax is not yet supported or may be invalid in this context".to_string()),
1268					),
1269					AstErrorKind::EmptyPipeline => (
1270						"AST_010",
1271						None,
1272						Some("A query pipeline must contain at least one operation".to_string()),
1273					),
1274				};
1275
1276				Diagnostic {
1277					code: code.to_string(),
1278					statement: None,
1279					message,
1280					column: None,
1281					fragment,
1282					label,
1283					help,
1284					notes: vec![],
1285					cause: None,
1286					operator_chain: None,
1287				}
1288			}
1289
1290			TypeError::Runtime { kind, message } => {
1291				let (code, help) = match &kind {
1292					RuntimeErrorKind::VariableNotFound { name } => (
1293						"RUNTIME_001",
1294						Some(format!("Define the variable using 'let {} = <value>' before using it", name)),
1295					),
1296					RuntimeErrorKind::VariableIsDataframe { name } => (
1297						"RUNTIME_002",
1298						Some(format!(
1299							"Extract a scalar value from the dataframe using '${} | only()', '${} | first()', or '${} | first_or_none()'",
1300							name, name, name
1301						)),
1302					),
1303					RuntimeErrorKind::VariableIsImmutable { .. } => (
1304						"RUNTIME_003",
1305						Some("Use 'let mut $name := value' to declare a mutable variable".to_string()),
1306					),
1307					RuntimeErrorKind::BreakOutsideLoop => (
1308						"RUNTIME_004",
1309						Some("Use BREAK inside a LOOP, WHILE, or FOR block".to_string()),
1310					),
1311					RuntimeErrorKind::ContinueOutsideLoop => (
1312						"RUNTIME_005",
1313						Some("Use CONTINUE inside a LOOP, WHILE, or FOR block".to_string()),
1314					),
1315					RuntimeErrorKind::MaxIterationsExceeded { .. } => (
1316						"RUNTIME_006",
1317						Some("Add a BREAK condition or use WHILE with a terminating condition".to_string()),
1318					),
1319					RuntimeErrorKind::UndefinedFunction { .. } => (
1320						"RUNTIME_007",
1321						Some("Define the function using 'DEF name [] { ... }' before calling it".to_string()),
1322					),
1323					RuntimeErrorKind::FieldNotFound {
1324						variable, available, ..
1325					} => {
1326						let help = if available.is_empty() {
1327							format!("The variable '{}' has no fields", variable)
1328						} else {
1329							format!("Available fields: {}", available.join(", "))
1330						};
1331						("RUNTIME_009", Some(help))
1332					}
1333					RuntimeErrorKind::AppendTargetNotFrame { .. } => (
1334						"RUNTIME_008",
1335						Some("APPEND can only target Frame variables. Use a new variable name or ensure the target was created by APPEND or FROM".to_string()),
1336					),
1337				};
1338
1339				let notes = match &kind {
1340					RuntimeErrorKind::VariableIsImmutable { .. } => {
1341						vec!["Only mutable variables can be reassigned".to_string()]
1342					}
1343					RuntimeErrorKind::VariableIsDataframe { .. } => {
1344						vec![
1345							"Dataframes must be explicitly converted to scalar values before use in expressions".to_string(),
1346							"Use only() for exactly 1 row × 1 column dataframes".to_string(),
1347							"Use first() to take the first value from the first column".to_string(),
1348						]
1349					}
1350					_ => vec![],
1351				};
1352
1353				let fragment = match &kind {
1354					RuntimeErrorKind::UndefinedFunction { name } => Fragment::internal(name.clone()),
1355					_ => Fragment::None,
1356				};
1357
1358				Diagnostic {
1359					code: code.to_string(),
1360					statement: None,
1361					message,
1362					column: None,
1363					fragment,
1364					label: None,
1365					help,
1366					notes,
1367					cause: None,
1368					operator_chain: None,
1369				}
1370			}
1371
1372			TypeError::Procedure { kind, message, fragment } => {
1373				let (code, help, label) = match &kind {
1374					ProcedureErrorKind::UndefinedProcedure { .. } => (
1375						"PROCEDURE_001",
1376						"Check the procedure name and available procedures",
1377						"unknown procedure",
1378					),
1379				};
1380
1381				Diagnostic {
1382					code: code.to_string(),
1383					statement: None,
1384					message,
1385					column: None,
1386					fragment,
1387					label: Some(label.to_string()),
1388					help: Some(help.to_string()),
1389					notes: vec![],
1390					cause: None,
1391					operator_chain: None,
1392				}
1393			}
1394		}
1395	}
1396}