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{ line: 4, column: 15 }
35/// });
36/// let diagnostic = Diagnostic::new_error("Something went wrong", span)
37///     .with_help("Have you tried turning it on and off again?", None);
38/// ```
39#[derive(Debug, Clone)]
40pub struct Diagnostic {
41    pub kind: DiagnosticKind,
42    pub message: String,
43    pub span: Option<Span>,
44    pub notes: Vec<DiagnosticNote>,
45    pub helps: Vec<DiagnosticHelp>,
46}
47
48/// A note enriches a [`Diagnostic`] with extra information, possibly referring
49/// to different locations in the original SQL query, that helps contextualize
50/// the error and helps the end user understand why it occurred.
51///
52/// Example:
53/// SELECT id, name FROM users GROUP BY id
54/// Note:      ^^^^ 'name' is not in the GROUP BY clause
55#[derive(Debug, Clone)]
56pub struct DiagnosticNote {
57    pub message: String,
58    pub span: Option<Span>,
59}
60
61/// A "help" enriches a [`Diagnostic`] with extra information, possibly
62/// referring to different locations in the original SQL query, that helps the
63/// user understand how they might fix the error or warning.
64///
65/// Example:
66/// SELECT id, name FROM users GROUP BY id
67/// Help: Add 'name' here                 ^^^^
68#[derive(Debug, Clone)]
69pub struct DiagnosticHelp {
70    pub message: String,
71    pub span: Option<Span>,
72}
73
74/// A [`Diagnostic`] can either be a hard error that prevents the query from
75/// being planned and executed, or a warning that indicates potential issues,
76/// performance problems, or causes for unexpected results, but is non-fatal.
77/// This enum expresses these two possibilities.
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum DiagnosticKind {
80    Error,
81    Warning,
82}
83
84impl Diagnostic {
85    /// Creates a new [`Diagnostic`] for a fatal error that prevents the SQL
86    /// query from being planned and executed. Optionally takes in a [`Span`] to
87    /// describe the location in the source code that caused the error, should
88    /// be provided when available.
89    pub fn new_error(message: impl Into<String>, span: Option<Span>) -> Self {
90        Self {
91            kind: DiagnosticKind::Error,
92            message: message.into(),
93            span,
94            notes: Vec::new(),
95            helps: Vec::new(),
96        }
97    }
98
99    /// Creates a new [`Diagnostic`] for a NON-fatal warning, such as a
100    /// performance problem, or possible cause for undesired results. Optionally
101    /// takes in a [`Span`] to describe the location in the source code that
102    /// caused the error, should be provided when available.
103    pub fn new_warning(message: impl Into<String>, span: Option<Span>) -> Self {
104        Self {
105            kind: DiagnosticKind::Warning,
106            message: message.into(),
107            span,
108            notes: Vec::new(),
109            helps: Vec::new(),
110        }
111    }
112
113    /// Adds a "note" to the [`Diagnostic`], which can have zero or many. A "note"
114    /// helps contextualize the error and helps the end user understand why it
115    /// occurred. It can refer to an arbitrary location in the SQL query, or to
116    /// no location.
117    pub fn add_note(&mut self, message: impl Into<String>, span: Option<Span>) {
118        self.notes.push(DiagnosticNote {
119            message: message.into(),
120            span,
121        });
122    }
123
124    /// Adds a "help" to the [`Diagnostic`], which can have zero or many. A
125    /// "help" helps the user understand how they might fix the error or
126    /// warning. It can refer to an arbitrary location in the SQL query, or to
127    /// no location.
128    pub fn add_help(&mut self, message: impl Into<String>, span: Option<Span>) {
129        self.helps.push(DiagnosticHelp {
130            message: message.into(),
131            span,
132        });
133    }
134
135    /// Like [`Diagnostic::add_note`], but returns `self` to allow chaining.
136    pub fn with_note(mut self, message: impl Into<String>, span: Option<Span>) -> Self {
137        self.add_note(message.into(), span);
138        self
139    }
140
141    /// Like [`Diagnostic::add_help`], but returns `self` to allow chaining.
142    pub fn with_help(mut self, message: impl Into<String>, span: Option<Span>) -> Self {
143        self.add_help(message.into(), span);
144        self
145    }
146}