1use indexmap::IndexMap;
2use log::info;
3use overlay::{Overlay, OverlayModel};
4use overlay_file::overlay_registry::{OverlayLocalRegistry, OverlayRegistry};
5pub use said::derivation::{HashFunction, HashFunctionCode};
6pub use said::error;
7pub use said::{ProtocolVersion, SelfAddressingIdentifier, make_me_sad};
8use serde::ser::Error;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use thiserror::Error;
12pub mod capture_base;
13pub mod overlay;
14use crate::state::{attribute::Attribute, oca_bundle::capture_base::CaptureBase};
15use oca_ast::ast::{CaptureContent, Command, CommandType, OCAAst, ObjectKind, OverlayContent};
16
17#[derive(Serialize, Deserialize, Debug, Clone)]
18pub struct OCABundle {
19 #[serde(rename = "v")]
21 pub version: String,
22 pub digest: Option<said::SelfAddressingIdentifier>,
23 pub capture_base: CaptureBase,
24 pub overlays: Vec<Overlay>,
25}
26
27#[derive(Serialize, Debug, Deserialize, Clone, Default)]
28pub struct OCABundleModel {
29 #[serde(rename = "v")]
31 pub version: String,
32 pub digest: Option<said::SelfAddressingIdentifier>,
33 pub capture_base: CaptureBase,
34 pub overlays: Vec<OverlayModel>,
35 #[serde(skip)]
37 pub attributes: Option<HashMap<String, Attribute>>,
38}
39
40impl From<OCABundleModel> for OCABundle {
41 fn from(model: OCABundleModel) -> Self {
42 OCABundle {
43 version: model.version.clone(),
44 digest: model.digest.clone(),
45 capture_base: model.capture_base.clone(),
46 overlays: model.overlays.iter().map(Overlay::from).collect(),
47 }
48 }
49}
50
51pub struct OCABundleWithRegistry {
52 pub bundle: OCABundle,
53 pub registry: OverlayLocalRegistry,
54}
55
56impl From<OCABundleWithRegistry> for OCABundleModel {
57 fn from(br: OCABundleWithRegistry) -> Self {
58 OCABundleModel {
59 version: br.bundle.version.clone(),
60 digest: br.bundle.digest.clone(),
61 capture_base: br.bundle.capture_base.clone(),
62 overlays: br
63 .bundle
64 .overlays
65 .iter()
66 .map(|om| {
67 let mut overlay = om.model.clone();
68 overlay.overlay_def = br.registry.get_overlay(&overlay.name).ok().cloned();
69 overlay
70 })
71 .collect(),
72 attributes: None,
73 }
74 }
75}
76
77impl OCABundleModel {
78 pub fn new(capture_base: CaptureBase, overlays: Vec<OverlayModel>) -> Self {
79 OCABundleModel {
80 version: "".to_string(),
81 digest: None,
82 capture_base,
83 overlays,
84 attributes: None,
85 }
86 }
87
88 pub fn to_ast(&self) -> OCAAst {
89 let mut ast = OCAAst::new();
90
91 let mut attributes = IndexMap::new();
92 self.capture_base
93 .attributes
94 .iter()
95 .for_each(|(attr_name, attr_type)| {
96 attributes.insert(attr_name.clone(), attr_type.clone());
97 });
98
99 let command = Command {
100 kind: CommandType::Add,
101 object_kind: ObjectKind::CaptureBase(CaptureContent {
102 attributes: Some(self.capture_base.attributes.clone()),
103 }),
104 };
105 ast.commands.push(command);
106
107 self.overlays.iter().for_each(|overlay| {
108 if let Some(overlay_def) = &overlay.overlay_def {
109 let overlay_content = OverlayContent {
110 overlay_def: overlay_def.clone(),
111 properties: overlay.properties.clone(),
112 };
113 let overlay_command = Command {
114 kind: CommandType::Add,
115 object_kind: ObjectKind::Overlay(overlay_content),
116 };
117 ast.commands.push(overlay_command);
118 }
119 });
120 ast
121 }
122
123 pub fn fill_attributes(&mut self) {
125 self.attributes = Some(HashMap::new());
127 if let Some(ref mut attrs) = self.attributes {
128 for (attr_name, attr_type) in self.capture_base.attributes.clone() {
129 let attr = Attribute {
130 name: attr_name.clone(),
131 attribute_type: Some(attr_type),
132 ..Default::default()
133 };
134 attrs.insert(attr_name.clone(), attr);
135 }
136 }
137 }
138
139 pub fn remove_attribute(&mut self, attr_name: &String) {
142 self.capture_base.attributes.shift_remove(attr_name);
143 }
144
145 pub fn get_attribute_by_name(&self, name: &str) -> Option<&Attribute> {
146 self.attributes.as_ref().and_then(|attrs| attrs.get(name))
147 }
148
149 pub fn compute_and_fill_digest(
159 &mut self,
160 ) -> Result<said::SelfAddressingIdentifier, OCABundleSerializationError> {
161 info!("Computing digest for OCABundle");
163 match self.capture_base.fill_digest() {
165 Ok(_) => info!("Capture base digest filled successfully"),
166 Err(e) => {
167 return Err(OCABundleSerializationError::SerializationError(
168 e.to_string(),
169 ));
170 }
171 }
172 let cb_said = self.capture_base.digest.clone();
173 info!("Capture base SAID: {:?}", cb_said);
174 for overlay in &mut self.overlays {
175 overlay.capture_base_said = cb_said.clone();
176 match overlay.fill_digest() {
178 Ok(_) => info!("Overlay {} digest filled successfully", overlay.name),
179 Err(e) => {
180 return Err(OCABundleSerializationError::SerializationError(
181 e.to_string(),
182 ));
183 }
184 }
185 }
186
187 let oca_bundle = OCABundle::from(self.clone());
188 let serialized_bundle = serde_json::to_string(&oca_bundle)
189 .map_err(|_| serde_json::Error::custom("Failed to serialize OCABundleModel"))
190 .unwrap();
191
192 let code = HashFunctionCode::Blake3_256;
193 let said_field = Some("digest");
194 let version = ProtocolVersion::new("OCAS", 2, 0).unwrap();
195 let input = serialized_bundle.as_str();
196 match make_me_sad(input, code, version, said_field) {
197 Ok(sad) => {
198 #[derive(Deserialize)]
199 struct OCABundlePartial {
200 digest: String,
201 #[serde(rename = "v")]
202 version: String,
203 }
204
205 let bundle: OCABundlePartial = serde_json::from_str(&sad)
206 .map_err(|e| OCABundleSerializationError::SerializationError(e.to_string()))?;
207 let said: SelfAddressingIdentifier = bundle.digest.parse().map_err(|_| {
208 OCABundleSerializationError::SerializationError(
209 "Failed to parse SAID".to_string(),
210 )
211 })?;
212 self.digest = Some(said.clone());
213 self.version = bundle.version;
214 Ok(said)
215 }
216 Err(_) => Err(OCABundleSerializationError::SerializationError(
217 "Failed to compute digest for OCABundle".to_string(),
218 )),
219 }
220 }
221}
222#[derive(Error, Debug)]
223pub enum OCABundleSerializationError {
224 #[error("Failed to serialize OCA bundle: {0}")]
225 SerializationError(String),
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use crate::build::from_ast;
232 use oca_file::ocafile::parse_from_string;
233 use overlay_file::overlay_registry::OverlayLocalRegistry;
234
235 #[test]
236 fn build_oca_bundle() {
237 let _ = env_logger::builder().is_test(true).try_init();
238 let unparsed_file = r#"
239-- version=2.0.0
240-- name=プラスウルトラ
241ADD ATTRIBUTE remove=Text
242ADD ATTRIBUTE name=Text age=Numeric car=[refs:EJeWVGxkqxWrdGi0efOzwg1YQK8FrA-ZmtegiVEtAVcu]
243REMOVE ATTRIBUTE remove
244ADD ATTRIBUTE incidentals_spare_parts=[[refs:EJeWVGxkqxWrdGi0efOzwg1YQK8FrA-ZmtegiVEtAVcu]]
245ADD ATTRIBUTE d=Text i=Text passed=Boolean
246ADD Overlay META
247 language="en"
248 description="Entrance credential"
249 name="Entrance credential"
250ADD Overlay CHARACTER_ENCODING
251 attribute_character_encodings
252 d="utf-8"
253 i="utf-8"
254 passed="utf-8"
255ADD Overlay CONFORMANCE
256 attribute_conformances=["d", "i", "passed"]
257ADD Overlay LABEL
258 language="en"
259 attribute_labels
260 d="Schema digest"
261 i="Credential Issuee"
262 passed="Passed"
263ADD Overlay FORMAT
264 attribute_formats
265 d="image/jpeg"
266ADD Overlay UNIT
267 metric_system="SI"
268 attribute_units
269 i="m^2"
270 d="°"
271ADD ATTRIBUTE list=[Text] el=Text
272ADD Overlay CARDINALITY
273 attribute_cardinalities
274 list="1-2"
275ADD Overlay ENTRY_CODE
276 attribute_entry_codes
277 list=refs:EJeWVGxkqxWrdGi0efOzwg1YQK8FrA-ZmtegiVEtAVcu
278 el=["o1", "o2", "o3"]
279ADD Overlay ENTRY
280 language="en"
281 attribute_entries
282 list=refs:EJeWVGxkqxWrdGi0efOzwg1YQK8FrA-ZmtegiVEtAVcu
283 el
284 o1="o1_label"
285 o2="o2_label"
286 o3="o3_label"
287"#;
288 let registry = OverlayLocalRegistry::from_dir("../overlay-file/core_overlays/").unwrap();
289 let oca_ast = parse_from_string(unparsed_file.to_string(), ®istry).unwrap();
290
291 let bundle_json = r#"
292 {"v":"OCAS02JSON0009d7_","digest":"ECQDVFB4dcPgHfMQXg-9xDpeBr8_-iZzy6ermbBMcj50","capture_base":{"digest":"EMDyoUr57UN7-Wy3kmF0WyG0xiQieckUdW18VGdEuve9","type":"capture_base/2.0.0","attributes":{"age":"Numeric","car":["refs:EJeWVGxkqxWrdGi0efOzwg1YQK8FrA-ZmtegiVEtAVcu"],"d":"Text","el":"Text","i":"Text","incidentals_spare_parts":[["refs:EJeWVGxkqxWrdGi0efOzwg1YQK8FrA-ZmtegiVEtAVcu"]],"list":["Text"],"name":"Text","passed":"Boolean"}},"overlays":[{"digest":"EEk6wQBfPuqddeVOPFLgSY9qv1ZorGCvip_oQtFdD9GV","capture_base":"EMDyoUr57UN7-Wy3kmF0WyG0xiQieckUdW18VGdEuve9","type":"overlay/meta/2.0.0","language":"en","description":"Entrance credential","name":"Entrance credential"},{"digest":"EPVOc4fR5Nwe2yHzFS-4wBf3kcm7C5D4XNjY9cxnFaQh","capture_base":"EMDyoUr57UN7-Wy3kmF0WyG0xiQieckUdW18VGdEuve9","type":"overlay/character_encoding/2.0.0","attribute_character_encodings":{"d":"utf-8","i":"utf-8","passed":"utf-8"}},{"digest":"EPB8YG6m6Q_uOWNKZp30qkN3_UlvTvLiTK_mm7ncCMiH","capture_base":"EMDyoUr57UN7-Wy3kmF0WyG0xiQieckUdW18VGdEuve9","type":"overlay/conformance/2.0.0","attribute_conformances":["d","i","passed"]},{"digest":"EEy4mJ4SIxauAyk8FI1QqBa26qG1Fqn2uhN_Vf4RMIbL","capture_base":"EMDyoUr57UN7-Wy3kmF0WyG0xiQieckUdW18VGdEuve9","type":"overlay/label/2.0.0","language":"en","attribute_labels":{"d":"Schema digest","i":"Credential Issuee","passed":"Passed"}},{"digest":"EJi35V6qV5tUhnjDR3qiB2irAKLkbQVu-rU_hehkhop1","capture_base":"EMDyoUr57UN7-Wy3kmF0WyG0xiQieckUdW18VGdEuve9","type":"overlay/format/2.0.0","attribute_formats":{"d":"image/jpeg"}},{"digest":"EPdl6CuC9i9IszrkqvEkv9qZPM-WnX47DOD80dwGiHpL","capture_base":"EMDyoUr57UN7-Wy3kmF0WyG0xiQieckUdW18VGdEuve9","type":"overlay/unit/2.0.0","metric_system":"SI","attribute_units":{"i":"m^2","d":"°"}},{"digest":"EFbS7GQMBi_RCk2Q8cJKR2ohCE--248bH1OQnwiFzmer","capture_base":"EMDyoUr57UN7-Wy3kmF0WyG0xiQieckUdW18VGdEuve9","type":"overlay/cardinality/2.0.0","attribute_cardinalities":{"list":"1-2"}},{"digest":"ED6ktKLPYEmJfYTEo7-YR-xyPwHUgpEOdEwOe_Kr6c22","capture_base":"EMDyoUr57UN7-Wy3kmF0WyG0xiQieckUdW18VGdEuve9","type":"overlay/entry_code/2.0.0","attribute_entry_codes":{"list":"refs:EJeWVGxkqxWrdGi0efOzwg1YQK8FrA-ZmtegiVEtAVcu","el":["o1","o2","o3"]}},{"digest":"EIMaWbfJ98gO1sTucmYdgaZu_u94djMa75BYl8lzkvfc","capture_base":"EMDyoUr57UN7-Wy3kmF0WyG0xiQieckUdW18VGdEuve9","type":"overlay/entry/2.0.0","language":"en","attribute_entries":{"list":"refs:EJeWVGxkqxWrdGi0efOzwg1YQK8FrA-ZmtegiVEtAVcu","el":{"o1":"o1_label","o2":"o2_label","o3":"o3_label"}}}]}
293"#;
294 let reference_json: serde_json::Value = serde_json::from_str(bundle_json).unwrap();
295 let mut oca_bundle = from_ast(None, &oca_ast).unwrap().oca_bundle;
296 let mut oca_bundle2 = from_ast(None, &oca_ast).unwrap().oca_bundle;
297 oca_bundle.compute_and_fill_digest().unwrap();
298 oca_bundle2.compute_and_fill_digest().unwrap();
299
300 let mut overlay_model = oca_bundle.overlays.first().unwrap().clone();
301 match overlay_model.fill_digest() {
304 Ok(_) => info!("Overlay model digest filled successfully"),
305 Err(e) => panic!("Failed to fill overlay model digest: {}", e),
306 }
307 let meta_said = overlay_model.digest.clone().unwrap();
308 let ref_said = "EEk6wQBfPuqddeVOPFLgSY9qv1ZorGCvip_oQtFdD9GV";
309 assert_eq!(meta_said.to_string(), ref_said.to_string());
310 assert_eq!(oca_bundle.version, "OCAS02JSON0009d7_");
311
312 let said = oca_bundle.digest.clone().unwrap();
313 let bundle = OCABundle::from(oca_bundle.clone());
314 let oca_bundle_json = serde_json::to_string(&bundle).unwrap();
315
316 println!("OCA Bundle JSON: \n{}", oca_bundle_json);
317
318 assert_eq!(
319 serde_json::from_str::<serde_json::Value>(&oca_bundle_json).unwrap(),
320 reference_json
321 );
322 let said2 = oca_bundle2.digest.unwrap();
323 assert_eq!(said, said2);
325 }
326}