rudof_rdf/rdf_core/rdf_data_config.rs
1use crate::rdf_core::{RDFError, visualizer::RDFVisualizationConfig};
2use std::{collections::HashMap, io, path::Path, str::FromStr};
3
4use prefixmap::PrefixMap;
5
6use iri_s::{IriS, error::IriSError};
7use serde::{Deserialize, Serialize};
8use std::io::Read;
9
10/// Configuration for RDF data readers and visualization settings.
11///
12/// This struct defines how RDF data should be processed, including base IRI resolution,
13/// SPARQL endpoints for querying external data, and visualization preferences.
14#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
15pub struct RdfDataConfig {
16 /// Default base IRI to resolve relative IRIs. If `None`, relative IRIs will be treated as errors.
17 pub base: Option<IriS>,
18
19 /// SPARQL endpoints for querying RDF data. Each endpoint is identified by a unique name.
20 pub endpoints: Option<HashMap<String, EndpointDescription>>,
21
22 /// If true, automatically set the base IRI to the local file or URI of the document being processed.
23 pub automatic_base: Option<bool>,
24
25 /// Configuration for RDF visualization appearance and styling.
26 pub rdf_visualization: Option<RDFVisualizationConfig>,
27}
28
29impl RdfDataConfig {
30 /// Creates a new `RdfDataConfig` with default settings.
31 ///
32 /// The default configuration has no base IRI, no endpoints, automatic base detection enabled,
33 /// and no custom visualization settings.
34 pub fn new() -> RdfDataConfig {
35 RdfDataConfig {
36 base: None,
37 endpoints: None,
38 automatic_base: Some(true),
39 rdf_visualization: None,
40 }
41 }
42
43 /// Adds a Wikidata SPARQL endpoint to the configuration.
44 ///
45 /// This method configures the Wikidata query service endpoint with appropriate prefixes
46 /// for convenient querying of Wikidata's knowledge graph.
47 ///
48 /// # Returns
49 /// The modified `RdfDataConfig` with the Wikidata endpoint added.
50 pub fn with_wikidata(mut self) -> Self {
51 let wikidata_name = "wikidata";
52 let wikidata_iri = "https://query.wikidata.org/sparql";
53 let wikidata = EndpointDescription::new_unchecked(wikidata_iri).with_prefixmap(PrefixMap::wikidata());
54
55 match self.endpoints {
56 None => {
57 self.endpoints = Some(HashMap::from([(wikidata_name.to_string(), wikidata)]));
58 },
59 Some(ref mut map) => {
60 map.insert(wikidata_name.to_string(), wikidata);
61 },
62 };
63 self
64 }
65
66 /// Loads an `RdfDataConfig` from a TOML file at the specified path.
67 ///
68 /// # Arguments
69 /// * `path` - Path to the TOML configuration file.
70 ///
71 /// # Returns
72 /// A `Result` containing the parsed configuration or an error if reading/parsing fails.
73 ///
74 /// # Errors
75 /// Returns `RDFError` if the file cannot be read or the TOML is invalid.
76 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<RdfDataConfig, RDFError> {
77 let path_name = path.as_ref().display().to_string();
78 let f = std::fs::File::open(path).map_err(|e| RDFError::ReadingConfigError {
79 path_name: path_name.clone(),
80 error: e,
81 })?;
82 let s = read_string(f).map_err(|e| RDFError::ReadingConfigError {
83 path_name: path_name.clone(),
84 error: e,
85 })?;
86 let config: RdfDataConfig = toml::from_str(s.as_str()).map_err(|e| RDFError::TomlError {
87 path_name: path_name.to_string(),
88 error: e,
89 })?;
90 Ok(config)
91 }
92
93 /// Gets the RDF visualization configuration, using defaults if none is set.
94 ///
95 /// # Returns
96 /// The `RDFVisualizationConfig` to use for visualization, either from this config
97 /// or the default configuration if none is specified.
98 pub fn rdf_visualization_config(&self) -> RDFVisualizationConfig {
99 self.rdf_visualization.clone().unwrap_or_default()
100 }
101}
102
103impl Default for RdfDataConfig {
104 /// Returns the default RDF data configuration with Wikidata endpoint pre-configured.
105 ///
106 /// The default configuration includes the Wikidata SPARQL endpoint and automatic
107 /// base IRI detection enabled.
108 fn default() -> Self {
109 Self::new().with_wikidata()
110 }
111}
112
113/// Description of a SPARQL endpoint for querying RDF data.
114///
115/// This struct contains the necessary information to connect to and query a SPARQL endpoint,
116/// including URLs for queries and updates, and optional prefix mappings.
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118pub struct EndpointDescription {
119 /// The URL of the SPARQL query endpoint.
120 query_url: IriS,
121 /// Optional URL for SPARQL update operations.
122 update_url: Option<IriS>,
123 /// Optional prefix map for abbreviating IRIs in queries.
124 prefixmap: Option<PrefixMap>,
125}
126
127impl EndpointDescription {
128 /// Creates a new `EndpointDescription` from a URL string without validation.
129 ///
130 /// # Arguments
131 /// * `str` - The URL string for the SPARQL query endpoint.
132 pub fn new_unchecked(str: &str) -> Self {
133 EndpointDescription {
134 query_url: IriS::new_unchecked(str),
135 update_url: None,
136 prefixmap: None,
137 }
138 }
139
140 /// Returns the query URL for this endpoint.
141 ///
142 /// # Returns
143 /// A reference to the `IriS` representing the SPARQL query endpoint URL.
144 pub fn query_url(&self) -> &IriS {
145 &self.query_url
146 }
147
148 /// Returns the prefix map for this endpoint, or a default empty map if none is set.
149 ///
150 /// # Returns
151 /// The `PrefixMap` containing IRI prefixes for query abbreviation.
152 pub fn prefixmap(&self) -> PrefixMap {
153 self.prefixmap.clone().unwrap_or_default()
154 }
155
156 /// Sets the prefix map for this endpoint.
157 ///
158 /// # Arguments
159 /// * `prefixmap` - The `PrefixMap` to associate with this endpoint.
160 ///
161 /// # Returns
162 /// The modified `EndpointDescription` with the new prefix map.
163 pub fn with_prefixmap(mut self, prefixmap: PrefixMap) -> Self {
164 self.prefixmap = Some(prefixmap);
165 self
166 }
167
168 /// Adds or replaces the prefix map for this endpoint.
169 ///
170 /// # Arguments
171 /// * `prefixmap` - The `PrefixMap` to set for this endpoint.
172 pub fn add_prefixmap(&mut self, prefixmap: PrefixMap) {
173 self.prefixmap = Some(prefixmap);
174 }
175}
176
177impl FromStr for EndpointDescription {
178 type Err = IriSError;
179
180 /// Parses an `EndpointDescription` from a URL string.
181 ///
182 /// This validates that the provided string is a valid IRI before creating the endpoint description.
183 ///
184 /// # Arguments
185 /// * `query_url` - The URL string to parse as the SPARQL query endpoint.
186 ///
187 /// # Returns
188 /// A `Result` containing the parsed `EndpointDescription` or an `IriSError` if parsing fails.
189 fn from_str(query_url: &str) -> Result<Self, Self::Err> {
190 let iri = IriS::from_str(query_url)?;
191 Ok(EndpointDescription {
192 query_url: iri,
193 update_url: None,
194 prefixmap: None,
195 })
196 }
197}
198
199/// Reads the entire contents of a reader into a string.
200///
201/// # Arguments
202/// * `reader` - The reader to read from.
203///
204/// # Returns
205/// A `Result` containing the string content or an I/O error.
206fn read_string<R: Read>(mut reader: R) -> io::Result<String> {
207 let mut buf = String::new();
208 reader.read_to_string(&mut buf)?;
209 Ok(buf)
210}