1use 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 }
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}