shapemap/
result_shape_map.rs

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/// Contains a map of the results obtained after applying ShEx validation
19#[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}