oca_dag/
lib.rs

1pub mod data_storage;
2pub mod versioning;
3
4use oca_ast::ast;
5use said::{derivation::HashFunction, SelfAddressingIdentifier};
6use std::collections::HashMap;
7use std::str::FromStr;
8
9#[derive(Debug, Clone)]
10pub struct CommandModel {
11    pub digest: SelfAddressingIdentifier,
12    pub json: String,
13}
14impl CommandModel {
15    fn new(command: ast::Command) -> Self {
16        Self {
17            digest: Self::calculate_command_digest(&command),
18            json: serde_json::to_string(&command).unwrap(),
19        }
20    }
21
22    fn calculate_command_digest(command: &ast::Command) -> SelfAddressingIdentifier {
23        let command_json = serde_json::to_string(command).unwrap();
24        let hash_algorithm = HashFunction::from_str("E").unwrap();
25        hash_algorithm.derive(command_json.as_bytes())
26    }
27}
28
29#[derive(Debug, Clone)]
30pub struct CaptureBaseModel {
31    pub capture_base_said: SelfAddressingIdentifier,
32    pub parent: Option<SelfAddressingIdentifier>,
33    pub command_digest: SelfAddressingIdentifier,
34}
35
36#[derive(Debug, Clone)]
37pub struct OverlayModel {
38    pub overlay_said: SelfAddressingIdentifier,
39    pub parent: Option<SelfAddressingIdentifier>,
40    pub command_digest: SelfAddressingIdentifier,
41}
42
43#[derive(Debug, Clone)]
44pub struct OCABundleModel {
45    pub oca_bundle_said: SelfAddressingIdentifier,
46    pub parent: Option<SelfAddressingIdentifier>,
47    pub capture_base_said: SelfAddressingIdentifier,
48    pub overlays_said: Vec<SelfAddressingIdentifier>,
49}
50
51#[derive(Debug, Clone)]
52struct State {
53    oca_bundle: Option<SelfAddressingIdentifier>,
54    capture_base: Option<SelfAddressingIdentifier>,
55    overlays: HashMap<String, SelfAddressingIdentifier>,
56}
57impl State {
58    fn new() -> Self {
59        Self {
60            oca_bundle: None,
61            capture_base: None,
62            overlays: HashMap::new(),
63        }
64    }
65}
66
67#[derive(Debug)]
68pub struct ResultModel {
69    pub command: Option<CommandModel>,
70    pub oca_bundle: Option<OCABundleModel>,
71    pub capture_base: Option<CaptureBaseModel>,
72    pub overlay: Option<OverlayModel>,
73}
74impl ResultModel {
75    fn new() -> Self {
76        Self {
77            command: None,
78            oca_bundle: None,
79            capture_base: None,
80            overlay: None,
81        }
82    }
83}
84
85pub fn build_core_db_model(oca_build: &oca_bundle::build::OCABuild) -> Vec<ResultModel> {
86    let mut state = State::new();
87    let mut result_models = vec![];
88
89    for step in &oca_build.steps {
90        let (new_state, result_model) = apply_step(state, step);
91        state = new_state;
92        result_models.push(result_model);
93    }
94
95    result_models
96}
97
98fn apply_step(state: State, step: &oca_bundle::build::OCABuildStep) -> (State, ResultModel) {
99    let mut current_state = state.clone();
100    let mut result = ResultModel::new();
101    let command_model = CommandModel::new(step.command.clone());
102    result.command = Some(command_model.clone());
103
104    match &step.command.object_kind {
105        ast::ObjectKind::CaptureBase(_) => {
106            let capture_base_model = CaptureBaseModel {
107                capture_base_said: step.result.capture_base.said.clone().unwrap(),
108                parent: state.capture_base,
109                command_digest: command_model.digest,
110            };
111            result.capture_base = Some(capture_base_model.clone());
112            current_state.capture_base = Some(capture_base_model.capture_base_said.clone());
113        }
114        ast::ObjectKind::Overlay(overlay_type, content) => {
115            let mut lang = None;
116            // match &content.properties {
117            if let Some(properties) = &content.properties {
118                if let Some(ast::NestedValue::Value(lang_value)) = properties.get("lang") {
119                    lang = isolang::Language::from_639_1(lang_value);
120                }
121            }
122
123            let overlay = step.result.overlays.iter().find(|overlay| {
124                overlay.overlay_type().eq(overlay_type) && overlay.language() == lang.as_ref()
125            });
126
127            if let Some(overlay) = overlay {
128                let overlay_key = match overlay.language() {
129                    Some(lang) => {
130                        format!("{}-{}", &overlay.overlay_type(), lang.to_639_1().unwrap())
131                    }
132                    None => format!("{}", overlay.overlay_type()),
133                };
134                let parent_overlay = state.overlays.get(&overlay_key);
135                let overlay_model = OverlayModel {
136                    overlay_said: overlay.said().clone().unwrap(),
137                    parent: parent_overlay.cloned(),
138                    command_digest: command_model.digest,
139                };
140                result.overlay = Some(overlay_model.clone());
141                current_state
142                    .overlays
143                    .insert(overlay_key, overlay_model.overlay_said.clone());
144            }
145        }
146        ast::ObjectKind::OCABundle(_) => {
147            dbg!("OCABundle");
148        }
149    }
150
151    let oca_bundle_model = OCABundleModel {
152        oca_bundle_said: step.result.said.clone().unwrap(),
153        parent: state.oca_bundle,
154        capture_base_said: step.result.capture_base.said.clone().unwrap(),
155        overlays_said: step
156            .result
157            .overlays
158            .iter()
159            .map(|overlay| overlay.said().clone().unwrap())
160            .collect(),
161    };
162    result.oca_bundle = Some(oca_bundle_model.clone());
163    current_state.oca_bundle = Some(oca_bundle_model.oca_bundle_said.clone());
164
165    (current_state, result)
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use indexmap::{indexmap, IndexMap};
172    use oca_ast::ast::Content;
173
174    #[test]
175    fn test_build_core_db_model() -> Result<(), Vec<String>> {
176        let mut commands = vec![];
177
178        // 1. ADD ATTR abc
179        commands.push(ast::Command {
180            kind: ast::CommandType::Add,
181            object_kind: ast::ObjectKind::CaptureBase(ast::CaptureContent {
182                attributes: Some(indexmap! {
183                    "abc".to_string() => ast::NestedAttrType::Value(ast::AttributeType::Text)
184                }),
185                properties: None,
186                flagged_attributes: None,
187            }),
188        });
189
190        // 2. add label en abc "ble"
191        commands.push(ast::Command {
192            kind: ast::CommandType::Add,
193            object_kind: ast::ObjectKind::Overlay(
194                ast::OverlayType::Label,
195                Content {
196                    attributes: Some(indexmap! {
197                        "abc".to_string() => ast::NestedValue::Value("ble".to_string())
198                    }),
199                    properties: Some(indexmap! {
200                        "lang".to_string() => ast::NestedValue::Value("en".to_string())
201                    }),
202                },
203            ),
204        });
205
206        // 3. add attr def
207        commands.push(ast::Command {
208            kind: ast::CommandType::Add,
209            object_kind: ast::ObjectKind::CaptureBase(ast::CaptureContent {
210                attributes: Some(indexmap! {
211                    "def".to_string() => ast::NestedAttrType::Value(ast::AttributeType::Text)
212                }),
213                properties: None,
214                flagged_attributes: None,
215            }),
216        });
217
218        // 4. add label fr abc "ble"
219        commands.push(ast::Command {
220            kind: ast::CommandType::Add,
221            object_kind: ast::ObjectKind::Overlay(
222                ast::OverlayType::Label,
223                Content {
224                    attributes: Some(indexmap! {
225                        "abc".to_string() => ast::NestedValue::Value("ble".to_string())
226                    }),
227                    properties: Some(indexmap! {
228                        "lang".to_string() => ast::NestedValue::Value("fr".to_string())
229                    }),
230                },
231            ),
232        });
233
234        // 5. update attr "en" abc "bererg"
235        /*
236        commands.push(
237            ast::Command {
238                kind: ast::CommandType::Modify,
239                object_kind: ast::ObjectKind::Overlay(ast::OverlayType::Label),
240                content: Some(ast::Content {
241                    attributes: Some(indexmap! {
242                        "abc".to_string() => ast::NestedValue::Value("bererg".to_string())
243                    }),
244                    properties: Some(indexmap! {
245                        "lang".to_string() => ast::NestedValue::Value("en".to_string())
246                    }),
247                }),
248            }
249        );
250        */
251
252        let ast = ast::OCAAst {
253            version: "0.1.0".to_string(),
254            commands,
255            commands_meta: IndexMap::new(),
256            meta: HashMap::new(),
257        };
258        let oca_build = oca_bundle::build::from_ast(None, &ast).unwrap();
259
260        let result = build_core_db_model(&oca_build);
261        assert_eq!(result.len(), 4);
262        assert_eq!(
263            result[0].command.clone().unwrap().digest.to_string(),
264            "EJnPP-iVzRutbWJF4tt3pX_89NB77854DMyYdl_aVaWH"
265        );
266        assert!(result[0].oca_bundle.is_some());
267        assert!(result[0].capture_base.is_some());
268        assert!(result[0].overlay.is_none());
269
270        assert!(result[3].oca_bundle.is_some());
271        assert!(result[3].capture_base.is_none());
272        assert!(result[3].overlay.is_some());
273
274        Ok(())
275    }
276}