1use crate::{
9 capability::CapabilityName,
10 datum::Datum,
11 env::Cx,
12 error::{Diagnostic, Result, Severity},
13 id::Symbol,
14 value::Value,
15};
16
17#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct HintMetadata {
25 pub kind: Symbol,
27 pub title: String,
29 pub detail: Option<String>,
31 pub tags: Vec<Symbol>,
33 pub arguments: Vec<Symbol>,
35 pub capabilities: Vec<CapabilityName>,
37 pub codec_forms: Vec<Symbol>,
39 pub examples: Vec<String>,
41}
42
43impl HintMetadata {
44 pub fn new(kind: Symbol, title: impl Into<String>) -> Self {
46 Self {
47 kind,
48 title: title.into(),
49 detail: None,
50 tags: Vec::new(),
51 arguments: Vec::new(),
52 capabilities: Vec::new(),
53 codec_forms: Vec::new(),
54 examples: Vec::new(),
55 }
56 }
57
58 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
60 self.detail = Some(detail.into());
61 self
62 }
63
64 pub fn with_tag(mut self, tag: Symbol) -> Self {
66 self.tags.push(tag);
67 self
68 }
69
70 pub fn with_argument(mut self, argument: Symbol) -> Self {
72 self.arguments.push(argument);
73 self
74 }
75
76 pub fn with_capability(mut self, capability: CapabilityName) -> Self {
78 self.capabilities.push(capability);
79 self
80 }
81
82 pub fn with_codec_form(mut self, form: Symbol) -> Self {
84 self.codec_forms.push(form);
85 self
86 }
87
88 pub fn with_example(mut self, example: impl Into<String>) -> Self {
90 self.examples.push(example.into());
91 self
92 }
93
94 pub fn attach_to(self, mut diagnostic: Diagnostic) -> Diagnostic {
96 diagnostic.related.push(self.to_diagnostic());
97 diagnostic
98 }
99
100 pub fn to_diagnostic(&self) -> Diagnostic {
102 let mut diagnostic = Diagnostic::info(self.title.clone());
103 diagnostic.code = Some(hint_metadata_code());
104 diagnostic
105 .related
106 .push(hint_field("kind", self.kind.to_string()));
107 if let Some(detail) = &self.detail {
108 diagnostic
109 .related
110 .push(hint_field("detail", detail.clone()));
111 }
112 for tag in &self.tags {
113 diagnostic.related.push(hint_field("tag", tag.to_string()));
114 }
115 for argument in &self.arguments {
116 diagnostic
117 .related
118 .push(hint_field("argument", argument.to_string()));
119 }
120 for capability in &self.capabilities {
121 diagnostic
122 .related
123 .push(hint_field("capability", capability.as_str().to_owned()));
124 }
125 for form in &self.codec_forms {
126 diagnostic
127 .related
128 .push(hint_field("codec-form", form.to_string()));
129 }
130 for example in &self.examples {
131 diagnostic
132 .related
133 .push(hint_field("example", example.clone()));
134 }
135 diagnostic
136 }
137
138 pub fn from_diagnostic(diagnostic: &Diagnostic) -> Option<Self> {
140 if !Self::is_hint_diagnostic(diagnostic) {
141 return None;
142 }
143
144 let mut kind = None;
145 let mut detail = None;
146 let mut tags = Vec::new();
147 let mut arguments = Vec::new();
148 let mut capabilities = Vec::new();
149 let mut codec_forms = Vec::new();
150 let mut examples = Vec::new();
151
152 for field in &diagnostic.related {
153 let Some(name) = hint_field_name(field) else {
154 continue;
155 };
156 match name {
157 "kind" => kind = Some(parse_symbol(&field.message)),
158 "detail" => detail = Some(field.message.clone()),
159 "tag" => tags.push(parse_symbol(&field.message)),
160 "argument" => arguments.push(parse_symbol(&field.message)),
161 "capability" => capabilities.push(CapabilityName::new(field.message.clone())),
162 "codec-form" => codec_forms.push(parse_symbol(&field.message)),
163 "example" => examples.push(field.message.clone()),
164 _ => {}
165 }
166 }
167
168 Some(Self {
169 kind: kind?,
170 title: diagnostic.message.clone(),
171 detail,
172 tags,
173 arguments,
174 capabilities,
175 codec_forms,
176 examples,
177 })
178 }
179
180 pub fn is_hint_diagnostic(diagnostic: &Diagnostic) -> bool {
182 let expected = hint_metadata_code();
183 diagnostic.code.as_ref() == Some(&expected)
184 }
185
186 pub fn collect_from_diagnostic(diagnostic: &Diagnostic) -> Vec<Self> {
188 diagnostic
189 .related
190 .iter()
191 .filter_map(Self::from_diagnostic)
192 .collect()
193 }
194
195 pub fn radar_text(&self) -> String {
197 let mut parts = vec![self.kind.to_string(), self.title.clone()];
198 if let Some(detail) = &self.detail {
199 parts.push(detail.clone());
200 }
201 parts.extend(self.tags.iter().map(ToString::to_string));
202 parts.extend(self.arguments.iter().map(ToString::to_string));
203 parts.extend(
204 self.capabilities
205 .iter()
206 .map(|capability| capability.as_str().to_owned()),
207 );
208 parts.extend(self.codec_forms.iter().map(ToString::to_string));
209 parts.extend(self.examples.iter().cloned());
210 parts.join(" ")
211 }
212
213 pub fn as_value(&self, cx: &mut Cx) -> Result<Value> {
215 let tags = symbol_list_value(cx, &self.tags)?;
216 let arguments = symbol_list_value(cx, &self.arguments)?;
217 let capabilities = cx.factory().list(
218 self.capabilities
219 .iter()
220 .map(|capability| cx.factory().symbol(capability.as_symbol()))
221 .collect::<Result<Vec<_>>>()?,
222 )?;
223 let codec_forms = symbol_list_value(cx, &self.codec_forms)?;
224 let examples = cx.factory().list(
225 self.examples
226 .iter()
227 .map(|example| cx.factory().string(example.clone()))
228 .collect::<Result<Vec<_>>>()?,
229 )?;
230 let detail = match &self.detail {
231 Some(detail) => cx.factory().string(detail.clone())?,
232 None => cx.factory().nil()?,
233 };
234 cx.factory().table(vec![
235 (Symbol::new("kind"), cx.factory().symbol(self.kind.clone())?),
236 (
237 Symbol::new("title"),
238 cx.factory().string(self.title.clone())?,
239 ),
240 (Symbol::new("detail"), detail),
241 (Symbol::new("tags"), tags),
242 (Symbol::new("arguments"), arguments),
243 (Symbol::new("capabilities"), capabilities),
244 (Symbol::new("codec-forms"), codec_forms),
245 (Symbol::new("examples"), examples),
246 (
247 Symbol::new("radar-text"),
248 cx.factory().string(self.radar_text())?,
249 ),
250 ])
251 }
252
253 pub fn as_datum(&self) -> Datum {
255 let mut fields = vec![
256 (Symbol::new("kind"), Datum::Symbol(self.kind.clone())),
257 (Symbol::new("title"), Datum::String(self.title.clone())),
258 (
259 Symbol::new("tags"),
260 Datum::Vector(self.tags.iter().cloned().map(Datum::Symbol).collect()),
261 ),
262 (
263 Symbol::new("arguments"),
264 Datum::Vector(self.arguments.iter().cloned().map(Datum::Symbol).collect()),
265 ),
266 (
267 Symbol::new("capabilities"),
268 Datum::Vector(
269 self.capabilities
270 .iter()
271 .map(|capability| Datum::Symbol(capability.as_symbol()))
272 .collect(),
273 ),
274 ),
275 (
276 Symbol::new("codec-forms"),
277 Datum::Vector(
278 self.codec_forms
279 .iter()
280 .cloned()
281 .map(Datum::Symbol)
282 .collect(),
283 ),
284 ),
285 (
286 Symbol::new("examples"),
287 Datum::Vector(self.examples.iter().cloned().map(Datum::String).collect()),
288 ),
289 (Symbol::new("radar-text"), Datum::String(self.radar_text())),
290 ];
291 if let Some(detail) = &self.detail {
292 fields.push((Symbol::new("detail"), Datum::String(detail.clone())));
293 }
294 Datum::Node {
295 tag: Symbol::qualified("core", "HintMetadata"),
296 fields,
297 }
298 }
299}
300
301pub(crate) fn diagnostic_hints_value(cx: &mut Cx, diagnostic: &Diagnostic) -> Result<Value> {
303 let values = HintMetadata::collect_from_diagnostic(diagnostic)
304 .into_iter()
305 .map(|hint| hint.as_value(cx))
306 .collect::<Result<Vec<_>>>()?;
307 cx.factory().list(values)
308}
309
310fn hint_metadata_code() -> Symbol {
311 Symbol::qualified("hint", "metadata")
312}
313
314fn hint_field(name: &'static str, value: String) -> Diagnostic {
315 let mut field = Diagnostic::info(value);
316 field.severity = Severity::Note;
317 field.code = Some(Symbol::qualified("hint-field", name));
318 field
319}
320
321fn hint_field_name(diagnostic: &Diagnostic) -> Option<&str> {
322 let code = diagnostic.code.as_ref()?;
323 if code.namespace.as_deref() != Some("hint-field") {
324 return None;
325 }
326 Some(code.name.as_ref())
327}
328
329fn parse_symbol(value: &str) -> Symbol {
330 match value.split_once('/') {
331 Some((namespace, name)) => Symbol::qualified(namespace.to_owned(), name.to_owned()),
332 None => Symbol::new(value.to_owned()),
333 }
334}
335
336fn symbol_list_value(cx: &mut Cx, symbols: &[Symbol]) -> Result<Value> {
337 let values = symbols
338 .iter()
339 .cloned()
340 .map(|symbol| cx.factory().symbol(symbol))
341 .collect::<Result<Vec<_>>>()?;
342 cx.factory().list(values)
343}