use anyhow::Result;
use cargo_metadata::{diagnostic::DiagnosticLevel, Message};
use itertools::process_results;
use md5::compute;
use serde::Serialize;
use std::io::{BufRead, Write};
#[derive(Debug, Serialize)]
pub struct GitLabCodeQuality {
pub description: String,
pub fingerprint: String,
pub location: Location,
pub severity: String,
}
impl GitLabCodeQuality {
fn calculate_fingerprint(message: &str, file: &str, line: usize) -> String {
let fingerprint = format!("{}{}{}", message, file, line);
let digest = compute(Vec::from(fingerprint));
format!("{:x}", digest)
}
pub fn new(message: &str, file: &str, line: usize, severity: &str) -> Self {
Self {
description: String::from(message),
fingerprint: Self::calculate_fingerprint(message, file, line),
location: Location {
path: String::from(file),
lines: Lines { begin: line },
},
severity: String::from(severity),
}
}
}
#[derive(Debug, Serialize)]
pub struct Location {
pub path: String,
pub lines: Lines,
}
#[derive(Debug, Serialize)]
pub struct Lines {
pub begin: usize,
}
fn parse_compiler_message(message: &Message) -> Vec<GitLabCodeQuality> {
match message {
Message::CompilerMessage(msg) => {
let message = &msg.message;
message
.spans
.iter()
.map(|span| {
GitLabCodeQuality::new(
&message.message,
&span.file_name,
span.line_start,
match message.level {
DiagnosticLevel::Help | DiagnosticLevel::Note => "info",
DiagnosticLevel::Warning => "minor",
DiagnosticLevel::Error => "major",
_ => "",
},
)
})
.collect()
}
_ => vec![],
}
}
fn process<R: BufRead>(reader: R) -> Result<Vec<GitLabCodeQuality>, std::io::Error> {
process_results(Message::parse_stream(reader), |input| {
input
.flat_map(|message| parse_compiler_message(&message))
.collect()
})
}
pub fn parse_to_writer<R: BufRead, W: Write>(reader: R, writer: W) -> Result<()> {
let results = process(reader)?;
serde_json::to_writer_pretty(writer, &results)?;
Ok(())
}
pub fn parse_to_string<R: BufRead>(reader: R) -> Result<String> {
let results = process(reader)?;
let json = serde_json::to_string_pretty(&results)?;
Ok(json)
}
#[deprecated(since = "0.2.0", note = "please use `lib::parse_to_string` instead")]
pub fn parse<R: BufRead>(reader: R) -> Result<String> {
parse_to_string(reader)
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn fingerprint_is_unique(message1 in "\\PC*", message2 in "\\PC*", file1 in "\\PC*", file2 in "\\PC*", line1 in 0usize..10000, line2 in 0usize..10000) {
let fingerprint1 = GitLabCodeQuality::calculate_fingerprint(&message1, &file1, line1);
let fingerprint2 = GitLabCodeQuality::calculate_fingerprint(&message2, &file2, line2);
assert_ne!(fingerprint1, fingerprint2);
}
}
}