Skip to main content

nautilus_schema/
diagnostic.rs

1//! Diagnostic types for the analysis API.
2//!
3//! This module defines a stable public contract for errors and warnings
4//! that tools (LSP servers, CLI validators, etc.) can consume without
5//! depending on the internal [`SchemaError`] representation.
6
7use crate::error::SchemaError;
8use crate::span::Span;
9
10/// Severity level of a diagnostic.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Severity {
13    /// A hard error that prevents code generation or execution.
14    Error,
15    /// A warning that should be addressed but doesn't block compilation.
16    Warning,
17}
18
19/// A single diagnostic message with a source location.
20///
21/// Spans use byte offsets.  Convert to line/column with
22/// [`Span::to_positions`] when needed for display.
23#[derive(Debug, Clone, PartialEq)]
24pub struct Diagnostic {
25    /// Severity of this diagnostic.
26    pub severity: Severity,
27    /// Human-readable message.
28    pub message: String,
29    /// Byte-offset span in the source text.
30    pub span: Span,
31}
32
33impl Diagnostic {
34    /// Create an error-level diagnostic.
35    pub fn error(message: impl Into<String>, span: Span) -> Self {
36        Self {
37            severity: Severity::Error,
38            message: message.into(),
39            span,
40        }
41    }
42
43    /// Create a warning-level diagnostic.
44    pub fn warning(message: impl Into<String>, span: Span) -> Self {
45        Self {
46            severity: Severity::Warning,
47            message: message.into(),
48            span,
49        }
50    }
51}
52
53impl From<SchemaError> for Diagnostic {
54    fn from(err: SchemaError) -> Self {
55        let span = err.span().unwrap_or(Span::single(0));
56        let message = err.to_string();
57        match err {
58            SchemaError::Warning(_, _) => Diagnostic::warning(message, span),
59            _ => Diagnostic::error(message, span),
60        }
61    }
62}
63
64impl From<&SchemaError> for Diagnostic {
65    fn from(err: &SchemaError) -> Self {
66        let span = err.span().unwrap_or(Span::single(0));
67        let message = err.to_string();
68        match err {
69            SchemaError::Warning(_, _) => Diagnostic::warning(message, span),
70            _ => Diagnostic::error(message, span),
71        }
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn from_schema_error_with_span() {
81        let span = Span::new(10, 20);
82        let err = SchemaError::Validation("field required".to_string(), span);
83        let diag = Diagnostic::from(&err);
84        assert_eq!(diag.severity, Severity::Error);
85        assert_eq!(diag.span, span);
86        assert!(diag.message.contains("Validation error"));
87    }
88
89    #[test]
90    fn from_schema_error_without_span() {
91        let err = SchemaError::Other("generic".to_string());
92        let diag = Diagnostic::from(err);
93        assert_eq!(diag.span, Span::single(0));
94    }
95}