1use colored::*;
2use serde::Serialize;
3use srdf::Object;
4
5use crate::ShapemapConfig;
6use crate::ShapemapError;
7use crate::ValidationStatus;
8use prefixmap::PrefixMap;
9use serde::ser::{SerializeMap, SerializeSeq};
10use shex_ast::{ir::shape_label::ShapeLabel, Node};
11use std::collections::hash_map::Entry;
12use std::collections::HashMap;
13use std::fmt::Display;
14use std::fmt::Formatter;
15use std::io::Error;
16use std::io::Write;
17
18#[derive(Debug, PartialEq, Default, Clone)]
20pub struct ResultShapeMap {
21 result: HashMap<Node, HashMap<ShapeLabel, ValidationStatus>>,
22
23 config: ShapemapConfig,
24}
25
26impl ResultShapeMap {
27 pub fn new() -> Self {
28 Self::default()
29 }
30
31 pub fn ok_color(&self) -> Option<Color> {
32 self.config.ok_color()
33 }
34
35 pub fn fail_color(&self) -> Option<Color> {
36 self.config.fail_color()
37 }
38
39 pub fn pending_color(&self) -> Option<Color> {
40 self.config.pending_color()
41 }
42
43 pub fn set_ok_color(&mut self, color: Color) {
44 self.config.set_ok_color(color);
45 }
46
47 pub fn set_fail_color(&mut self, color: Color) {
48 self.config.set_fail_color(color);
49 }
50
51 pub fn set_pending_color(&mut self, color: Color) {
52 self.config.set_pending_color(color)
53 }
54
55 pub fn nodes_prefixmap(&self) -> PrefixMap {
56 self.config.nodes_prefixmap()
57 }
58
59 pub fn shapes_prefixmap(&self) -> PrefixMap {
60 self.config.shapes_prefixmap()
61 }
62
63 pub fn with_nodes_prefixmap(mut self, prefixmap: &PrefixMap) -> Self {
64 self.config = self.config.with_nodes_prefixmap(&prefixmap.clone());
65 self
66 }
67
68 pub fn with_shapes_prefixmap(mut self, prefixmap: &PrefixMap) -> Self {
69 self.config = self.config.with_shapes_prefixmap(&prefixmap.clone());
70 self
71 }
72
73 pub fn add_result(
74 &mut self,
75 node: Node,
76 shape_label: ShapeLabel,
77 status: ValidationStatus,
78 ) -> Result<(), ShapemapError> {
79 let _cn = node.clone();
80 let _sl = shape_label.clone();
81 match self.result.entry(node) {
82 Entry::Occupied(mut c) => {
83 let map = c.get_mut();
84 match map.entry(shape_label) {
85 Entry::Occupied(mut c) => {
86 let cell_status = c.get_mut();
87 match (cell_status.clone(), status) {
88 (
89 ValidationStatus::Conformant(conformant_info),
90 ValidationStatus::Conformant(conformant_info2),
91 ) => {
92 *cell_status = ValidationStatus::Conformant(
93 conformant_info.merge(conformant_info2),
94 )
95 }
96 (
97 ValidationStatus::Conformant(_conformant_info),
98 ValidationStatus::NonConformant(_non_conformant_info),
99 ) => todo!(),
100 (
101 ValidationStatus::Conformant(_conformant_info),
102 ValidationStatus::Pending,
103 ) => {}
104 (
105 ValidationStatus::Conformant(_conformant_info),
106 ValidationStatus::Inconsistent(
107 _conformant_info2,
108 _non_conformant_info2,
109 ),
110 ) => todo!(),
111 (
112 ValidationStatus::NonConformant(_non_conformant_info),
113 ValidationStatus::Conformant(_conformant_info),
114 ) => todo!(),
115 (
116 ValidationStatus::NonConformant(_non_conformant_info),
117 ValidationStatus::NonConformant(_non_conformant_info2),
118 ) => {}
119 (
120 ValidationStatus::NonConformant(_non_conformant_info),
121 ValidationStatus::Pending,
122 ) => {}
123 (
124 ValidationStatus::NonConformant(_non_conformant_info),
125 ValidationStatus::Inconsistent(
126 _conformant_info2,
127 _non_conformant_info2,
128 ),
129 ) => todo!(),
130 (
131 ValidationStatus::Pending,
132 ValidationStatus::Conformant(conformant_info),
133 ) => *cell_status = ValidationStatus::Conformant(conformant_info),
134 (
135 ValidationStatus::Pending,
136 ValidationStatus::NonConformant(non_conformant_info),
137 ) => {
138 *cell_status = ValidationStatus::NonConformant(non_conformant_info)
139 }
140 (ValidationStatus::Pending, ValidationStatus::Pending) => {}
141 (
142 ValidationStatus::Pending,
143 ValidationStatus::Inconsistent(
144 _conformant_info,
145 _non_conformant_info,
146 ),
147 ) => todo!(),
148 (
149 ValidationStatus::Inconsistent(
150 _conformant_info,
151 _non_conformant_info,
152 ),
153 ValidationStatus::Conformant(_conformant_info2),
154 ) => todo!(),
155 (
156 ValidationStatus::Inconsistent(
157 _conformant_info,
158 _non_conformant_info,
159 ),
160 ValidationStatus::NonConformant(_non_conformant_info2),
161 ) => todo!(),
162 (
163 ValidationStatus::Inconsistent(
164 _conformant_info,
165 _non_conformant_info,
166 ),
167 ValidationStatus::Pending,
168 ) => todo!(),
169 (
170 ValidationStatus::Inconsistent(
171 _conformant_info,
172 _non_conformant_info,
173 ),
174 ValidationStatus::Inconsistent(
175 _conformant_info2,
176 _non_conformant_info2,
177 ),
178 ) => todo!(),
179 };
180 Ok(())
181 }
182 Entry::Vacant(v) => {
183 v.insert(status);
184 Ok(())
185 }
186 }
187 }
188 Entry::Vacant(v) => {
189 let mut map = HashMap::new();
190 map.insert(shape_label, status);
191 v.insert(map);
192 Ok(())
193 }
194 }?;
195 Ok(())
196 }
197
198 pub fn get_info(&self, node: &Node, label: &ShapeLabel) -> Option<ValidationStatus> {
199 match self.result.get(node) {
200 Some(shapes) => shapes.get(label).cloned(),
201 None => None,
202 }
203 }
204
205 pub fn iter(&self) -> impl Iterator<Item = (&Node, &ShapeLabel, &ValidationStatus)> {
206 self.result.iter().flat_map(|(node, shapes)| {
207 shapes
208 .iter()
209 .map(move |(shape, status)| (node, shape, status))
210 })
211 }
212
213 pub fn show_minimal(&self, mut writer: Box<dyn Write + 'static>) -> Result<(), Error> {
214 for (node, label, status) in self.iter() {
215 let node_label = format!(
216 "{}@{}",
217 show_node(node, &self.nodes_prefixmap()),
218 show_shapelabel(label, &self.shapes_prefixmap())
219 );
220 match status {
221 ValidationStatus::Conformant(_conformant_info) => {
222 let node_label = match self.ok_color() {
223 None => ColoredString::from(node_label),
224 Some(color) => node_label.color(color),
225 };
226 writeln!(writer, "{node_label} -> OK")?;
227 }
228 ValidationStatus::NonConformant(_non_conformant_info) => {
229 let node_label = match self.fail_color() {
230 None => ColoredString::from(node_label),
231 Some(color) => node_label.color(color),
232 };
233 writeln!(writer, "{node_label} -> Fail")?;
234 }
235 ValidationStatus::Pending => {
236 let node_label = match self.pending_color() {
237 None => ColoredString::from(node_label),
238 Some(color) => node_label.color(color),
239 };
240 writeln!(writer, "{node_label} -> Pending")?
241 }
242 ValidationStatus::Inconsistent(_conformant, _inconformant) => {
243 let node_label = match self.pending_color() {
244 None => ColoredString::from(node_label),
245 Some(color) => node_label.color(color),
246 };
247 writeln!(writer, "{node_label} -> Inconsistent")?
248 }
249 }
250 }
251 Ok(())
252 }
253}
254
255fn show_node(node: &Node, prefixmap: &PrefixMap) -> String {
256 match node.as_object() {
257 Object::Iri(iri) => prefixmap.qualify(iri),
258 _ => format!("{node}"),
259 }
260}
261
262fn show_shapelabel(shapelabel: &ShapeLabel, prefixmap: &PrefixMap) -> String {
263 match shapelabel {
264 ShapeLabel::Iri(iri) => prefixmap.qualify(iri),
265 ShapeLabel::BNode(str) => format!("_:{str}"),
266 ShapeLabel::Start => "Start".to_owned(),
267 }
268}
269
270impl Display for ResultShapeMap {
271 fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
272 for (node, label, status) in self.iter() {
273 let node_label = format!(
274 "{}@{}",
275 show_node(node, &self.nodes_prefixmap()),
276 show_shapelabel(label, &self.shapes_prefixmap())
277 );
278 match status {
279 ValidationStatus::Conformant(conformant_info) => {
280 let node_label = match self.ok_color() {
281 None => ColoredString::from(node_label),
282 Some(color) => node_label.color(color),
283 };
284 write!(f, "{node_label} -> OK, reason: {conformant_info}")?;
285 }
286 ValidationStatus::NonConformant(non_conformant_info) => {
287 let node_label = match self.fail_color() {
288 None => ColoredString::from(node_label),
289 Some(color) => node_label.color(color),
290 };
291 write!(f, "{node_label} -> Fail, reason: {non_conformant_info}")?;
292 }
293 ValidationStatus::Pending => {
294 let node_label = match self.pending_color() {
295 None => ColoredString::from(node_label),
296 Some(color) => node_label.color(color),
297 };
298 write!(f, "{node_label} -> Pending")?
299 }
300 ValidationStatus::Inconsistent(conformant, inconformant) => {
301 let node_label = match self.pending_color() {
302 None => ColoredString::from(node_label),
303 Some(color) => node_label.color(color),
304 };
305 write!(f, "{node_label} -> Inconsistent, conformant: {conformant}, non-conformant: {inconformant}")?
306 }
307 }
308 }
309 Ok(())
310 }
311}
312
313struct ResultSerializer<'a> {
314 node: &'a Node,
315 shape: &'a ShapeLabel,
316 status: &'a ValidationStatus,
317}
318
319impl Serialize for ResultSerializer<'_> {
320 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
321 where
322 S: serde::Serializer,
323 {
324 let mut map = serializer.serialize_map(Some(4))?;
325 map.serialize_entry("node", &self.node.to_string())?;
326 map.serialize_entry("shape", &self.shape.to_string())?;
327 map.serialize_entry("status", &self.status.code())?;
328 map.serialize_entry("appInfo", &self.status.app_info())?;
329 map.serialize_entry("reason", &self.status.reason())?;
330 map.end()
331 }
332}
333
334impl Serialize for ResultShapeMap {
335 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
336 where
337 S: serde::Serializer,
338 {
339 let mut seq = serializer.serialize_seq(Some(self.result.len()))?;
340 for (node, shape, status) in self.iter() {
341 let result_aux = ResultSerializer {
342 node,
343 shape,
344 status,
345 };
346 seq.serialize_element(&result_aux)?;
347 }
348 seq.end()
349 }
350}