Skip to main content

yog_cli/serve/
error.rs

1//! What a request got wrong, typed: what to say, and each problem with
2//! where it is — a parameter's path, a file's line and column — so a tool
3//! shows it without reading it out of a string.
4
5use yog_data::Schema;
6
7/// The error a request is answered with.
8#[derive(Debug, Clone, Default, PartialEq, Schema)]
9pub struct ServiceError {
10    pub message: String,
11    /// Every problem, each with its code; at least one.
12    #[data(default)]
13    pub problems: Vec<Problem>,
14    /// The file the request is about, when it's about one.
15    pub file: Option<String>,
16}
17
18/// One problem: what it is, what's wrong, and where.
19#[derive(Debug, Clone, Default, PartialEq, Schema)]
20pub struct Problem {
21    /// What it is (`yog_ids::problem_code`).
22    pub code: String,
23    pub message: String,
24    /// The value it's about: a parameter, a field of one (`args.nodes[2].icon`).
25    #[data(default)]
26    pub path: String,
27    /// The line and column of the text it's in, when it's about text.
28    pub line: Option<u32>,
29    pub col: Option<u32>,
30}
31
32impl ServiceError {
33    /// One problem of kind `code`.
34    pub fn of(code: &str, message: impl Into<String>) -> Self {
35        let message = message.into();
36        ServiceError {
37            problems: vec![Problem {
38                code: code.to_string(),
39                message: message.clone(),
40                ..Problem::default()
41            }],
42            message,
43            file: None,
44        }
45    }
46
47    /// `message` of kind `code`, about the text at `line`:`col`.
48    pub fn at(code: &str, message: impl Into<String>, line: u32, col: Option<u32>) -> Self {
49        let mut e = Self::of(code, message);
50        e.problems[0].line = Some(line);
51        e.problems[0].col = col;
52        e
53    }
54
55    /// Text that doesn't parse.
56    pub fn syntax(e: &yog_data::SyntaxError) -> Self {
57        Self::at(
58            yog_ids::problem_code::SYNTAX,
59            e.message.clone(),
60            e.at.line,
61            Some(e.at.col),
62        )
63    }
64
65    /// What a request names isn't there.
66    pub fn not_found(message: impl Into<String>) -> Self {
67        Self::of(yog_ids::problem_code::NOT_FOUND, message)
68    }
69
70    /// Parameter `name` that doesn't read: each of its problems, at its path.
71    pub fn param(name: &str, problems: Vec<yog_data::schema::Problem>) -> Self {
72        let problems: Vec<Problem> = problems
73            .into_iter()
74            .map(|p| Problem {
75                code: p.code.to_string(),
76                path: if p.path.is_root() {
77                    name.to_string()
78                } else {
79                    format!("{name}.{}", p.path)
80                },
81                message: p.message,
82                ..Problem::default()
83            })
84            .collect();
85        let message = match problems.as_slice() {
86            [one] => format!("`{}`: {}", one.path, one.message),
87            _ => format!("`{name}` has {} problems", problems.len()),
88        };
89        ServiceError {
90            message,
91            problems,
92            file: None,
93        }
94    }
95}
96
97impl From<String> for ServiceError {
98    fn from(message: String) -> Self {
99        Self::of(yog_ids::problem_code::FAILED, message)
100    }
101}
102
103impl From<&str> for ServiceError {
104    fn from(message: &str) -> Self {
105        message.to_string().into()
106    }
107}