1use crate::{
7 jsonld::schema::{
8 JsonLdContext, JsonLdHeader, OneOrMany, SimpleContext, TermDef, TermDetail, TypeOrVec,
9 },
10 object::Object,
11 prelude::DataModel,
12 tree,
13};
14use petgraph::Direction;
15use std::{
16 collections::{HashMap, HashSet},
17 error::Error,
18};
19
20pub fn to_json_ld(model: &DataModel, root: Option<&str>) -> Result<JsonLdHeader, Box<dyn Error>> {
40 let context = create_context(model, root)?;
41 let config = model.config.clone().unwrap_or_default();
42 let object = match root {
43 Some(name) => model
44 .objects
45 .iter()
46 .find(|o| o.name == name)
47 .ok_or_else(|| format!("Object {name} not found"))?,
48 None => model.objects.first().ok_or("No objects found in model")?,
49 };
50
51 let object_type = match object.term.clone() {
52 Some(term) => TypeOrVec::Single(term),
53 None => TypeOrVec::Single(format!("{}:{}", config.prefix, object.name)),
54 };
55
56 Ok(JsonLdHeader {
57 context: Some(JsonLdContext::Object(context)),
58 type_: Some(object_type),
59 ..Default::default()
60 })
61}
62
63fn create_context(model: &DataModel, root: Option<&str>) -> Result<SimpleContext, String> {
81 let mut context = SimpleContext::default();
82 let config = model.config.clone().unwrap_or_default();
83 let model_id = if !config.prefix.is_empty() {
84 config.prefix
85 } else if config.id.is_some() {
86 config.id.unwrap()
87 } else {
88 "model".to_string()
89 };
90
91 context
93 .terms
94 .insert(model_id.clone(), TermDef::Simple(config.repo.clone()));
95
96 if let Some(prefixes) = &config.prefixes {
98 for (prefix, uri) in prefixes {
99 context
100 .terms
101 .insert(prefix.clone(), TermDef::Simple(uri.clone()));
102 }
103 }
104
105 let mut context_cache: HashMap<String, SimpleContext> = HashMap::new();
106
107 let root_name = match root {
109 Some(name) => name.to_string(),
110 None => model
111 .objects
112 .first()
113 .ok_or("No objects found in model")?
114 .name
115 .clone(),
116 };
117
118 let graph = tree::object_graph(model, &root_name)?;
120
121 if let Some(root_idx) = graph
124 .node_indices()
125 .find(|&idx| graph[idx].name == root_name)
126 {
127 let obj_context = build_object_context(&graph, root_idx, &model_id, &mut context_cache);
128 for (term_name, term_def) in obj_context.terms {
129 context.terms.insert(term_name, term_def);
130 }
131 }
132 Ok(context)
133}
134
135fn build_object_context(
145 graph: &petgraph::graph::DiGraph<Object, ()>,
146 node_idx: petgraph::graph::NodeIndex,
147 model_id: &str,
148 cache: &mut HashMap<String, SimpleContext>,
149) -> SimpleContext {
150 let object = &graph[node_idx];
151
152 if let Some(cached) = cache.get(&object.name) {
153 return cached.clone();
154 }
155
156 let mut context = SimpleContext::default();
157 let object_names: HashSet<String> = graph
158 .node_indices()
159 .map(|idx| graph[idx].name.clone())
160 .collect();
161
162 for attr in &object.attributes {
163 let has_nested = attr.dtypes.iter().any(|dt| object_names.contains(dt));
164 let term_def = if has_nested || attr.is_array {
165 build_detailed_term_def(graph, node_idx, attr, model_id, has_nested, cache)
166 } else {
167 let term_id = get_attr_term_id(attr, object, model_id);
168 TermDef::Simple(term_id)
169 };
170
171 context.terms.insert(attr.name.clone(), term_def);
172 }
173
174 cache.insert(object.name.clone(), context.clone());
175 context
176}
177
178fn build_detailed_term_def(
180 graph: &petgraph::graph::DiGraph<Object, ()>,
181 node_idx: petgraph::graph::NodeIndex,
182 attr: &crate::attribute::Attribute,
183 model_id: &str,
184 has_nested: bool,
185 cache: &mut HashMap<String, SimpleContext>,
186) -> TermDef {
187 let object_type = attr
189 .dtypes
190 .first()
191 .and_then(|dtype| find_sub_object(graph, dtype))
192 .map(|_idx| "@id".to_string());
193
194 let mut detail = TermDetail {
196 type_: object_type,
197 container: if attr.is_array {
198 Some(OneOrMany::One("@set".to_string()))
199 } else {
200 None
201 },
202 ..Default::default()
203 };
204
205 if has_nested {
206 let nested = build_nested_for_attr(graph, node_idx, attr, model_id, cache);
207 if !nested.terms.is_empty() {
208 detail.context = Some(Box::new(JsonLdContext::Object(nested)));
209 }
210 }
211
212 TermDef::Detailed(detail)
213}
214
215fn find_sub_object(
219 graph: &petgraph::graph::DiGraph<Object, ()>,
220 dtype: &str,
221) -> Option<petgraph::graph::NodeIndex> {
222 graph.node_indices().find(|&idx| graph[idx].name == dtype)
223}
224
225fn get_attr_term_id(attr: &crate::attribute::Attribute, object: &Object, model_id: &str) -> String {
230 match &attr.term {
231 Some(term) => term.clone(),
232 None => format!("{}:{}/{}", model_id, object.name, attr.name),
233 }
234}
235
236fn get_object_term_or_default(term: &Option<String>, model_id: &str, object_name: &str) -> String {
240 match term {
241 Some(term) => term.clone(),
242 None => format!("{}:{}", model_id, object_name),
243 }
244}
245
246fn build_nested_for_attr(
257 graph: &petgraph::graph::DiGraph<Object, ()>,
258 parent_idx: petgraph::graph::NodeIndex,
259 attr: &crate::attribute::Attribute,
260 model_id: &str,
261 cache: &mut HashMap<String, SimpleContext>,
262) -> SimpleContext {
263 let mut context = SimpleContext::default();
264
265 for neighbor_idx in graph.neighbors_directed(parent_idx, Direction::Outgoing) {
266 let neighbor_obj = &graph[neighbor_idx];
267
268 if attr.dtypes.contains(&neighbor_obj.name) {
269 let nested_context = build_object_context(graph, neighbor_idx, model_id, cache);
270
271 let term = get_object_term_or_default(&neighbor_obj.term, model_id, &neighbor_obj.name);
273 context.type_ = Some(TypeOrVec::Single(term));
274
275 for (key, value) in nested_context.terms {
276 context.terms.insert(key, value);
277 }
278 }
279 }
280
281 context
282}