1use std::borrow::Cow;
2use std::fmt;
3
4use owo_colors::OwoColorize;
5
6pub trait Hint {
12 fn hints(&self) -> Hints<'_> {
14 Hints::none()
15 }
16}
17
18pub struct Hints<'a>(Vec<Cow<'a, str>>);
22
23impl Hints<'_> {
24 pub fn none() -> Self {
26 Self(Vec::new())
27 }
28
29 pub fn push(&mut self, hint: String) {
31 self.0.push(Cow::Owned(hint));
32 }
33
34 pub fn into_owned(self) -> Hints<'static> {
36 Hints(
37 self.0
38 .into_iter()
39 .map(|cow| Cow::Owned(cow.into_owned()))
40 .collect(),
41 )
42 }
43
44 pub fn is_empty(&self) -> bool {
46 self.0.is_empty()
47 }
48
49 pub fn extend(&mut self, other: Hints<'_>) {
51 for hint in other.0 {
52 let hint = Cow::Owned(hint.into_owned());
53 if !self.0.iter().any(|existing| existing == &hint) {
54 self.0.push(hint);
55 }
56 }
57 }
58}
59
60pub struct ErrorWithHints<'a, E> {
65 error: E,
66 hints: Hints<'a>,
67}
68
69impl<'a, E> ErrorWithHints<'a, E> {
70 pub fn new(error: E, hints: Hints<'a>) -> Self {
72 Self { error, hints }
73 }
74}
75
76impl<E: fmt::Display> fmt::Display for ErrorWithHints<'_, E> {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 write!(f, "{}", self.error)?;
79 if !self.hints.is_empty() {
80 writeln!(f)?;
81 write!(f, "{}", self.hints)?;
82 }
83 Ok(())
84 }
85}
86
87impl<'a> From<&'a str> for Hints<'a> {
88 fn from(hint: &'a str) -> Self {
89 Self(vec![Cow::Borrowed(hint)])
90 }
91}
92
93impl From<String> for Hints<'_> {
94 fn from(hint: String) -> Self {
95 Self(vec![Cow::Owned(hint)])
96 }
97}
98
99impl FromIterator<String> for Hints<'_> {
100 fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> Self {
101 Self(iter.into_iter().map(Cow::Owned).collect())
102 }
103}
104
105impl fmt::Display for Hints<'_> {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 for hint in &self.0 {
108 write!(f, "\n{HintPrefix} {hint}")?;
109 }
110 Ok(())
111 }
112}
113
114impl<'a> IntoIterator for Hints<'a> {
115 type Item = Cow<'a, str>;
116 type IntoIter = std::vec::IntoIter<Cow<'a, str>>;
117
118 fn into_iter(self) -> Self::IntoIter {
119 self.0.into_iter()
120 }
121}
122
123pub struct HintPrefix;
125
126impl fmt::Display for HintPrefix {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 write!(f, "{}{}", "hint".bold().cyan(), ":".bold())
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::{ErrorWithHints, HintPrefix, Hints};
135
136 #[test]
137 fn extend_deduplicates_matching_hints() {
138 let mut hints = Hints::from("same");
139 hints.extend(Hints::from("same"));
140 hints.extend(Hints::from("other"));
141
142 let hints = hints
143 .into_iter()
144 .map(std::borrow::Cow::into_owned)
145 .collect::<Vec<_>>();
146 assert_eq!(hints, vec!["same".to_string(), "other".to_string()]);
147 }
148
149 #[test]
150 fn error_with_hints_separates_hints_from_error() {
151 assert_eq!(
152 ErrorWithHints::new("error", Hints::from("fix it")).to_string(),
153 format!("error\n\n{HintPrefix} fix it")
154 );
155 assert_eq!(
156 ErrorWithHints::new("error", Hints::none()).to_string(),
157 "error"
158 );
159 }
160}