1use std::io::prelude::*;
2
3use exocore_protos::{
4 core::{cell_application_config, cell_node_config, CellApplicationConfig, NodeConfig},
5 generated::{
6 exocore_apps::Manifest,
7 exocore_core::{
8 node_cell_config, CellConfig, CellNodeConfig, LocalNodeConfig, NodeCellConfig,
9 },
10 },
11};
12
13use super::Error;
14
15pub trait LocalNodeConfigExt {
17 fn config(&self) -> &LocalNodeConfig;
18
19 fn read_yaml<R: Read>(reader: R) -> Result<LocalNodeConfig, Error>;
20
21 fn to_yaml_string(&self) -> Result<String, Error>;
22
23 fn write_yaml<W: Write>(&self, write: W) -> Result<(), Error>;
24
25 fn create_cell_node_config(&self, roles: Vec<cell_node_config::Role>) -> CellNodeConfig;
26
27 fn add_cell(&mut self, cell: NodeCellConfig);
28}
29
30impl LocalNodeConfigExt for LocalNodeConfig {
31 fn config(&self) -> &LocalNodeConfig {
32 self
33 }
34
35 fn read_yaml<R: Read>(reader: R) -> Result<LocalNodeConfig, Error> {
36 let config = serde_yaml::from_reader(reader)
37 .map_err(|err| Error::Config(anyhow!("Couldn't decode YAML node config: {}", err)))?;
38
39 Ok(config)
40 }
41
42 fn to_yaml_string(&self) -> Result<String, Error> {
43 serde_yaml::to_string(self.config())
44 .map_err(|err| Error::Config(anyhow!("Couldn't encode node config to YAML: {}", err)))
45 }
46
47 fn write_yaml<W: Write>(&self, write: W) -> Result<(), Error> {
48 serde_yaml::to_writer(write, self.config())
49 .map_err(|err| Error::Config(anyhow!("Couldn't encode node config to YAML: {}", err)))
50 }
51
52 fn create_cell_node_config(&self, roles: Vec<cell_node_config::Role>) -> CellNodeConfig {
53 let node_config = self.config();
54 CellNodeConfig {
55 node: Some(NodeConfig {
56 public_key: node_config.public_key.clone(),
57 id: node_config.id.clone(),
58 name: node_config.name.clone(),
59 addresses: node_config.addresses.clone(),
60 }),
61 roles: roles.into_iter().map(|r| r.into()).collect(),
62 }
63 }
64
65 fn add_cell(&mut self, cell: NodeCellConfig) {
66 self.cells.retain(|other| other.id != cell.id);
67 self.cells.push(cell);
68 }
69}
70
71pub trait CellNodeConfigExt {
73 fn config(&self) -> &CellNodeConfig;
74
75 fn to_yaml_string(&self) -> Result<String, Error>;
76
77 fn read_yaml<R: Read>(reader: R) -> Result<CellNodeConfig, Error>;
78}
79
80impl CellNodeConfigExt for CellNodeConfig {
81 fn config(&self) -> &CellNodeConfig {
82 self
83 }
84
85 fn to_yaml_string(&self) -> Result<String, Error> {
86 serde_yaml::to_string(self.config()).map_err(|err| {
87 Error::Config(anyhow!("Couldn't encode cell node config to YAML: {}", err))
88 })
89 }
90
91 fn read_yaml<R: Read>(reader: R) -> Result<CellNodeConfig, Error> {
92 let config = serde_yaml::from_reader(reader).map_err(|err| {
93 Error::Config(anyhow!("Couldn't decode YAML cell node config: {}", err))
94 })?;
95
96 Ok(config)
97 }
98}
99
100pub trait CellConfigExt {
101 fn config(&self) -> &CellConfig;
102
103 fn read_yaml<R: Read>(reader: R) -> Result<CellConfig, Error>;
104
105 fn to_yaml_string(&self) -> Result<String, Error>;
106
107 fn write_yaml<W: Write>(&self, writer: W) -> Result<(), Error>;
108
109 fn from_node_cell(config: &NodeCellConfig) -> Result<CellConfig, Error>;
110
111 fn find_node(&mut self, node_pk: &str) -> Option<&mut CellNodeConfig>;
112
113 fn add_node(&mut self, node: CellNodeConfig);
114
115 fn add_application(&mut self, cell_app: CellApplicationConfig);
116}
117
118impl CellConfigExt for CellConfig {
119 fn config(&self) -> &CellConfig {
120 self
121 }
122
123 fn read_yaml<R: Read>(reader: R) -> Result<CellConfig, Error> {
124 let config: CellConfig = serde_yaml::from_reader(reader)
125 .map_err(|err| Error::Config(anyhow!("Couldn't decode YAML cell config: {}", err)))?;
126
127 Ok(config)
128 }
129
130 fn to_yaml_string(&self) -> Result<String, Error> {
131 serde_yaml::to_string(self.config())
132 .map_err(|err| Error::Config(anyhow!("Couldn't encode cell config to YAML: {}", err)))
133 }
134
135 fn write_yaml<W: Write>(&self, writer: W) -> Result<(), Error> {
136 serde_yaml::to_writer(writer, self.config())
137 .map_err(|err| Error::Config(anyhow!("Couldn't encode cell config to YAML: {}", err)))
138 }
139
140 fn from_node_cell(config: &NodeCellConfig) -> Result<CellConfig, Error> {
141 match &config.location {
142 Some(node_cell_config::Location::Inline(cell_config)) => Ok(cell_config.clone()),
143 other => Err(Error::Config(anyhow!(
144 "Invalid cell instance config: {:?}",
145 other
146 ))),
147 }
148 }
149
150 fn find_node(&mut self, node_pk: &str) -> Option<&mut CellNodeConfig> {
151 self.nodes.iter_mut().find(|cell_node| {
152 cell_node
153 .node
154 .as_ref()
155 .map_or(false, |n| n.public_key == node_pk)
156 })
157 }
158
159 fn add_node(&mut self, node: CellNodeConfig) {
160 let Some(node_pk) = node.node.as_ref().map(|n| n.public_key.as_str()) else {
161 return;
162 };
163
164 if let Some(cell_node) = self.find_node(node_pk) {
166 *cell_node = node;
167 return;
168 }
169
170 self.nodes.push(node);
172 }
173
174 fn add_application(&mut self, cell_app: CellApplicationConfig) {
176 for existing_cell_app in &mut self.apps {
178 if cell_app.public_key == existing_cell_app.public_key {
179 *existing_cell_app = cell_app;
180 return;
181 }
182 }
183
184 self.apps.push(cell_app);
185 }
186}
187
188pub trait NodeConfigExt {
190 fn read_yaml<R: Read>(reader: R) -> Result<NodeConfig, Error>;
191
192 fn to_yaml_string(&self) -> Result<String, Error>;
193}
194
195impl NodeConfigExt for NodeConfig {
196 fn read_yaml<R: Read>(reader: R) -> Result<NodeConfig, Error> {
197 let config: NodeConfig = serde_yaml::from_reader(reader)
198 .map_err(|err| Error::Config(anyhow!("Couldn't decode YAML node config: {}", err)))?;
199
200 Ok(config)
201 }
202
203 fn to_yaml_string(&self) -> Result<String, Error> {
204 serde_yaml::to_string(self).map_err(|err| {
205 Error::Config(anyhow!("Couldn't encode cell node config to YAML: {}", err))
206 })
207 }
208}
209
210pub trait CellApplicationConfigExt {
212 fn from_manifest(manifest: Manifest) -> CellApplicationConfig;
213}
214
215impl CellApplicationConfigExt for CellApplicationConfig {
216 fn from_manifest(manifest: Manifest) -> CellApplicationConfig {
217 CellApplicationConfig {
218 name: manifest.name.clone(),
219 version: manifest.version.clone(),
220 public_key: manifest.public_key.clone(),
221 package_url: String::new(),
222 location: Some(cell_application_config::Location::Inline(manifest)),
223 }
224 }
225}
226
227pub trait ManifestExt {
229 fn manifest(&self) -> &Manifest;
230
231 fn read_yaml<R: Read>(reader: R) -> Result<Manifest, Error>;
232
233 fn write_yaml<W: Write>(&self, writer: W) -> Result<(), Error>;
234}
235
236impl ManifestExt for Manifest {
237 fn manifest(&self) -> &Manifest {
238 self
239 }
240
241 fn read_yaml<R: Read>(reader: R) -> Result<Manifest, Error> {
242 let config: Manifest = serde_yaml::from_reader(reader)
243 .map_err(|err| Error::Config(anyhow!("Couldn't decode YAML manifest: {}", err)))?;
244
245 Ok(config)
246 }
247
248 fn write_yaml<W: Write>(&self, writer: W) -> Result<(), Error> {
249 serde_yaml::to_writer(writer, self.manifest()).map_err(|err| {
250 Error::Config(anyhow!(
251 "Couldn't encode application manifest to YAML: {}",
252 err
253 ))
254 })
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use exocore_protos::{
261 core::{
262 cell_application_config, CellApplicationConfig, ChainConfig, EntityIndexConfig,
263 MutationIndexConfig, NodeAddresses,
264 },
265 generated::exocore_core::{
266 cell_node_config, node_cell_config, CellConfig, CellNodeConfig, LocalNodeConfig,
267 NodeCellConfig, NodeConfig,
268 },
269 };
270
271 use super::{
272 super::{Cell, CellNodes},
273 *,
274 };
275 use crate::{dir::os::OsDirectory, tests_utils::find_test_fixture};
276
277 #[test]
278 fn parse_node_config_yaml_ser_deser() -> anyhow::Result<()> {
279 use exocore_protos::generated::exocore_core::NodeStoreConfig;
280
281 let conf_ser = LocalNodeConfig {
282 keypair: "keypair".to_string(),
283 public_key: "pk".to_string(),
284 name: "node_name".to_string(),
285 id: String::new(),
286 cells: vec![
287 NodeCellConfig {
288 id: "cell1".into(),
289 location: Some(node_cell_config::Location::Inline(CellConfig {
290 public_key: "pk".to_string(),
291 keypair: "kp".to_string(),
292 name: "cell_name".to_string(),
293 id: String::new(),
294 nodes: vec![CellNodeConfig {
295 node: Some(NodeConfig {
296 public_key: "pk".to_string(),
297 name: "node_name".to_string(),
298 id: String::new(),
299 addresses: Some(NodeAddresses {
300 p2p: vec!["maddr".to_string()],
301 http: vec!["http_addr".to_string()],
302 }),
303 }),
304 roles: vec![cell_node_config::Role::ChainRole.into()],
305 }],
306 apps: vec![
307 CellApplicationConfig {
308 name: "app1".to_string(),
309 version: "0.0.1".to_string(),
310 public_key: "pk1".to_string(),
311 package_url: "https://somewhere/package.zip".to_string(),
312 location: Some(cell_application_config::Location::Inline(
313 Manifest {
314 name: "app1".to_string(),
315 ..Default::default()
316 },
317 )),
318 },
319 CellApplicationConfig {
320 name: "app2".to_string(),
321 version: "0.0.1".to_string(),
322 public_key: "pk2".to_string(),
323 package_url: "https://somewhere/package.zip".to_string(),
324 location: None,
325 },
326 ],
327 })),
328 },
329 NodeCellConfig {
330 id: "cell2".into(),
331 location: None,
332 },
333 ],
334 addresses: Some(NodeAddresses {
335 p2p: vec!["maddr".to_string()],
336 http: vec!["http_addr".to_string()],
337 }),
338 listen_addresses: Some(NodeAddresses {
339 p2p: vec!["listen_maddr".to_string()],
340 http: vec!["listen_http_addr".to_string()],
341 }),
342 store: Some(NodeStoreConfig {
343 index: Some(EntityIndexConfig {
344 chain_index_min_depth: Some(3),
345 chain_index_depth_leeway: Some(10),
346 pending_index: Some(MutationIndexConfig {
347 indexer_num_threads: Some(2),
348 indexer_heap_size_bytes: Some(30_000_000),
349 entity_mutations_cache_size: Some(2000),
350 }),
351 chain_index: Some(MutationIndexConfig {
352 indexer_num_threads: Some(2),
353 indexer_heap_size_bytes: Some(30_000_000),
354 entity_mutations_cache_size: Some(2000),
355 }),
356 ..Default::default()
357 }),
358 query_parallelism: Some(5),
359 }),
360 chain: Some(ChainConfig {
361 segment_max_size: Some(1_000),
362 segment_max_open_mmap: Some(2),
363 }),
364 };
365
366 let conf_yaml = conf_ser.to_yaml_string()?;
367 let conf_deser = LocalNodeConfig::read_yaml(conf_yaml.as_bytes())?;
368 assert_eq!(conf_ser, conf_deser);
369
370 Ok(())
371 }
372
373 #[test]
374 fn parse_node_config_example_yaml_file() -> anyhow::Result<()> {
375 let node_path = find_test_fixture("examples/node");
376 let dir = OsDirectory::new(node_path);
379 let (cells, node) = Cell::from_local_node_directory(dir)?;
380 assert_eq!(1, cells.len());
381 assert_eq!(2, node.p2p_addresses().len());
382
383 {
384 let cell = cells[0].clone().unwrap_full();
386
387 {
388 let nodes = cell.cell().nodes();
389 assert_eq!(2, nodes.count());
390 }
391
392 {
393 let schemas = cell
394 .cell()
395 .schemas()
396 .get_message_descriptor("exocore.example_app.Task");
397 assert!(schemas.is_ok());
398 }
399 }
400
401 Ok(())
402 }
403
404 #[test]
405 fn write_node_config_to_yaml_read_write() -> anyhow::Result<()> {
406 let config_init = LocalNodeConfig {
407 name: "node_name".to_string(),
408 ..Default::default()
409 };
410
411 let mut bytes = Vec::new();
412
413 config_init.write_yaml(&mut bytes)?;
414
415 let config_read = LocalNodeConfig::read_yaml(bytes.as_slice())?;
416
417 assert_eq!(config_init, config_read);
418
419 Ok(())
420 }
421
422 #[test]
423 fn node_config_add_cell() {
424 let mut config = LocalNodeConfig::default();
425
426 config.add_cell(NodeCellConfig {
427 id: "id1".into(),
428 ..Default::default()
429 });
430 assert_eq!(1, config.cells.len());
431
432 config.add_cell(NodeCellConfig {
433 id: "id1".into(),
434 ..Default::default()
435 });
436 assert_eq!(1, config.cells.len());
437
438 config.add_cell(NodeCellConfig {
439 id: "id2".into(),
440 ..Default::default()
441 });
442 assert_eq!(2, config.cells.len());
443 }
444
445 #[test]
446 fn cell_config_yaml_read_write() -> anyhow::Result<()> {
447 let config_init = CellConfig {
448 ..Default::default()
449 };
450
451 let mut bytes = Vec::new();
452 config_init.write_yaml(&mut bytes)?;
453
454 let config_read = CellConfig::read_yaml(bytes.as_slice())?;
455
456 assert_eq!(config_init, config_read);
457
458 Ok(())
459 }
460
461 #[test]
462 fn cell_config_add_node() {
463 let mut config = CellConfig {
464 ..Default::default()
465 };
466
467 let node1 = CellNodeConfig {
468 node: Some(NodeConfig {
469 public_key: "pk1".to_string(),
470 ..Default::default()
471 }),
472 ..Default::default()
473 };
474
475 config.add_node(node1);
476 assert_eq!(config.nodes.len(), 1);
477
478 let node1_changed = CellNodeConfig {
479 node: Some(NodeConfig {
480 public_key: "pk1".to_string(),
481 name: "new name".to_string(),
482 ..Default::default()
483 }),
484 ..Default::default()
485 };
486 config.add_node(node1_changed);
487 assert_eq!(config.nodes.len(), 1);
488 assert_eq!("new name", config.nodes[0].node.as_ref().unwrap().name);
489
490 let node2 = CellNodeConfig {
491 node: Some(NodeConfig {
492 public_key: "pk2".to_string(),
493 ..Default::default()
494 }),
495 ..Default::default()
496 };
497
498 config.add_node(node2);
499 assert_eq!(config.nodes.len(), 2);
500 }
501
502 #[test]
503 fn cell_config_add_app() {
504 let mut config = CellConfig {
505 ..Default::default()
506 };
507
508 config.add_application(CellApplicationConfig {
509 name: "name".to_string(),
510 version: "0.0.1".to_string(),
511 public_key: "pk1".to_string(),
512 package_url: "https://some/location/package.zip".to_string(),
513 location: Some(cell_application_config::Location::Inline(Manifest {
514 ..Default::default()
515 })),
516 });
517 assert_eq!(config.apps.len(), 1);
518
519 config.add_application(CellApplicationConfig {
520 name: "name".to_string(),
521 version: "0.0.1".to_string(),
522 public_key: "pk1".to_string(),
523 package_url: "https://some/location/package.zip".to_string(),
524 location: Some(cell_application_config::Location::Inline(Manifest {
525 ..Default::default()
526 })),
527 });
528 assert_eq!(config.apps.len(), 1);
529
530 config.add_application(CellApplicationConfig {
531 name: "name".to_string(),
532 version: "0.0.1".to_string(),
533 public_key: "pk2".to_string(),
534 package_url: "https://some/location/package.zip".to_string(),
535 location: Some(cell_application_config::Location::Inline(Manifest {
536 ..Default::default()
537 })),
538 });
539 assert_eq!(config.apps.len(), 2);
540 }
541}