1use iri_s::IriS;
2use prefixmap::IriRef;
3use serde::{Deserialize, Serialize};
4use shex_ast::{IriOrStr, Schema, SchemaJsonError, Shape, ShapeDecl, ShapeExpr, ShapeExprLabel};
5use shex_compact::ShExParser;
6use std::collections::{hash_map::Entry, HashMap};
7use url::Url;
8
9use crate::{ResolveMethod, SchemaWithoutImportsError, ShExFormat};
10
11#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
12pub struct SchemaWithoutImports {
13 source_schema: Box<Schema>,
14
15 local_shapes_counter: usize,
16
17 imported_schemas: Vec<IriOrStr>,
18
19 #[serde(skip)]
20 shapes_map: HashMap<ShapeExprLabel, (ShapeExpr, IriS)>,
21}
22
23impl SchemaWithoutImports {
24 pub fn resolve_iriref(&self, iri_ref: &IriRef) -> IriS {
25 self.source_schema.resolve_iriref(iri_ref)
26 }
27
28 pub fn local_shapes_count(&self) -> usize {
30 self.local_shapes_counter
31 }
32
33 pub fn total_shapes_count(&self) -> usize {
35 self.shapes_map.len()
36 }
37
38 pub fn shapes(&self) -> impl Iterator<Item = (&ShapeExprLabel, &(ShapeExpr, IriS))> {
42 self.shapes_map.iter()
43 }
44 pub fn imported_schemas(&self) -> impl Iterator<Item = &IriOrStr> {
46 self.imported_schemas.iter()
47 }
48
49 pub fn resolve_imports(
51 schema: &Schema,
52 base: &Option<IriS>,
53 resolve_method: Option<&ResolveMethod>,
54 ) -> Result<SchemaWithoutImports, SchemaWithoutImportsError> {
55 let resolve_method = match resolve_method {
56 None => ResolveMethod::default(),
57 Some(m) => m.clone(),
58 };
59 let mut visited = Vec::new();
60 let mut pending: Vec<_> = schema.imports();
61 let mut map = HashMap::new();
62 let mut local_shapes_counter = 0;
63 if let Some(shapes) = schema.shapes() {
64 for decl in shapes {
65 local_shapes_counter += 1;
66 Self::add_shape_decl(&mut map, decl, &schema.source_iri())?;
67 }
68 }
69 Self::resolve_imports_visited(&mut pending, &mut visited, base, &resolve_method, &mut map)?;
70 Ok(SchemaWithoutImports {
71 source_schema: Box::new(schema.clone()),
72 local_shapes_counter,
73 imported_schemas: visited.clone(),
74 shapes_map: map,
75 })
76 }
77
78 pub fn add_shape_decl(
79 map: &mut HashMap<ShapeExprLabel, (ShapeExpr, IriS)>,
80 decl: ShapeDecl,
81 source_iri: &IriS,
82 ) -> Result<(), SchemaWithoutImportsError> {
83 let id = decl.id.clone();
84 match map.entry(decl.id) {
85 Entry::Occupied(entry) => {
86 let (old_shape_expr, maybe_iri) = entry.get();
87 return Err(SchemaWithoutImportsError::DuplicatedShapeDecl {
88 label: id,
89 old_shape_expr: Box::new(old_shape_expr.clone()),
90 imported_from: maybe_iri.clone(),
91 shape_expr2: Box::new(decl.shape_expr),
92 });
93 }
94 Entry::Vacant(v) => {
95 v.insert((decl.shape_expr.clone(), source_iri.clone()));
96 }
97 }
98 Ok(())
99 }
100
101 pub fn resolve_imports_visited(
102 pending: &mut Vec<IriOrStr>,
103 visited: &mut Vec<IriOrStr>,
104 base: &Option<IriS>,
105 resolve_method: &ResolveMethod,
106 map: &mut HashMap<ShapeExprLabel, (ShapeExpr, IriS)>,
107 ) -> Result<(), SchemaWithoutImportsError> {
108 while let Some(candidate) = pending.pop() {
109 if !visited.contains(&candidate) {
110 let candidate_iri = resolve_iri_or_str(&candidate, base, resolve_method)?;
111 let new_schema = match resolve_method {
112 ResolveMethod::RotatingFormats(formats) => {
113 find_schema_rotating_formats(&candidate_iri, formats.clone(), base)
114 }
115 ResolveMethod::ByGuessingExtension => todo!(),
116 ResolveMethod::ByContentNegotiation => todo!(),
117 }?;
118 for i in new_schema.imports() {
119 if !visited.contains(&i) {
120 pending.push(i.clone())
121 }
122 }
123 if let Some(shapes) = new_schema.shapes() {
124 for decl in shapes {
125 Self::add_shape_decl(map, decl, &candidate_iri)?;
126 }
127 }
128 visited.push(candidate.clone());
129 }
130 }
131 Ok(())
132 }
133
134 pub fn count_extends(&self) -> HashMap<usize, usize> {
135 let mut result = HashMap::new();
136 for (_, (shape, _)) in self.shapes() {
137 let extends_counter = match shape {
138 ShapeExpr::Shape(Shape { extends: None, .. }) => Some(0),
139 ShapeExpr::Shape(Shape {
140 extends: Some(es), ..
141 }) => Some(es.len()),
142 _ => None,
143 };
144
145 if let Some(ec) = extends_counter {
146 match result.entry(ec) {
147 Entry::Occupied(mut v) => {
148 let r = v.get_mut();
149 *r += 1;
150 }
151 Entry::Vacant(vac) => {
152 vac.insert(1);
153 }
154 }
155 }
156 }
157 result
158 }
159}
160
161pub fn find_schema_rotating_formats(
162 iri: &IriS,
163 formats: Vec<ShExFormat>,
164 base: &Option<IriS>,
165) -> Result<Schema, SchemaWithoutImportsError> {
166 for format in &formats {
167 match get_schema_from_iri(iri, format, base) {
168 Err(_e) => {
169 }
171 Ok(schema) => return Ok(schema),
172 }
173 }
174 Err(SchemaWithoutImportsError::SchemaFromIriRotatingFormats {
175 iri: iri.clone(),
176 formats: format!("{formats:?}"),
177 })
178}
179
180pub fn resolve_iri_or_str(
181 value: &IriOrStr,
182 base: &Option<IriS>,
183 _resolve_method: &ResolveMethod,
184) -> Result<IriS, SchemaWithoutImportsError> {
185 match value {
186 IriOrStr::IriS(iri) => Ok(iri.clone()),
187 IriOrStr::String(str) => match Url::parse(str) {
188 Ok(url) => Ok(IriS::new_unchecked(url.as_str())),
189 Err(_e) => match base {
190 None => todo!(),
191 Some(base) => {
192 let iri =
193 base.join(str)
194 .map_err(|e| SchemaWithoutImportsError::ResolvingStrIri {
195 base: Box::new(base.clone()),
196 str: str.clone(),
197 error: Box::new(e),
198 })?;
199 Ok(iri)
200 }
201 },
202 },
203 }
204}
205
206#[cfg(not(target_family = "wasm"))]
207pub fn local_folder_as_iri() -> Result<IriS, SchemaJsonError> {
208 use tracing::debug;
209
210 let current_dir = std::env::current_dir().map_err(|e| SchemaJsonError::CurrentDir {
211 error: format!("{e}"),
212 })?;
213 debug!("Current dir: {current_dir:?}");
214 let url = Url::from_file_path(¤t_dir)
215 .map_err(|_e| SchemaJsonError::LocalFolderIriError { path: current_dir })?;
216 debug!("url: {url}");
217 Ok(IriS::new_unchecked(url.as_str()))
218}
219
220#[cfg(target_family = "wasm")]
221pub fn local_folder_as_iri() -> Result<IriS, SchemaJsonError> {
222 Err(SchemaJsonError::CurrentDir {
223 error: String::from("No local folder on web"),
224 })
225}
226
227pub fn get_schema_from_iri(
228 iri: &IriS,
229 format: &ShExFormat,
230 base: &Option<IriS>,
231) -> Result<Schema, SchemaWithoutImportsError> {
232 match format {
233 ShExFormat::ShExC => {
234 let content =
235 iri.dereference(base)
236 .map_err(|e| SchemaWithoutImportsError::DereferencingIri {
237 iri: iri.clone(),
238 error: format!("{e}"),
239 })?;
240 let schema = ShExParser::parse(content.as_str(), None).map_err(|e| {
241 SchemaWithoutImportsError::ShExCError {
242 error: format!("{e}"),
243 content: content.clone(),
244 }
245 })?;
246 Ok(schema)
247 }
248 ShExFormat::ShExJ => {
249 let schema =
250 Schema::from_iri(iri).map_err(|e| SchemaWithoutImportsError::ShExJError {
251 iri: iri.clone(),
252 error: format!("{e}"),
253 })?;
254 Ok(schema)
255 }
256 ShExFormat::Turtle => {
257 todo!()
258 }
259 }
260}