rudof_lib/
rudof.rs

1use crate::{RudofConfig, RudofError, ShapesGraphSource};
2use iri_s::IriS;
3use shacl_ast::{ShaclParser, ShaclWriter};
4use shacl_validation::shacl_processor::{GraphValidation, ShaclProcessor};
5use shacl_validation::store::graph::Graph;
6
7use shapemap::{NodeSelector, ShapeSelector};
8
9use shapes_converter::ShEx2Uml;
10#[cfg(feature = "dctap")]
11use shapes_converter::Tap2ShEx;
12use shex_ast::ir::schema_ir::SchemaIR;
13use shex_compact::ShExParser;
14use shex_validation::{ResolveMethod, SchemaWithoutImports};
15use srdf::Sparql;
16use srdf::{FocusRDF, SRDFGraph};
17use std::fmt::Debug;
18#[cfg(feature = "dctap")]
19use std::path::Path;
20use std::str::FromStr;
21use std::{io, result};
22
23// These are the structs that are publicly re-exported
24#[cfg(feature = "dctap")]
25pub use dctap::{DCTAPFormat, DCTap as DCTAP};
26pub use iri_s::iri;
27pub use prefixmap::PrefixMap;
28pub use shacl_ast::ShaclFormat;
29pub use shacl_validation::shacl_processor::ShaclValidationMode;
30pub use shacl_validation::validation_report::report::ValidationReport;
31pub use shapemap::{QueryShapeMap, ResultShapeMap, ShapeMapFormat, ValidationStatus};
32pub use shex_compact::{ShExFormatter, ShapeMapParser, ShapemapFormatter as ShapeMapFormatter};
33pub use shex_validation::Validator as ShExValidator;
34pub use shex_validation::{ShExFormat, ValidatorConfig};
35pub use srdf::{QuerySolution, QuerySolutions, RDFFormat, ReaderMode, SRDFSparql, VarName};
36pub type Result<T> = result::Result<T, RudofError>;
37pub use shacl_ast::ast::Schema as ShaclSchema;
38pub use shapes_converter::UmlGenerationMode;
39pub use shex_ast::Schema as ShExSchema;
40pub use sparql_service::RdfData;
41
42/// This represents the public API to interact with `rudof`
43#[derive(Debug)]
44pub struct Rudof {
45    config: RudofConfig,
46    rdf_data: RdfData,
47    shacl_schema: Option<ShaclSchema>, // TODO: Should we store a compiled schema to avoid compiling it for each validation request?
48    shex_schema: Option<ShExSchema>,
49    shex_schema_ir: Option<SchemaIR>,
50    resolved_shex_schema: Option<SchemaWithoutImports>,
51    shex_validator: Option<ShExValidator>,
52    shapemap: Option<QueryShapeMap>,
53
54    #[cfg(feature = "dctap")]
55    dctap: Option<DCTAP>,
56    shex_results: Option<ResultShapeMap>,
57}
58
59// TODO: We added this declaration so PyRudof can contain Rudof and be Send as required by PyO3
60// TODO: Review what are the consequences of this declaration
61unsafe impl Send for Rudof {}
62
63impl Rudof {
64    pub fn new(config: &RudofConfig) -> Rudof {
65        Rudof {
66            config: config.clone(),
67            shex_schema: None,
68            shex_schema_ir: None,
69            shacl_schema: None,
70            resolved_shex_schema: None,
71            shex_validator: None,
72            rdf_data: RdfData::new(),
73            shapemap: None,
74
75            #[cfg(feature = "dctap")]
76            dctap: None,
77            shex_results: None,
78        }
79    }
80
81    pub fn config(&self) -> &RudofConfig {
82        &self.config
83    }
84
85    pub fn update_config(&mut self, config: &RudofConfig) {
86        self.config = config.clone();
87    }
88
89    /// Resets the current RDF Data
90    pub fn reset_data(&mut self) {
91        self.rdf_data = RdfData::new()
92    }
93
94    #[cfg(feature = "dctap")]
95    /// Resets the current DCTAP
96    pub fn reset_dctap(&mut self) {
97        self.dctap = None
98    }
99
100    /// Resets the current SHACL shapes graph
101    pub fn reset_shacl(&mut self) {
102        self.shacl_schema = None
103    }
104
105    /// Resets all current values
106    pub fn reset_all(&mut self) {
107        self.reset_data();
108        #[cfg(feature = "dctap")]
109        self.reset_dctap();
110        self.reset_shacl();
111        self.reset_shapemap();
112        self.reset_validation_results();
113        self.reset_shex();
114    }
115
116    /// Get the shapes graph schema from the current RDF data
117    pub fn get_shacl_from_data(&mut self) -> Result<()> {
118        let schema = shacl_schema_from_data(self.rdf_data.clone())?;
119        self.shacl_schema = Some(schema);
120        Ok(())
121    }
122
123    /// Get the current SHACL
124    pub fn get_shacl(&self) -> Option<&ShaclSchema> {
125        self.shacl_schema.as_ref()
126    }
127
128    /// Get the current ShEx Schema
129    pub fn get_shex(&self) -> Option<&ShExSchema> {
130        self.shex_schema.as_ref()
131    }
132
133    /// Get the current ShEx Schema Internal Representation
134    pub fn get_shex_ir(&self) -> Option<&SchemaIR> {
135        self.shex_schema_ir.as_ref()
136    }
137
138    /// Get the current DCTAP
139    #[cfg(feature = "dctap")]
140    pub fn get_dctap(&self) -> Option<&DCTAP> {
141        self.dctap.as_ref()
142    }
143
144    /// Get the current shapemap
145    pub fn get_shapemap(&self) -> Option<&QueryShapeMap> {
146        self.shapemap.as_ref()
147    }
148
149    /// Converts the current DCTAP to a ShExSchema
150    /// Stores the value of the ShExSchema in the current shex
151    #[cfg(feature = "dctap")]
152    pub fn dctap2shex(&mut self) -> Result<()> {
153        if let Some(dctap) = self.get_dctap() {
154            let converter = Tap2ShEx::new(&self.config.tap2shex_config());
155            let shex = converter
156                .convert(dctap)
157                .map_err(|e| RudofError::DCTap2ShEx {
158                    error: format!("{e}"),
159                })?;
160            self.shex_schema = Some(shex);
161            Ok(())
162        } else {
163            Err(RudofError::NoDCTAP)
164        }
165    }
166
167    /// Generate a UML Class-like representation of a ShEx schema according to PlantUML syntax
168    ///
169    pub fn shex2plant_uml<W: io::Write>(
170        &self,
171        mode: &UmlGenerationMode,
172        writer: &mut W,
173    ) -> Result<()> {
174        if let Some(shex) = &self.shex_schema {
175            let mut converter = ShEx2Uml::new(&self.config.shex2uml_config());
176            converter
177                .convert(shex)
178                .map_err(|e| RudofError::ShEx2PlantUmlError {
179                    error: format!("{e}"),
180                })?;
181            converter.as_plantuml(writer, mode).map_err(|e| {
182                RudofError::ShEx2PlantUmlErrorAsPlantUML {
183                    error: format!("{e}"),
184                }
185            })?;
186            Ok(())
187        } else {
188            Err(RudofError::ShEx2UmlWithoutShEx)
189        }
190    }
191
192    pub fn serialize_data<W: io::Write>(&self, format: &RDFFormat, writer: &mut W) -> Result<()> {
193        self.rdf_data
194            .serialize(format, writer)
195            .map_err(|e| RudofError::SerializingData {
196                error: format!("{e}"),
197            })
198    }
199
200    /// Serialize the current ShapeMap
201    pub fn serialize_shapemap<W: io::Write>(
202        &self,
203        format: &ShapeMapFormat,
204        formatter: &ShapeMapFormatter,
205        writer: &mut W,
206    ) -> Result<()> {
207        if let Some(shapemap) = &self.shapemap {
208            match format {
209                ShapeMapFormat::Compact => {
210                    formatter.write_shapemap(shapemap, writer).map_err(|e| {
211                        RudofError::ErrorFormattingShapeMap {
212                            shapemap: format!("{:?}", shapemap.clone()),
213                            error: format!("{e}"),
214                        }
215                    })
216                }
217                ShapeMapFormat::JSON => {
218                    serde_json::to_writer_pretty(writer, &shapemap).map_err(|e| {
219                        RudofError::ErrorWritingShExJson {
220                            schema: format!("{:?}", shapemap.clone()),
221                            error: format!("{e}"),
222                        }
223                    })?;
224                    Ok(())
225                }
226            }
227        } else {
228            Err(RudofError::NoShapeMapToSerialize)
229        }
230    }
231
232    /// Serialize the current ShEx Schema
233    pub fn serialize_shex<W: io::Write>(
234        &self,
235        format: &ShExFormat,
236        formatter: &ShExFormatter,
237        writer: &mut W,
238    ) -> Result<()> {
239        if let Some(shex) = &self.shex_schema {
240            match format {
241                ShExFormat::ShExC => {
242                    formatter.write_schema(shex, writer).map_err(|e| {
243                        RudofError::ErrorFormattingSchema {
244                            schema: format!("{:?}", shex.clone()),
245                            error: format!("{e}"),
246                        }
247                    })?;
248                    Ok(())
249                }
250                ShExFormat::ShExJ => {
251                    serde_json::to_writer_pretty(writer, &shex).map_err(|e| {
252                        RudofError::ErrorWritingShExJson {
253                            schema: format!("{:?}", shex.clone()),
254                            error: format!("{e}"),
255                        }
256                    })?;
257                    Ok(())
258                }
259                ShExFormat::Turtle => Err(RudofError::NotImplemented {
260                    msg: format!("ShEx to ShExR for {shex:?}"),
261                }),
262            }
263        } else {
264            Err(RudofError::NoShExSchemaToSerialize)
265        }
266    }
267
268    pub fn run_query_str(&mut self, str: &str) -> Result<QuerySolutions<RdfData>> {
269        self.rdf_data
270            .check_store()
271            .map_err(|e| RudofError::StorageError {
272                error: format!("{e}"),
273            })?;
274        let results = self
275            .rdf_data
276            .query_select(str)
277            .map_err(|e| RudofError::QueryError {
278                str: str.to_string(),
279                error: format!("{e}"),
280            })?;
281        Ok(results)
282    }
283
284    pub fn run_query<R: io::Read>(&mut self, reader: &mut R) -> Result<QuerySolutions<RdfData>> {
285        let mut str = String::new();
286        reader
287            .read_to_string(&mut str)
288            .map_err(|e| RudofError::ReadError {
289                error: format!("{e}"),
290            })?;
291        self.run_query_str(str.as_str())
292    }
293
294    pub fn serialize_shacl<W: io::Write>(
295        &self,
296        format: &ShaclFormat,
297        writer: &mut W,
298    ) -> Result<()> {
299        if let Some(shacl) = &self.shacl_schema {
300            match format {
301                ShaclFormat::Internal => {
302                    write!(writer, "{shacl}").map_err(|e| RudofError::SerializingSHACLInternal {
303                        error: format!("{e}"),
304                    })
305                }
306                _ => {
307                    let data_format = shacl_format2rdf_format(format)?;
308                    let mut shacl_writer: ShaclWriter<SRDFGraph> = ShaclWriter::new();
309                    shacl_writer
310                        .write(shacl)
311                        .map_err(|e| RudofError::WritingSHACL {
312                            shacl: format!("{:?}", shacl.clone()),
313                            error: format!("{e}"),
314                        })?;
315                    shacl_writer.serialize(&data_format, writer).map_err(|e| {
316                        RudofError::SerializingSHACL {
317                            error: format!("{e}"),
318                            shacl: format!("{:?}", shacl.clone()),
319                        }
320                    })?;
321                    Ok(())
322                }
323            }
324        } else {
325            Err(RudofError::NoShaclToSerialize)
326        }
327    }
328
329    /// Resets the current ShEx validation results
330    /// The action is necessary to start a fresh validation
331    pub fn reset_validation_results(&mut self) {
332        // TODO: We could add another operation to reset only the current validation results keeping the compiled schema
333        /*if let Some(ref mut validator) = &mut self.shex_validator {
334            validator.reset_result_map()
335        }*/
336        self.shex_results = None
337    }
338
339    /// Resets the current validator
340    /// This operation removes the current shex_schema
341    pub fn reset_shex(&mut self) {
342        self.shex_schema = None;
343        self.shex_validator = None
344    }
345
346    /// Reads a SHACL schema from a reader
347    /// - `base` is used to resolve relative IRIs
348    /// - `format` indicates the Shacl format
349    pub fn read_shacl<R: io::Read>(
350        &mut self,
351        reader: R,
352        format: &ShaclFormat,
353        base: Option<&str>,
354        reader_mode: &ReaderMode,
355    ) -> Result<()> {
356        let format = match format {
357            ShaclFormat::Internal => Err(RudofError::InternalSHACLFormatNonReadable),
358            ShaclFormat::Turtle => Ok(RDFFormat::Turtle),
359            ShaclFormat::NTriples => Ok(RDFFormat::NTriples),
360            ShaclFormat::RDFXML => Ok(RDFFormat::RDFXML),
361            ShaclFormat::TriG => Ok(RDFFormat::TriG),
362            ShaclFormat::N3 => Ok(RDFFormat::N3),
363            ShaclFormat::NQuads => Ok(RDFFormat::NQuads),
364        }?;
365
366        let rdf_graph =
367            SRDFGraph::from_reader(reader, &format, base, reader_mode).map_err(|e| {
368                RudofError::ReadError {
369                    error: format!("{e}"),
370                }
371            })?;
372        let schema = shacl_schema_from_data(rdf_graph)?;
373        self.shacl_schema = Some(schema);
374        Ok(())
375    }
376
377    /// Reads a `DCTAP` and replaces the current one
378    /// - `format` indicates the DCTAP format
379    #[cfg(feature = "dctap")]
380    pub fn read_dctap<R: std::io::Read>(&mut self, reader: R, format: &DCTAPFormat) -> Result<()> {
381        let dctap = match format {
382            DCTAPFormat::CSV => {
383                let dctap = DCTAP::from_reader(reader, &self.config.tap_config()).map_err(|e| {
384                    RudofError::DCTAPReaderCSVReader {
385                        error: format!("{e}"),
386                    }
387                })?;
388                Ok(dctap)
389            }
390            DCTAPFormat::XLS | DCTAPFormat::XLSB | DCTAPFormat::XLSM | DCTAPFormat::XLSX => {
391                Err(RudofError::DCTAPReadXLSNoPath)
392            }
393        }?;
394        self.dctap = Some(dctap);
395        Ok(())
396    }
397
398    /// Reads a `DCTAP` and replaces the current one
399    /// - `format` indicates the DCTAP format
400    #[cfg(feature = "dctap")]
401    pub fn read_dctap_path<P: AsRef<Path>>(&mut self, path: P, format: &DCTAPFormat) -> Result<()> {
402        let path_name = path.as_ref().display().to_string();
403        let dctap =
404            match format {
405                DCTAPFormat::CSV => {
406                    let dctap = DCTAP::from_path(path, &self.config.tap_config()).map_err(|e| {
407                        RudofError::DCTAPReaderCSV {
408                            path: path_name,
409                            error: format!("{e}"),
410                        }
411                    })?;
412                    Ok(dctap)
413                }
414                DCTAPFormat::XLS | DCTAPFormat::XLSB | DCTAPFormat::XLSM | DCTAPFormat::XLSX => {
415                    let path_buf = path.as_ref().to_path_buf();
416                    let dctap = DCTAP::from_excel(path_buf, None, &self.config.tap_config())
417                        .map_err(|e| RudofError::DCTAPReaderPathXLS {
418                            path: path_name,
419                            error: format!("{e}"),
420                            format: format!("{format:?}"),
421                        })?;
422                    Ok(dctap)
423                }
424            }?;
425        self.dctap = Some(dctap);
426        Ok(())
427    }
428
429    /// Reads a `ShExSchema` and replaces the current one
430    /// It also updates the current ShEx validator with the new ShExSchema
431    /// - `base` is used to resolve relative IRIs
432    /// - `format` indicates the ShEx format according to [`ShExFormat`](https://docs.rs/shex_validation/latest/shex_validation/shex_format/enum.ShExFormat.html)
433    pub fn read_shex<R: io::Read>(
434        &mut self,
435        reader: R,
436        format: &ShExFormat,
437        base: Option<&str>,
438    ) -> Result<()> {
439        let schema_json = match format {
440            ShExFormat::ShExC => {
441                let base = match base {
442                    Some(str) => {
443                        let iri = IriS::from_str(str).map_err(|e| RudofError::BaseIriError {
444                            str: str.to_string(),
445                            error: format!("{e}"),
446                        })?;
447                        Ok(Some(iri))
448                    }
449                    None => Ok(None),
450                }?;
451                let schema_json = ShExParser::from_reader(reader, base).map_err(|e| {
452                    RudofError::ShExCParserError {
453                        error: format!("{e}"),
454                    }
455                })?;
456                Ok(schema_json)
457            }
458            ShExFormat::ShExJ => {
459                let schema_json =
460                    ShExSchema::from_reader(reader).map_err(|e| RudofError::ShExJParserError {
461                        error: format!("{e}"),
462                    })?;
463                Ok(schema_json)
464            }
465            ShExFormat::Turtle => {
466                todo!()
467                /*let rdf = parse_data(
468                    &vec![input.clone()],
469                    &DataFormat::Turtle,
470                    reader_mode,
471                    &config.rdf_config(),
472                )?;
473                let schema = ShExRParser::new(rdf).parse()?;
474                Ok(schema) */
475            }
476        }?;
477        self.shex_schema = Some(schema_json.clone());
478        let mut schema = SchemaIR::new();
479        schema
480            .from_schema_json(&schema_json)
481            .map_err(|e| RudofError::CompilingSchemaError {
482                error: format!("{e}"),
483            })?;
484        self.shex_schema_ir = Some(schema.clone());
485
486        let validator =
487            ShExValidator::new(schema, &self.config.validator_config()).map_err(|e| {
488                RudofError::ShExValidatorCreationError {
489                    error: format!("{e}"),
490                    schema: format!("{}", schema_json),
491                }
492            })?;
493        self.shex_validator = Some(validator);
494        Ok(())
495    }
496
497    /// Validate RDF data using SHACL
498    ///
499    /// mode: Indicates whether to use SPARQL or native Rust implementation
500    /// shapes_graph_source: Indicates the source of the shapes graph,
501    /// which can be extracted from the current RDF data,
502    /// or from the current SHACL schema.
503    /// If there is no current SHACL schema, it tries to get it from the current RDF data
504    pub fn validate_shacl(
505        &mut self,
506        mode: &ShaclValidationMode,
507        shapes_graph_source: &ShapesGraphSource,
508    ) -> Result<ValidationReport> {
509        let (compiled_schema, shacl_schema) = match shapes_graph_source {
510            ShapesGraphSource::CurrentSchema if self.shacl_schema.is_some() => {
511                let ast_schema = self.shacl_schema.as_ref().unwrap();
512                let compiled_schema = ast_schema.clone().to_owned().try_into().map_err(|e| {
513                    RudofError::SHACLCompilationError {
514                        error: format!("{e}"),
515                        schema: Box::new(ast_schema.clone()),
516                    }
517                })?;
518                Ok((compiled_schema, ast_schema.clone()))
519            }
520            _ => {
521                let ast_schema = shacl_schema_from_data(self.rdf_data.clone())?;
522                let compiled_schema = ast_schema.to_owned().try_into().map_err(|e| {
523                    RudofError::SHACLCompilationError {
524                        error: format!("{e}"),
525                        schema: Box::new(ast_schema.clone()),
526                    }
527                })?;
528                Ok((compiled_schema, ast_schema))
529            }
530        }?;
531        let validator = GraphValidation::from_graph(Graph::from_data(self.rdf_data.clone()), *mode);
532        let result = ShaclProcessor::validate(&validator, &compiled_schema).map_err(|e| {
533            RudofError::SHACLValidationError {
534                error: format!("{e}"),
535                schema: Box::new(shacl_schema),
536            }
537        })?;
538        Ok(result)
539    }
540
541    /// Validate RDF data using ShEx
542    /// It uses a ShEx validator which has a corrent ShEx schema and the current ShapeMap
543    pub fn validate_shex(&mut self) -> Result<ResultShapeMap> {
544        let schema_str = format!("{:?}", self.shex_validator);
545        match self.shex_validator {
546            None => Err(RudofError::ShExValidatorUndefined {}),
547            Some(ref mut validator) => match &self.shapemap {
548                None => Err(RudofError::NoShapeMap { schema: schema_str }),
549                Some(shapemap) => {
550                    let schema = validator.schema().clone();
551                    let result = validator
552                        .validate_shapemap2(
553                            shapemap,
554                            &self.rdf_data,
555                            &schema,
556                            &Some(self.rdf_data.prefixmap_in_memory()),
557                            &None, // TODO!! Get schema prefix map
558                        )
559                        .map_err(|e| RudofError::ShExValidatorError {
560                            schema: schema_str.clone(),
561                            rdf_data: format!("{:?}", self.rdf_data),
562                            query_map: format!("{shapemap:?}"),
563                            error: format!("{e}"),
564                        })?;
565                    Ok(result.clone())
566                }
567            },
568        }
569    }
570
571    /// Adds an endpoint to the current RDF data
572    pub fn add_endpoint(&mut self, iri: &IriS, prefixmap: &PrefixMap) -> Result<()> {
573        let sparql_endpoint =
574            SRDFSparql::new(iri, prefixmap).map_err(|e| RudofError::AddingEndpointError {
575                iri: iri.clone(),
576                error: format!("{e}"),
577            })?;
578
579        self.rdf_data.add_endpoint(sparql_endpoint);
580        Ok(())
581    }
582
583    /// Parses an RDF graph from a reader and merges it with the current graph
584    pub fn read_data<R: io::Read>(
585        &mut self,
586        reader: R,
587        format: &RDFFormat,
588        base: Option<&str>,
589        reader_mode: &ReaderMode,
590    ) -> Result<()> {
591        self.rdf_data
592            .merge_from_reader(reader, format, base, reader_mode)
593            .map_err(|e| RudofError::MergeRDFDataFromReader {
594                format: format!("{format:?}"),
595                base: format!("{base:?}"),
596                reader_mode: format!("{reader_mode:?}"),
597                error: format!("{e}"),
598            })?;
599        Ok(())
600    }
601
602    /// Cleans the in-memory graph
603    pub fn clean_rdf_graph(&mut self) {
604        self.rdf_data.clean_graph();
605    }
606
607    /// Add a pair of node selector and shape selector to the current shapemap
608    pub fn shapemap_add_node_shape_selectors(&mut self, node: NodeSelector, shape: ShapeSelector) {
609        match &mut self.shapemap {
610            None => {
611                let mut shapemap = QueryShapeMap::new();
612                shapemap.add_association(node, shape);
613                self.shapemap = Some(shapemap)
614            }
615            Some(ref mut sm) => {
616                sm.add_association(node, shape);
617            }
618        };
619    }
620
621    /// Read a shapemap
622    pub fn read_shapemap<R: io::Read>(
623        &mut self,
624        mut reader: R,
625        shapemap_format: &ShapeMapFormat,
626    ) -> Result<()> {
627        let mut v = Vec::new();
628        reader
629            .read_to_end(&mut v)
630            .map_err(|e| RudofError::ReadError {
631                error: format!("{e}"),
632            })?;
633        let s = String::from_utf8(v).map_err(|e| RudofError::Utf8Error {
634            error: format!("{e}"),
635        })?;
636        let shapemap = match shapemap_format {
637            ShapeMapFormat::Compact => {
638                let shapemap = ShapeMapParser::parse(
639                    s.as_str(),
640                    &Some(self.nodes_prefixmap()),
641                    &self.shex_shapes_prefixmap(),
642                )
643                .map_err(|e| RudofError::ShapeMapParseError {
644                    str: s.to_string(),
645                    error: format!("{e}"),
646                })?;
647                Ok(shapemap)
648            }
649            ShapeMapFormat::JSON => todo!(),
650        }?;
651        self.shapemap = Some(shapemap);
652        Ok(())
653    }
654
655    pub fn reset_shapemap(&mut self) {
656        self.shapemap = None
657    }
658
659    /// Returns the RDF data prefixmap
660    pub fn nodes_prefixmap(&self) -> PrefixMap {
661        self.rdf_data.prefixmap_in_memory()
662    }
663
664    /// Returns the shapes prefixmap
665    ///
666    /// If no ShEx schema has been set, returns None
667    pub fn shex_shapes_prefixmap(&self) -> Option<PrefixMap> {
668        self.shex_validator
669            .as_ref()
670            .map(|validator| validator.shapes_prefixmap())
671    }
672
673    /// Get current RDF Data
674    pub fn get_rdf_data(&self) -> &RdfData {
675        &self.rdf_data
676    }
677
678    /// Obtains the current `shex_schema` after resolving import declarations
679    ///
680    /// If the import declarations in the current schema have not been resolved, it resolves them
681    pub fn shex_schema_without_imports(&mut self) -> Result<SchemaWithoutImports> {
682        match &self.resolved_shex_schema {
683            None => match &self.shex_schema {
684                Some(schema) => {
685                    let schema_resolved = SchemaWithoutImports::resolve_imports(
686                        schema,
687                        &Some(schema.source_iri()),
688                        Some(&ResolveMethod::default()),
689                    )
690                    .map_err(|e| RudofError::ResolvingImportsShExSchema {
691                        error: format!("{e}"),
692                    })?;
693                    self.resolved_shex_schema = Some(schema_resolved.clone());
694                    Ok(schema_resolved)
695                }
696                None => Err(RudofError::NoShExSchemaForResolvingImports),
697            },
698            Some(resolved_schema) => Ok(resolved_schema.clone()),
699        }
700    }
701}
702
703fn shacl_schema_from_data<RDF: FocusRDF + Debug>(rdf_data: RDF) -> Result<ShaclSchema> {
704    let schema = ShaclParser::new(rdf_data)
705        .parse()
706        .map_err(|e| RudofError::SHACLParseError {
707            error: format!("{e}"),
708        })?;
709    Ok(schema)
710}
711
712fn shacl_format2rdf_format(shacl_format: &ShaclFormat) -> Result<RDFFormat> {
713    match shacl_format {
714        ShaclFormat::N3 => Ok(RDFFormat::N3),
715        ShaclFormat::NQuads => Ok(RDFFormat::NQuads),
716        ShaclFormat::NTriples => Ok(RDFFormat::NTriples),
717        ShaclFormat::RDFXML => Ok(RDFFormat::RDFXML),
718        ShaclFormat::TriG => Ok(RDFFormat::TriG),
719        ShaclFormat::Turtle => Ok(RDFFormat::Turtle),
720        ShaclFormat::Internal => Err(RudofError::NoInternalFormatForRDF),
721    }
722}
723
724#[cfg(test)]
725mod tests {
726    use iri_s::iri;
727    use shacl_ast::ShaclFormat;
728    use shacl_validation::shacl_processor::ShaclValidationMode;
729    use shapemap::ShapeMapFormat;
730    use shex_ast::{ir::shape_label::ShapeLabel, Node};
731    use shex_validation::ShExFormat;
732
733    use crate::RudofConfig;
734
735    use super::Rudof;
736
737    #[test]
738    fn test_shex_validation_ok() {
739        let data = r#"<http://example/x> <http://example/p> 23 ."#;
740        let shex = r#"<http://example/S> { <http://example/p> . }"#;
741        let shapemap = r#"<http://example/x>@<http://example/S>"#;
742        let mut rudof = Rudof::new(&RudofConfig::default());
743        rudof
744            .read_data(
745                data.as_bytes(),
746                &srdf::RDFFormat::Turtle,
747                None,
748                &srdf::ReaderMode::Strict,
749            )
750            .unwrap();
751
752        rudof
753            .read_shex(shex.as_bytes(), &ShExFormat::ShExC, None)
754            .unwrap();
755        rudof
756            .read_shapemap(shapemap.as_bytes(), &ShapeMapFormat::default())
757            .unwrap();
758        let result = rudof.validate_shex().unwrap();
759        let node = Node::iri(iri!("http://example/x"));
760        let shape = ShapeLabel::iri(iri!("http://example/S"));
761        assert!(result.get_info(&node, &shape).unwrap().is_conformant())
762    }
763
764    #[test]
765    fn test_shex_validation_ko() {
766        let data = r#"<http://example/x> <http://example/other> 23 ."#;
767        let shex = r#"<http://example/S> { <http://example/p> . }"#;
768        let shapemap = r#"<http://example/x>@<http://example/S>"#;
769        let mut rudof = Rudof::new(&RudofConfig::default());
770        rudof
771            .read_data(
772                data.as_bytes(),
773                &srdf::RDFFormat::Turtle,
774                None,
775                &srdf::ReaderMode::Strict,
776            )
777            .unwrap();
778
779        rudof
780            .read_shex(shex.as_bytes(), &ShExFormat::ShExC, None)
781            .unwrap();
782        rudof
783            .read_shapemap(shapemap.as_bytes(), &ShapeMapFormat::default())
784            .unwrap();
785        let result = rudof.validate_shex().unwrap();
786        let node = Node::iri(iri!("http://example/x"));
787        let shape = ShapeLabel::iri(iri!("http://example/S"));
788        assert!(result.get_info(&node, &shape).unwrap().is_non_conformant(),)
789    }
790
791    #[test]
792    fn test_shacl_validation_ok() {
793        let data = r#"prefix : <http://example.org/> 
794        :x :p 23 .
795        "#;
796        let shacl = r#"prefix :       <http://example.org/> 
797            prefix sh:     <http://www.w3.org/ns/shacl#> 
798            prefix xsd:    <http://www.w3.org/2001/XMLSchema#> 
799
800            :S a sh:NodeShape; sh:closed true ;
801              sh:targetNode :x ;
802            sh:property [                  
803                sh:path     :p ; 
804                sh:minCount 1; 
805                sh:maxCount 1;
806                sh:datatype xsd:integer ;
807            ] .
808             "#;
809        let mut rudof = Rudof::new(&RudofConfig::default());
810        rudof
811            .read_data(
812                data.as_bytes(),
813                &srdf::RDFFormat::Turtle,
814                None,
815                &srdf::ReaderMode::Strict,
816            )
817            .unwrap();
818
819        rudof
820            .read_shacl(
821                shacl.as_bytes(),
822                &ShaclFormat::Turtle,
823                None,
824                &srdf::ReaderMode::Lax,
825            )
826            .unwrap();
827        let result = rudof
828            .validate_shacl(
829                &ShaclValidationMode::Native,
830                &crate::ShapesGraphSource::CurrentSchema,
831            )
832            .unwrap();
833        assert!(result.results().is_empty())
834    }
835
836    #[test]
837    fn test_shacl_validation_ko() {
838        let data = r#"prefix : <http://example.org/> 
839        :x :other 23 .
840        "#;
841        let shacl = r#"prefix :       <http://example.org/> 
842            prefix sh:     <http://www.w3.org/ns/shacl#> 
843            prefix xsd:    <http://www.w3.org/2001/XMLSchema#> 
844
845            :S a sh:NodeShape; 
846             sh:targetNode :x ; 
847            sh:property [                  
848                sh:path     :p ; 
849                sh:minCount 1; 
850                sh:maxCount 1;
851                sh:datatype xsd:integer ;
852            ] .
853             "#;
854        let mut rudof = Rudof::new(&RudofConfig::default());
855        rudof
856            .read_data(
857                data.as_bytes(),
858                &srdf::RDFFormat::Turtle,
859                None,
860                &srdf::ReaderMode::Strict,
861            )
862            .unwrap();
863
864        rudof
865            .read_shacl(
866                shacl.as_bytes(),
867                &ShaclFormat::Turtle,
868                None,
869                &srdf::ReaderMode::Lax,
870            )
871            .unwrap();
872        let result = rudof
873            .validate_shacl(
874                &ShaclValidationMode::Native,
875                &crate::ShapesGraphSource::CurrentSchema,
876            )
877            .unwrap();
878        assert!(!result.conforms())
879    }
880
881    #[test]
882    fn test_shacl_validation_data_ko() {
883        let data = r#"prefix :       <http://example.org/> 
884            prefix sh:     <http://www.w3.org/ns/shacl#> 
885            prefix xsd:    <http://www.w3.org/2001/XMLSchema#> 
886
887            :x :other 23 .
888
889            :S a sh:NodeShape; 
890               sh:targetNode :x ; 
891               sh:property [                  
892                sh:path     :p ; 
893                sh:minCount 1; 
894                sh:maxCount 1;
895                sh:datatype xsd:integer ;
896            ] .
897             "#;
898        let mut rudof = Rudof::new(&RudofConfig::default());
899        rudof
900            .read_data(
901                data.as_bytes(),
902                &srdf::RDFFormat::Turtle,
903                None,
904                &srdf::ReaderMode::Strict,
905            )
906            .unwrap();
907        let result = rudof
908            .validate_shacl(
909                &ShaclValidationMode::Native,
910                &crate::ShapesGraphSource::CurrentData,
911            )
912            .unwrap();
913        assert!(!result.conforms())
914    }
915
916    #[test]
917    fn test_shacl_validation_data_ok() {
918        let data = r#"prefix :       <http://example.org/> 
919            prefix sh:     <http://www.w3.org/ns/shacl#> 
920            prefix xsd:    <http://www.w3.org/2001/XMLSchema#> 
921
922            :x :p 23 .
923
924            :S a sh:NodeShape; 
925               sh:targetNode :x ; 
926               sh:property [                  
927                sh:path     :p ; 
928                sh:minCount 1; 
929                sh:maxCount 1;
930                sh:datatype xsd:integer ;
931            ] .
932             "#;
933        let mut rudof = Rudof::new(&RudofConfig::default());
934        rudof
935            .read_data(
936                data.as_bytes(),
937                &srdf::RDFFormat::Turtle,
938                None,
939                &srdf::ReaderMode::Strict,
940            )
941            .unwrap();
942        let result = rudof
943            .validate_shacl(
944                &ShaclValidationMode::Native,
945                &crate::ShapesGraphSource::CurrentData,
946            )
947            .unwrap();
948        assert!(result.conforms())
949    }
950}