ferrous_actions/cargo_hooks/
annotation.rs1use super::Hook;
2use crate::actions::core::AnnotationLevel;
3use crate::actions::exec::Command;
4use crate::warning;
5use async_trait::async_trait;
6use cargo_metadata::diagnostic::{DiagnosticLevel, DiagnosticSpan};
7use std::borrow::Cow;
8
9#[derive(Default)]
10pub struct Annotation {
11 subcommand: String,
12}
13
14impl Annotation {
15 pub fn new(subcommand: &str) -> Annotation {
16 Annotation {
17 subcommand: subcommand.to_string(),
18 }
19 }
20
21 fn process_json_record(cargo_subcommand: &str, line: &str) {
22 use crate::actions::core::Annotation;
23 use crate::node::path::Path;
24 use cargo_metadata::Message;
25
26 let line = line.trim();
28 if line.is_empty() {
29 return;
30 }
31
32 let metadata: Message = match serde_json::from_str(line) {
33 Ok(metadata) => metadata,
34 Err(e) => {
35 warning!("Unable to cargo output line as JSON metadata record: {}", e);
36 return;
37 }
38 };
39 if let Message::CompilerMessage(compiler_message) = metadata {
40 let diagnostic = &compiler_message.message;
41 let level = Self::annotation_level(diagnostic.level);
42 let mut annotation = if let Some(rendered) = &diagnostic.rendered {
43 let mut annotation = Annotation::from(rendered.as_str());
44 annotation.title(&format!("cargo-{}: {}", cargo_subcommand, diagnostic.message));
45 annotation
46 } else {
47 let mut annotation = Annotation::from(diagnostic.message.as_str());
48 annotation.title(&format!("cargo-{}", cargo_subcommand));
49 annotation
50 };
51 if let Some(span) = Self::get_primary_span(&diagnostic.spans) {
52 let file_name = Path::from(&span.file_name);
53 annotation
54 .file(&file_name)
55 .start_line(span.line_start)
56 .end_line(span.line_end)
57 .start_column(span.column_start)
58 .end_column(span.column_end);
59 }
60 annotation.output(level);
61 }
62 }
63
64 fn annotation_level(level: DiagnosticLevel) -> AnnotationLevel {
65 #[allow(clippy::match_same_arms)]
66 match level {
67 DiagnosticLevel::Ice | DiagnosticLevel::Error => AnnotationLevel::Error,
68 DiagnosticLevel::Warning => AnnotationLevel::Warning,
69 DiagnosticLevel::FailureNote | DiagnosticLevel::Note | DiagnosticLevel::Help => AnnotationLevel::Notice,
70 _ => AnnotationLevel::Warning,
71 }
72 }
73
74 fn get_primary_span(spans: &[DiagnosticSpan]) -> Option<&DiagnosticSpan> {
75 spans.iter().find(|s| s.is_primary)
76 }
77}
78
79#[async_trait(?Send)]
80impl Hook for Annotation {
81 fn additional_cargo_options(&self) -> Vec<Cow<str>> {
82 vec!["--message-format=json".into()]
83 }
84
85 fn modify_command(&self, command: &mut Command) {
86 use crate::actions::exec::Stdio;
87
88 let subcommand = self.subcommand.clone();
89 command
90 .outline(move |line| Self::process_json_record(&subcommand, line))
91 .stdout(Stdio::null());
92 }
93}