sui/client_ptb/
error.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use miette::{LabeledSpan, Severity, miette};
5use std::fmt;
6use thiserror::Error;
7
8pub type PTBResult<T> = Result<T, PTBError>;
9
10/// Represents the location of a range of text in the PTB source.
11#[derive(Debug, Clone, PartialEq, Eq, Copy)]
12pub struct Span {
13    pub start: usize,
14    pub end: usize,
15}
16
17/// A value that has an associated location in source code.
18pub struct Spanned<T> {
19    pub span: Span,
20    pub value: T,
21}
22
23/// An error with a message, a location in the source code, and an optional help message.
24#[derive(Debug, Clone, Error)]
25#[error("{message}")]
26pub struct PTBError {
27    pub message: String,
28    pub span: Span,
29    pub help: Option<String>,
30    pub severity: Severity,
31}
32
33#[macro_export]
34macro_rules! sp {
35    (_, $value:pat) => {
36        $crate::client_ptb::error::Spanned { value: $value, .. }
37    };
38    ($loc:pat, _) => {
39        $crate::client_ptb::error::Spanned { span: $loc, .. }
40    };
41    ($loc:pat, $value:pat) => {
42        $crate::client_ptb::error::Spanned {
43            span: $loc,
44            value: $value,
45        }
46    };
47}
48
49#[macro_export]
50macro_rules! error {
51    ($l:expr, $($arg:tt)*) => {
52        return Err($crate::err!($l, $($arg)*))
53    };
54    ($l:expr => help: { $($h:expr),* }, $($arg:tt)*) => {
55        return Err($crate::err!($l => help: { $($h),* }, $($arg)*))
56    };
57}
58
59#[macro_export]
60macro_rules! err {
61    ($l:expr, $($arg:tt)*) => {
62        $crate::client_ptb::error::PTBError {
63            message: format!($($arg)*),
64            span: $l,
65            help: None,
66            severity: miette::Severity::Error,
67        }
68    };
69    ($l:expr => help: { $($h:expr),* }, $($arg:tt)*) => {
70        $crate::client_ptb::error::PTBError {
71            message: format!($($arg)*),
72            span: $l,
73            help: Some(format!($($h),*)),
74            severity: miette::Severity::Error,
75        }
76    };
77}
78
79pub use sp;
80
81impl PTBError {
82    /// Add a help message to an error.
83    pub fn with_help(self, help: String) -> Self {
84        let PTBError {
85            message,
86            span,
87            help: _,
88            severity,
89        } = self;
90        PTBError {
91            message,
92            span,
93            help: Some(help),
94            severity,
95        }
96    }
97}
98
99impl Span {
100    /// Wrap a value with a span.
101    pub fn wrap<T>(self, value: T) -> Spanned<T> {
102        Spanned { span: self, value }
103    }
104
105    /// Widen the span to include another span. The resulting span will start at the minimum of the
106    /// two start positions and end at the maximum of the two end positions.
107    pub fn widen(self, other: Span) -> Span {
108        Span {
109            start: self.start.min(other.start),
110            end: self.end.max(other.end),
111        }
112    }
113
114    /// Widen the span to include another if it is Some, otherwise return the original span.
115    pub fn widen_opt(self, other: Option<Span>) -> Span {
116        other.map_or(self, |other| self.widen(other))
117    }
118
119    /// Create a span that points to the end of the file/string contents.
120    pub fn eof_span() -> Span {
121        Self {
122            start: usize::MAX,
123            end: usize::MAX,
124        }
125    }
126}
127
128impl<T> Spanned<T> {
129    /// Apply a function `f` to the underlying value, returning a new `Spanned` with the same span.
130    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
131        Spanned {
132            span: self.span,
133            value: f(self.value),
134        }
135    }
136
137    /// Widen the span to include another span. The resulting span will start at the minimum of the
138    /// two start positions and end at the maximum of the two end positions.
139    pub fn widen<U>(self, other: Spanned<U>) -> Spanned<T> {
140        self.widen_span(other.span)
141    }
142
143    /// Widen the span to include another span. The resulting span will start at the minimum of the
144    /// two start positions and end at the maximum of the two end positions.
145    pub fn widen_span(self, other: Span) -> Spanned<T> {
146        Spanned {
147            span: self.span.widen(other),
148            value: self.value,
149        }
150    }
151}
152
153impl<T: fmt::Debug> fmt::Debug for Spanned<T> {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        f.debug_struct("Spanned")
156            .field("span", &self.span)
157            .field("value", &self.value)
158            .finish()
159    }
160}
161
162impl<T: Clone> Clone for Spanned<T> {
163    fn clone(&self) -> Self {
164        Spanned {
165            span: self.span,
166            value: self.value.clone(),
167        }
168    }
169}
170
171impl<T: Copy> Copy for Spanned<T> {}
172
173fn build_error_report(file_string: &str, error: PTBError) -> miette::Report {
174    let PTBError {
175        span,
176        message,
177        help,
178        severity,
179    } = error;
180    let clamp = |x: usize| x.min(file_string.len() - 1);
181    let label = LabeledSpan::at(clamp(span.start)..clamp(span.end), message.clone());
182    let error_string = match severity {
183        Severity::Advice => "Advice found when processing PTB".to_string(),
184        Severity::Warning => "Warning when processing PTB".to_string(),
185        Severity::Error => "Error when processing PTB".to_string(),
186    };
187    match help {
188        Some(help_msg) => miette!(labels = vec![label], help = help_msg, "{}", error_string),
189        None => miette!(
190            labels = vec![label],
191            severity = severity,
192            "{}",
193            error_string
194        ),
195    }
196    .with_source_code(file_string.to_string())
197}
198
199pub fn build_error_reports(source_string: &str, errors: Vec<PTBError>) -> Vec<miette::Report> {
200    errors
201        .into_iter()
202        .map(|e| build_error_report(source_string, e))
203        .collect()
204}