Skip to main content

terraphim_middleware/thesaurus/
mod.rs

1//! Logseq is a knowledge graph that uses Markdown files to store notes. This
2//! module provides a middleware for creating a Thesaurus from a Logseq
3//! haystack.
4//!
5//! Example:
6//!
7//! If we parse a file named `path/to/concept.md` with the following content:
8//!
9//! ```markdown
10//! synonyms:: foo, bar, baz
11//! ```
12//!
13//! Then the thesaurus will contain the following entries:
14//!
15//! ```rust
16//! use terraphim_types::{Thesaurus, Concept, NormalizedTerm};
17//! let concept = Concept::new("concept".into());
18//! let nterm = NormalizedTerm::new(concept.id, concept.value.clone());
19//! let mut thesaurus = Thesaurus::new("Engineer".to_string());
20//! thesaurus.insert(concept.value.clone(), nterm.clone());
21//! thesaurus.insert("foo".to_string().into(),nterm.clone());
22//! thesaurus.insert("bar".to_string().to_string().into(), nterm.clone());
23//! thesaurus.insert("baz".to_string().into(), nterm.clone());
24//! ```
25//! The logic as follows: if you ask for concept by name you get concept, if you ask (get) for any of the synonyms you will get concept with id,
26//! its pre-computed reverse tree traversal - any of the synonyms (leaf) maps into the concepts (root)
27
28pub use terraphim_automata::builder::{Logseq, ThesaurusBuilder};
29use terraphim_config::ConfigState;
30use terraphim_config::Role;
31use terraphim_persistence::Persistable;
32use terraphim_rolegraph::{RoleGraph, RoleGraphSync};
33use terraphim_types::SearchQuery;
34use terraphim_types::{RoleName, Thesaurus};
35
36use crate::Result;
37use std::path::PathBuf;
38
39pub async fn build_thesaurus_from_haystack(
40    config_state: &mut ConfigState,
41    search_query: &SearchQuery,
42) -> Result<()> {
43    // build thesaurus from haystack or load from remote
44    // FIXME: introduce LRU cache for locally build thesaurus via persistance crate
45    log::debug!("Building thesaurus from haystack");
46    let config = config_state.config.lock().await.clone();
47    let roles = config.roles.clone();
48    let default_role = config.default_role.clone();
49    let role_name = search_query.role.clone().unwrap_or_default();
50    log::debug!("Role name: {}", role_name);
51    let role: &mut Role = &mut roles
52        .get(&role_name)
53        .unwrap_or(&roles[&default_role])
54        .to_owned();
55    log::debug!("Role: {:?}", role);
56    for haystack in &role.haystacks {
57        log::debug!("Updating thesaurus for haystack: {:?}", haystack);
58
59        let logseq = Logseq::default();
60        let mut thesaurus: Thesaurus = logseq
61            .build(
62                role_name.as_lowercase().to_string(),
63                PathBuf::from(&haystack.location),
64            )
65            .await?;
66        match thesaurus.save().await {
67            Ok(_) => {
68                log::info!("Thesaurus for role `{}` saved to persistence", role_name);
69                // We reload the thesaurus from persistence to ensure we are using the
70                // canonical, persisted version going forward.
71                thesaurus = thesaurus.load().await?;
72            }
73            Err(e) => log::error!("Failed to save thesaurus: {:?}", e),
74        }
75
76        log::debug!("Make sure thesaurus updated in a role {}", role_name);
77
78        update_thesaurus(config_state, &role_name, thesaurus).await?;
79    }
80    Ok(())
81}
82
83async fn update_thesaurus(
84    config_state: &mut ConfigState,
85    role_name: &RoleName,
86    thesaurus: Thesaurus,
87) -> Result<()> {
88    log::debug!("Updating thesaurus for role: {}", role_name);
89    let rolegraph = RoleGraph::new(role_name.clone(), thesaurus).await;
90    match rolegraph {
91        Ok(rolegraph) => {
92            let rolegraph_value = RoleGraphSync::from(rolegraph);
93            // Actually update the config_state.roles, not just a local copy
94            config_state
95                .roles
96                .insert(role_name.clone(), rolegraph_value);
97            log::info!("Successfully updated rolegraph for role: {}", role_name);
98        }
99        Err(e) => log::error!("Failed to update role and thesaurus: {:?}", e),
100    }
101
102    Ok(())
103}