datafusion_common/
diagnostic.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::Span;
19
20/// Additional contextual information intended for end users, to help them
21/// understand what went wrong by providing human-readable messages, and
22/// locations in the source query that relate to the error in some way.
23///
24/// You can think of a single [`Diagnostic`] as a single "block" of output from
25/// rustc. i.e. either an error or a warning, optionally with some notes and
26/// help messages.
27///
28/// Example:
29///
30/// ```rust
31/// # use datafusion_common::{Location, Span, Diagnostic};
32/// let span = Some(Span {
33///     start: Location { line: 2, column: 1 },
34///     end: Location {
35///         line: 4,
36///         column: 15,
37///     },
38/// });
39/// let diagnostic = Diagnostic::new_error("Something went wrong", span)
40///     .with_help("Have you tried turning it on and off again?", None);
41/// ```
42#[derive(Debug, Clone)]
43pub struct Diagnostic {
44    pub kind: DiagnosticKind,
45    pub message: String,
46    pub span: Option<Span>,
47    pub notes: Vec<DiagnosticNote>,
48    pub helps: Vec<DiagnosticHelp>,
49}
50
51/// A note enriches a [`Diagnostic`] with extra information, possibly referring
52/// to different locations in the original SQL query, that helps contextualize
53/// the error and helps the end user understand why it occurred.
54///
55/// Example:
56/// SELECT id, name FROM users GROUP BY id
57/// Note:      ^^^^ 'name' is not in the GROUP BY clause
58#[derive(Debug, Clone)]
59pub struct DiagnosticNote {
60    pub message: String,
61    pub span: Option<Span>,
62}
63
64/// A "help" enriches a [`Diagnostic`] with extra information, possibly
65/// referring to different locations in the original SQL query, that helps the
66/// user understand how they might fix the error or warning.
67///
68/// Example:
69/// SELECT id, name FROM users GROUP BY id
70/// Help: Add 'name' here                 ^^^^
71#[derive(Debug, Clone)]
72pub struct DiagnosticHelp {
73    pub message: String,
74    pub span: Option<Span>,
75}
76
77/// A [`Diagnostic`] can either be a hard error that prevents the query from
78/// being planned and executed, or a warning that indicates potential issues,
79/// performance problems, or causes for unexpected results, but is non-fatal.
80/// This enum expresses these two possibilities.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum DiagnosticKind {
83    Error,
84    Warning,
85}
86
87impl Diagnostic {
88    /// Creates a new [`Diagnostic`] for a fatal error that prevents the SQL
89    /// query from being planned and executed. Optionally takes in a [`Span`] to
90    /// describe the location in the source code that caused the error, should
91    /// be provided when available.
92    pub fn new_error(message: impl Into<String>, span: Option<Span>) -> Self {
93        Self {
94            kind: DiagnosticKind::Error,
95            message: message.into(),
96            span,
97            notes: Vec::new(),
98            helps: Vec::new(),
99        }
100    }
101
102    /// Creates a new [`Diagnostic`] for a NON-fatal warning, such as a
103    /// performance problem, or possible cause for undesired results. Optionally
104    /// takes in a [`Span`] to describe the location in the source code that
105    /// caused the error, should be provided when available.
106    pub fn new_warning(message: impl Into<String>, span: Option<Span>) -> Self {
107        Self {
108            kind: DiagnosticKind::Warning,
109            message: message.into(),
110            span,
111            notes: Vec::new(),
112            helps: Vec::new(),
113        }
114    }
115
116    /// Adds a "note" to the [`Diagnostic`], which can have zero or many. A "note"
117    /// helps contextualize the error and helps the end user understand why it
118    /// occurred. It can refer to an arbitrary location in the SQL query, or to
119    /// no location.
120    pub fn add_note(&mut self, message: impl Into<String>, span: Option<Span>) {
121        self.notes.push(DiagnosticNote {
122            message: message.into(),
123            span,
124        });
125    }
126
127    /// Adds a "help" to the [`Diagnostic`], which can have zero or many. A
128    /// "help" helps the user understand how they might fix the error or
129    /// warning. It can refer to an arbitrary location in the SQL query, or to
130    /// no location.
131    pub fn add_help(&mut self, message: impl Into<String>, span: Option<Span>) {
132        self.helps.push(DiagnosticHelp {
133            message: message.into(),
134            span,
135        });
136    }
137
138    /// Like [`Diagnostic::add_note`], but returns `self` to allow chaining.
139    pub fn with_note(mut self, message: impl Into<String>, span: Option<Span>) -> Self {
140        self.add_note(message.into(), span);
141        self
142    }
143
144    /// Like [`Diagnostic::add_help`], but returns `self` to allow chaining.
145    pub fn with_help(mut self, message: impl Into<String>, span: Option<Span>) -> Self {
146        self.add_help(message.into(), span);
147        self
148    }
149}