1use std::collections::BTreeMap;
2
3use crate::{
4 literal::{Literal, LiteralValue},
5 perspectives::{
6 add_link::AddLinkPerspectiveAddLink, query_links::QueryLinksPerspectiveQueryLinks,
7 PerspectivesClient,
8 },
9 subject_proxy::SubjectProxy,
10 types::LinkExpression,
11};
12use anyhow::{anyhow, Result};
13use chrono::naive::NaiveDateTime;
14use regex::Regex;
15use serde_json::Value;
16type DateTime = NaiveDateTime;
17
18pub struct PerspectiveProxy {
19 client: PerspectivesClient,
20 perspective_uuid: String,
21}
22
23impl PerspectiveProxy {
24 pub fn new(client: PerspectivesClient, perspective_uuid: String) -> Self {
25 Self {
26 client,
27 perspective_uuid,
28 }
29 }
30
31 pub async fn add_link(
32 &self,
33 source: String,
34 target: String,
35 predicate: Option<String>,
36 status: Option<String>,
37 ) -> Result<AddLinkPerspectiveAddLink> {
38 self.client
39 .add_link(
40 self.perspective_uuid.clone(),
41 source,
42 target,
43 predicate,
44 status,
45 )
46 .await
47 }
48
49 pub async fn get(
50 &self,
51 source: Option<String>,
52 target: Option<String>,
53 predicate: Option<String>,
54 from_date: Option<DateTime>,
55 until_date: Option<DateTime>,
56 limit: Option<f64>,
57 ) -> Result<Vec<QueryLinksPerspectiveQueryLinks>> {
58 self.client
59 .query_links(
60 self.perspective_uuid.clone(),
61 source,
62 target,
63 predicate,
64 from_date,
65 until_date,
66 limit,
67 )
68 .await
69 }
70
71 pub async fn infer(&self, prolog_query: String) -> Result<Value> {
72 self.client
73 .infer(self.perspective_uuid.clone(), prolog_query)
74 .await
75 }
76
77 pub async fn add_dna(&self, name: String, dna: String, dna_type: String) -> Result<()> {
78 let mut predicate = "ad4m://has_custom_dna";
79
80 if dna_type == "subject_class" {
81 predicate = "ad4m://has_subject_class"
82 } else if dna_type == "flow" {
83 predicate = "ad4m://has_flow"
84 }
85
86 let literal_name = Literal::from_string(name);
87
88 let links = self
89 .get(
90 Some("ad4m://self".into()),
91 Some(literal_name.to_url().unwrap()),
92 Some(predicate.into()),
93 None,
94 None,
95 None,
96 )
97 .await?;
98
99 if links.is_empty() {
100 self.set_single_target(
101 "ad4m://self".into(),
102 predicate.into(),
103 literal_name.to_url().unwrap(),
104 )
105 .await?;
106 }
107
108 let literal = Literal::from_string(dna);
109
110 self.add_link(
111 literal_name.to_url().unwrap(),
112 literal.to_url().unwrap(),
113 Some("ad4m://sdna".into()),
114 Some("shared".to_string()),
115 )
116 .await?;
117 Ok(())
118 }
119
120 pub async fn get_dna(&self) -> Result<Vec<String>> {
121 self.get(Some("ad4m://sdna".into()), None, None, None, None, None)
122 .await?
123 .into_iter()
124 .map(|link| {
125 let literal = Literal::from_url(link.data.target)?;
126 match literal.get() {
127 Ok(LiteralValue::String(string)) => Ok(string),
128 _ => Err(anyhow::anyhow!("Not a string literal")),
129 }
130 })
131 .collect()
132 }
133
134 pub async fn get_single_target(&self, source: String, predicate: String) -> Result<String> {
135 let links = self
136 .client
137 .query_links(
138 self.perspective_uuid.clone(),
139 Some(source),
140 None,
141 Some(predicate),
142 None,
143 None,
144 None,
145 )
146 .await?;
147 if links.is_empty() {
148 return Err(anyhow::anyhow!("No links found"));
149 }
150 if links.len() > 1 {
151 return Err(anyhow::anyhow!("Multiple links found"));
152 }
153 Ok(links[0].data.target.clone())
154 }
155
156 pub async fn set_single_target(
157 &self,
158 source: String,
159 predicate: String,
160 target: String,
161 ) -> Result<()> {
162 let links: Vec<LinkExpression> = self
163 .client
164 .query_links(
165 self.perspective_uuid.clone(),
166 Some(source.clone()),
167 None,
168 Some(predicate.clone()),
169 None,
170 None,
171 None,
172 )
173 .await?
174 .into_iter()
175 .map(LinkExpression::from)
176 .collect();
177
178 for link in links {
179 self.client
180 .remove_link(self.perspective_uuid.clone(), link)
181 .await?;
182 }
183
184 self.client
185 .add_link(
186 self.perspective_uuid.clone(),
187 source,
188 target,
189 Some(predicate),
190 Some("shared".to_string()),
191 )
192 .await?;
193 Ok(())
194 }
195
196 pub async fn subject_classes(&self) -> Result<Vec<String>> {
197 let mut classes = Vec::new();
198 if let Value::Array(classes_results) = self.infer("subject_class(X, _)".into()).await? {
199 for class in classes_results {
200 if let Some(Value::String(class_name)) = class.get("X") {
201 classes.push(class_name.clone());
202 }
203 }
204 }
205 Ok(classes)
206 }
207
208 pub async fn subject_class_properties(&self, class: &String) -> Result<Vec<String>> {
209 let mut propertys = Vec::new();
210 if let Value::Array(propertys_results) = self
211 .infer(format!("subject_class(\"{}\", C), property(C, X)", class))
212 .await?
213 {
214 for property in propertys_results {
215 if let Some(Value::String(property_name)) = property.get("X") {
216 propertys.push(property_name.clone());
217 }
218 }
219 }
220 Ok(propertys)
221 }
222
223 pub async fn subject_class_collections(&self, class: &String) -> Result<Vec<String>> {
224 let mut collections = Vec::new();
225 if let Value::Array(collection_results) = self
226 .infer(format!("subject_class(\"{}\", C), collection(C, X)", class))
227 .await?
228 {
229 for colletion in collection_results {
230 if let Some(Value::String(collection_name)) = colletion.get("X") {
231 collections.push(collection_name.clone());
232 }
233 }
234 }
235 Ok(collections)
236 }
237
238 pub async fn create_subject(&self, class: &String, base: &str) -> Result<()> {
239 if let Ok(Value::Array(results)) = self
240 .infer(format!(
241 "subject_class(\"{}\", C), constructor(C, Action)",
242 class
243 ))
244 .await
245 {
246 if let Some(Value::Object(action)) = results.first() {
248 if let Value::String(action_string) = action
250 .get("Action")
251 .ok_or(anyhow::anyhow!("Unbound variable Action is not set"))?
252 {
253 self.execute_action(action_string, base, None).await?;
255 return Ok(());
256 }
257 }
258 }
259 Err(anyhow::anyhow!("No constructor found for class: {}", class))
260 }
261
262 pub async fn is_subject_instance(&self, class: &String, base: &String) -> Result<bool> {
263 match self
264 .infer(format!(
265 r#"subject_class("{}", C), instance(C, "{}")"#,
266 class, base
267 ))
268 .await?
269 {
270 Value::Array(results) => {
271 if results.is_empty() {
272 Ok(false)
273 } else {
274 Ok(true)
275 }
276 }
277 Value::Bool(b) => Ok(b),
278 _ => Ok(false),
279 }
280 }
281
282 pub async fn get_subject(&self, class: &String, base: &String) -> Result<SubjectProxy> {
283 if self.is_subject_instance(class, base).await? {
284 Ok(SubjectProxy::new(self, class.clone(), base.clone()))
285 } else {
286 Err(anyhow!(
287 "Expression {} is not a subject instance of class: {}",
288 base,
289 class
290 ))
291 }
292 }
293
294 pub async fn get_subject_classes(&self, base: &String) -> Result<Vec<String>> {
295 let mut classes = Vec::new();
296 if let Value::Array(classes_results) = self
297 .infer(format!(
298 r#"instance(C, "{}"), subject_class(Classname, C)"#,
299 base
300 ))
301 .await?
302 {
303 for class in classes_results {
304 if let Some(Value::String(class_name)) = class.get("Classname") {
305 classes.push(class_name.clone());
306 }
307 }
308 }
309 Ok(classes)
310 }
311
312 pub async fn execute_action(
313 &self,
314 action: &str,
315 base: &str,
316 params: Option<BTreeMap<&str, &String>>,
317 ) -> Result<()> {
318 let commands = parse_action(action)?;
319 for command in commands {
320 let mut command = command.replace("this", base);
321 if let Some(ref params) = params {
322 for (key, value) in params.iter() {
323 command = command.replace(key, value);
324 }
325 }
326 match command.action.as_str() {
327 "addLink" => {
328 self.add_link(
330 command.source,
331 command.target,
332 command.predicate,
333 command.status,
334 )
335 .await?;
336 }
337 "removeLink" => {
338 unimplemented!();
339 }
342 "setSingleTarget" => {
343 self.set_single_target(
344 command.source,
345 command
346 .predicate
347 .ok_or(anyhow::anyhow!("No predicate in set_single_target action"))?,
348 command.target,
349 )
350 .await?;
351 }
352 _ => {
353 return Err(anyhow::anyhow!("Unknown action: {}", command.action));
354 }
355 }
356 }
357 Ok(())
358 }
359}
360
361#[derive(Debug)]
362struct Command {
363 pub action: String,
364 pub source: String,
365 pub predicate: Option<String>,
366 pub target: String,
367 pub status: Option<String>,
368}
369
370impl Command {
371 pub fn replace(&self, pattern: &str, replacement: &str) -> Command {
372 Command {
373 action: self.action.replace(pattern, replacement),
374 source: self.source.replace(pattern, replacement),
375 predicate: self
376 .predicate
377 .as_ref()
378 .map(|f| f.replace(pattern, replacement)),
379 target: self.target.replace(pattern, replacement),
380 status: self
381 .status
382 .as_ref()
383 .map(|f| f.replace(pattern, replacement)),
384 }
385 }
386}
387
388fn parse_action(action: &str) -> Result<Vec<Command>> {
389 let action_regex = Regex::new(r"\[(?P<command>\{.*})*\]")?;
390
391 let command_regex = Regex::new(
394 r#"\{(action:\s*"(?P<action>[\S--,]+)",?\s*)|(source:\s*"(?P<source>[\S--,]+)",?\s*)|(predicate:\s*"(?P<predicate>[\S--,]+)",?\s*)|(target:\s*"(?P<target>[\S--,]+)",?\s*)|(status:\s*"(?P<status>[\S--,]+)",?\s*)\}"#,
395 )?;
396
397 let mut commands = Vec::new();
398 for capture in action_regex.captures_iter(action) {
399 let mut action = None;
400 let mut source = None;
401 let mut predicate = None;
402 let mut target = None;
403 let mut status = None;
404 command_regex
405 .captures_iter(capture.name("command").unwrap().as_str())
406 .for_each(|capture| {
407 action = action.or(capture.name("action").map(|e| e.as_str()));
408 source = source.or(capture.name("source").map(|e| e.as_str()));
409 predicate = predicate.or(capture.name("predicate").map(|e| e.as_str()));
410 target = target.or(capture.name("target").map(|e| e.as_str()));
411 status = status.or(capture.name("status").map(|e| e.as_str()));
412 });
413
414 commands.push(Command {
415 action: action.ok_or(anyhow!("Comman without action"))?.into(),
416 source: source.ok_or(anyhow!("Comman without source"))?.into(),
417 predicate: predicate.map(|e| e.into()),
418 target: target.ok_or(anyhow!("Comman without target"))?.into(),
419 status: status.map(|e| e.into()),
420 });
421 }
423
424 Ok(commands)
425}