1use crate::Parser;
2use anyhow::{Context, Result};
3use qlty_types::tests::v1::FileCoverage;
4use serde_json::Value;
5
6#[derive(Debug, Clone, PartialEq, Eq, Default)]
7pub struct Qlty {}
8
9impl Qlty {
10 pub fn new() -> Self {
11 Self {}
12 }
13}
14
15impl Parser for Qlty {
16 fn parse_text(&self, text: &str) -> Result<Vec<FileCoverage>> {
17 text.lines()
18 .filter(|line| !line.is_empty())
19 .map(|line| {
20 let json_line: Value =
21 serde_json::from_str(line).with_context(|| "Failed to parse JSONL line")?;
22
23 let path = json_line
24 .get("path")
25 .and_then(Value::as_str)
26 .ok_or_else(|| anyhow::anyhow!("Missing or invalid `path` field"))?;
27
28 let hits: Vec<i64> = json_line
29 .get("hits")
30 .and_then(Value::as_array)
31 .ok_or_else(|| anyhow::anyhow!("Missing or invalid `hits` field"))?
32 .iter()
33 .map(|v| {
34 v.as_str()
35 .ok_or_else(|| anyhow::anyhow!("Invalid hit value, expected a string"))?
36 .parse::<i64>()
37 .map_err(|e| anyhow::anyhow!("Failed to parse hit value: {}", e))
38 })
39 .collect::<Result<Vec<_>, _>>()?;
40
41 Ok(FileCoverage {
42 path: path.to_string(),
43 hits,
44 ..Default::default()
45 })
46 })
47 .collect()
48 }
49}
50
51#[cfg(test)]
52mod test {
53 use super::*;
54
55 #[test]
56 fn qlty_single_file() {
57 let input = r#"{"path":"lib/fish.rb","hits":["-1","1","1","0","-1","-1","1","0","-1","-1","1","0","-1","-1"]}"#;
58 let results = Qlty::new().parse_text(input).unwrap();
59 insta::assert_yaml_snapshot!(results, @r#"
60 - path: lib/fish.rb
61 hits:
62 - "-1"
63 - "1"
64 - "1"
65 - "0"
66 - "-1"
67 - "-1"
68 - "1"
69 - "0"
70 - "-1"
71 - "-1"
72 - "1"
73 - "0"
74 - "-1"
75 - "-1"
76 "#);
77 }
78
79 #[test]
80 fn qlty_multiple_files() {
81 let input = r#"
82{"path":"lib/dog.rb","hits":["-1","1","1","2","-1","-1","-1","1","0","-1","-1","-1","-1","1","1","-1","-1","1","0","-1","-1","1","0","-1","-1","1","1","-1","-1","1","-1","0","-1","-1","-1","-1","-1","-1","-1","1","0","0","0","0","0","0","0","0","0","0","0","-1","0","-1","-1","-1"]}
83{"path":"lib/fish.rb","hits":["-1","1","1","0","-1","-1","1","0","-1","-1","1","0","-1","-1"]}
84"#;
85 let results = Qlty::new().parse_text(input).unwrap();
86 insta::assert_yaml_snapshot!(results, @r#"
87 - path: lib/dog.rb
88 hits:
89 - "-1"
90 - "1"
91 - "1"
92 - "2"
93 - "-1"
94 - "-1"
95 - "-1"
96 - "1"
97 - "0"
98 - "-1"
99 - "-1"
100 - "-1"
101 - "-1"
102 - "1"
103 - "1"
104 - "-1"
105 - "-1"
106 - "1"
107 - "0"
108 - "-1"
109 - "-1"
110 - "1"
111 - "0"
112 - "-1"
113 - "-1"
114 - "1"
115 - "1"
116 - "-1"
117 - "-1"
118 - "1"
119 - "-1"
120 - "0"
121 - "-1"
122 - "-1"
123 - "-1"
124 - "-1"
125 - "-1"
126 - "-1"
127 - "-1"
128 - "1"
129 - "0"
130 - "0"
131 - "0"
132 - "0"
133 - "0"
134 - "0"
135 - "0"
136 - "0"
137 - "0"
138 - "0"
139 - "0"
140 - "-1"
141 - "0"
142 - "-1"
143 - "-1"
144 - "-1"
145 - path: lib/fish.rb
146 hits:
147 - "-1"
148 - "1"
149 - "1"
150 - "0"
151 - "-1"
152 - "-1"
153 - "1"
154 - "0"
155 - "-1"
156 - "-1"
157 - "1"
158 - "0"
159 - "-1"
160 - "-1"
161 "#);
162 }
163}