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 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 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 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 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 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 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}