1mod app_bundle;
11mod app_manifest;
12mod error;
13
14use crate::{dna::DnaBundle, prelude::*};
15pub use app_bundle::*;
16pub use app_manifest::app_manifest_validated::*;
17pub use app_manifest::*;
18use derive_more::Into;
19pub use error::*;
20use holo_hash::{AgentPubKey, DnaHash};
21use holochain_serialized_bytes::prelude::*;
22use holochain_util::ffs;
23use holochain_zome_types::cell::CloneId;
24use holochain_zome_types::prelude::*;
25use indexmap::IndexMap;
26use std::{collections::HashMap, path::PathBuf};
27
28pub type InstalledAppId = String;
30
31#[derive(Debug, serde::Serialize, serde::Deserialize)]
33#[serde(tag = "type", content = "value", rename_all = "snake_case")]
34pub enum DnaSource {
35 Path(PathBuf),
37 Bundle(Box<DnaBundle>),
39 Hash(DnaHash),
41}
42
43#[derive(Debug, serde::Serialize, serde::Deserialize)]
45#[serde(tag = "type", content = "value", rename_all = "snake_case")]
46pub enum CoordinatorSource {
47 Path(PathBuf),
49 Bundle(Box<CoordinatorBundle>),
51}
52
53#[derive(Debug, serde::Serialize, serde::Deserialize)]
55pub struct RegisterDnaPayload {
56 #[serde(default)]
58 pub modifiers: DnaModifiersOpt<YamlProperties>,
59 pub source: DnaSource,
61}
62
63#[derive(Debug, serde::Serialize, serde::Deserialize)]
64pub struct UpdateCoordinatorsPayload {
66 pub dna_hash: DnaHash,
68 pub source: CoordinatorSource,
70}
71
72#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
74pub struct CreateCloneCellPayload {
75 pub role_name: RoleName,
77 pub modifiers: DnaModifiersOpt<YamlProperties>,
81 pub membrane_proof: Option<MembraneProof>,
83 pub name: Option<String>,
85}
86
87#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
89pub struct DisableCloneCellPayload {
90 pub clone_cell_id: CloneCellId,
92}
93
94pub type EnableCloneCellPayload = DisableCloneCellPayload;
96
97#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
99pub struct DeleteCloneCellPayload {
100 pub app_id: InstalledAppId,
102
103 pub clone_cell_id: CloneCellId,
105}
106
107#[derive(Debug, serde::Serialize, serde::Deserialize)]
109pub struct InstallAppPayload {
110 pub source: AppBundleSource,
113
114 #[serde(default)]
136 pub agent_key: Option<AgentPubKey>,
137
138 #[serde(default)]
141 pub installed_app_id: Option<InstalledAppId>,
142
143 #[serde(default)]
149 pub network_seed: Option<NetworkSeed>,
150
151 #[serde(default)]
154 pub roles_settings: Option<RoleSettingsMap>,
155
156 #[serde(default)]
160 pub ignore_genesis_failure: bool,
161
162 #[serde(default)]
169 pub allow_throwaway_random_agent_key: bool,
170}
171
172pub type MemproofMap = HashMap<RoleName, MembraneProof>;
174pub type ModifiersMap = HashMap<RoleName, DnaModifiersOpt<YamlProperties>>;
176pub type ExistingCellsMap = HashMap<RoleName, CellId>;
178pub type RoleSettingsMap = HashMap<RoleName, RoleSettings>;
180pub type RoleSettingsMapYaml = HashMap<RoleName, RoleSettingsYaml>;
182
183#[derive(Debug, serde::Serialize, serde::Deserialize)]
185#[serde(tag = "type", content = "value", rename_all = "snake_case")]
186pub enum RoleSettings {
187 UseExisting {
190 cell_id: CellId,
192 },
193 Provisioned {
195 membrane_proof: Option<MembraneProof>,
201 modifiers: Option<DnaModifiersOpt<YamlProperties>>,
204 },
205}
206
207impl Default for RoleSettings {
208 fn default() -> Self {
209 Self::Provisioned {
210 membrane_proof: None,
211 modifiers: None,
212 }
213 }
214}
215
216impl From<RoleSettingsYaml> for RoleSettings {
217 fn from(role_settings: RoleSettingsYaml) -> Self {
218 match role_settings {
219 RoleSettingsYaml::Provisioned {
220 membrane_proof,
221 modifiers,
222 } => Self::Provisioned {
223 membrane_proof,
224 modifiers,
225 },
226 RoleSettingsYaml::UseExisting { cell_id } => Self::UseExisting { cell_id },
227 }
228 }
229}
230
231#[derive(Debug, serde::Serialize, serde::Deserialize)]
233#[serde(tag = "type", rename_all = "snake_case")]
234pub enum RoleSettingsYaml {
235 UseExisting {
238 cell_id: CellId,
240 },
241 Provisioned {
243 membrane_proof: Option<MembraneProof>,
249 modifiers: Option<DnaModifiersOpt<YamlProperties>>,
252 },
253}
254
255#[derive(Debug, serde::Serialize, serde::Deserialize)]
257#[serde(tag = "type", content = "value", rename_all = "snake_case")]
258pub enum AppBundleSource {
259 #[serde(with = "serde_bytes")]
261 Bytes(Vec<u8>),
262 Path(PathBuf),
264 }
267
268impl AppBundleSource {
269 pub async fn resolve(self) -> Result<AppBundle, AppBundleError> {
271 Ok(match self {
272 Self::Bytes(bytes) => AppBundle::decode(&bytes)?,
273 Self::Path(path) => AppBundle::decode(&ffs::read(&path).await?)?,
274 })
276 }
277}
278
279#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
281pub struct InstallAppDnaPayload {
282 pub hash: DnaHash,
284 pub role_name: RoleName,
286 pub membrane_proof: Option<MembraneProof>,
288}
289
290impl InstallAppDnaPayload {
291 pub fn hash_only(hash: DnaHash, role_name: RoleName) -> Self {
293 Self {
294 hash,
295 role_name,
296 membrane_proof: None,
297 }
298 }
299}
300
301#[derive(Clone, Debug, Into, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
303pub struct InstalledCell {
304 cell_id: CellId,
305 role_name: RoleName,
306}
307
308impl InstalledCell {
309 pub fn new(cell_id: CellId, role_name: RoleName) -> Self {
311 Self { cell_id, role_name }
312 }
313
314 pub fn into_id(self) -> CellId {
316 self.cell_id
317 }
318
319 pub fn into_role_name(self) -> RoleName {
321 self.role_name
322 }
323
324 pub fn into_inner(self) -> (CellId, RoleName) {
326 (self.cell_id, self.role_name)
327 }
328
329 pub fn as_id(&self) -> &CellId {
331 &self.cell_id
332 }
333
334 pub fn as_role_name(&self) -> &RoleName {
336 &self.role_name
337 }
338}
339
340#[derive(
343 Clone,
344 Debug,
345 PartialEq,
346 Eq,
347 serde::Serialize,
348 serde::Deserialize,
349 derive_more::Constructor,
350 shrinkwraprs::Shrinkwrap,
351)]
352#[shrinkwrap(mutable, unsafe_ignore_visibility)]
353pub struct InstalledApp {
354 #[shrinkwrap(main_field)]
355 app: InstalledAppCommon,
356 pub status: AppStatus,
358}
359
360impl InstalledApp {
361 pub fn new_fresh(app: InstalledAppCommon) -> Self {
363 Self {
364 app,
365 status: AppStatus::Disabled(DisabledAppReason::NeverStarted),
366 }
367 }
368
369 #[cfg(feature = "test_utils")]
371 pub fn new_running(app: InstalledAppCommon) -> Self {
372 Self {
373 app,
374 status: AppStatus::Running,
375 }
376 }
377
378 pub fn into_app_and_status(self) -> (InstalledAppCommon, AppStatus) {
381 (self.app, self.status)
382 }
383
384 pub fn status(&self) -> &AppStatus {
386 &self.status
387 }
388
389 pub fn id(&self) -> &InstalledAppId {
391 &self.app.installed_app_id
392 }
393}
394
395pub type InstalledAppMap = IndexMap<InstalledAppId, InstalledApp>;
397
398#[derive(
400 Clone,
401 Debug,
402 PartialEq,
403 Eq,
404 serde::Serialize,
405 serde::Deserialize,
406 derive_more::From,
407 shrinkwraprs::Shrinkwrap,
408)]
409#[shrinkwrap(mutable, unsafe_ignore_visibility)]
410pub struct RunningApp(InstalledAppCommon);
411
412impl RunningApp {
413 pub fn into_stopped(self, reason: StoppedAppReason) -> StoppedApp {
415 StoppedApp {
416 app: self.0,
417 reason,
418 }
419 }
420
421 pub fn into_common(self) -> InstalledAppCommon {
423 self.0
424 }
425}
426
427impl From<RunningApp> for InstalledApp {
428 fn from(app: RunningApp) -> Self {
429 Self {
430 app: app.into_common(),
431 status: AppStatus::Running,
432 }
433 }
434}
435
436#[derive(
438 Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, shrinkwraprs::Shrinkwrap,
439)]
440#[shrinkwrap(mutable, unsafe_ignore_visibility)]
441pub struct StoppedApp {
442 #[shrinkwrap(main_field)]
443 app: InstalledAppCommon,
444 reason: StoppedAppReason,
445}
446
447impl StoppedApp {
448 pub fn new_fresh(app: InstalledAppCommon) -> Self {
450 Self {
451 app,
452 reason: StoppedAppReason::Disabled(DisabledAppReason::NeverStarted),
453 }
454 }
455
456 pub fn from_app(app: &InstalledApp) -> Option<Self> {
459 StoppedAppReason::from_status(app.status()).map(|reason| Self {
460 app: app.as_ref().clone(),
461 reason,
462 })
463 }
464
465 pub fn into_active(self) -> RunningApp {
467 RunningApp(self.app)
468 }
469
470 pub fn into_common(self) -> InstalledAppCommon {
472 self.app
473 }
474}
475
476impl From<StoppedApp> for InstalledAppCommon {
477 fn from(d: StoppedApp) -> Self {
478 d.app
479 }
480}
481
482impl From<StoppedApp> for InstalledApp {
483 fn from(d: StoppedApp) -> Self {
484 Self {
485 app: d.app,
486 status: d.reason.into(),
487 }
488 }
489}
490
491#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
493pub struct InstalledAppCommon {
494 pub installed_app_id: InstalledAppId,
496
497 pub agent_key: AgentPubKey,
499
500 pub role_assignments: IndexMap<RoleName, AppRoleAssignment>,
502
503 pub manifest: AppManifest,
505
506 pub installed_at: Timestamp,
508}
509
510impl InstalledAppCommon {
511 pub fn new<S: ToString, I: IntoIterator<Item = (RoleName, AppRoleAssignment)>>(
513 installed_app_id: S,
514 agent_key: AgentPubKey,
515 role_assignments: I,
516 manifest: AppManifest,
517 installed_at: Timestamp,
518 ) -> AppResult<Self> {
519 let role_assignments: IndexMap<_, _> = role_assignments.into_iter().collect();
520 if let Some((illegal_role_name, _)) = role_assignments
522 .iter()
523 .find(|(role_name, _)| role_name.contains(CLONE_ID_DELIMITER))
524 {
525 return Err(AppError::IllegalRoleName(illegal_role_name.clone()));
526 }
527 Ok(InstalledAppCommon {
528 installed_app_id: installed_app_id.to_string(),
529 agent_key,
530 role_assignments,
531 manifest,
532 installed_at,
533 })
534 }
535
536 pub fn id(&self) -> &InstalledAppId {
538 &self.installed_app_id
539 }
540
541 pub fn provisioned_cells(&self) -> impl Iterator<Item = (&RoleName, CellId)> {
543 self.role_assignments
544 .iter()
545 .filter_map(|(role_name, role)| {
546 role.provisioned_dna_hash()
547 .map(|d| (role_name, CellId::new(d.clone(), self.agent_key.clone())))
548 })
549 }
550
551 pub fn clone_cells(&self) -> impl Iterator<Item = (&CloneId, CellId)> {
553 self.role_assignments
554 .iter()
555 .flat_map(|app_role_assignment| {
556 app_role_assignment
557 .1
558 .as_primary()
559 .into_iter()
560 .flat_map(|p| {
561 p.clones
562 .iter()
563 .map(|(id, d)| (id, CellId::new(d.clone(), self.agent_key.clone())))
564 })
565 })
566 }
567
568 pub fn disabled_clone_cells(&self) -> impl Iterator<Item = (&CloneId, CellId)> {
570 self.role_assignments
571 .iter()
572 .flat_map(|app_role_assignment| {
573 app_role_assignment
574 .1
575 .as_primary()
576 .into_iter()
577 .flat_map(|p| {
578 p.disabled_clones
579 .iter()
580 .map(|(id, d)| (id, CellId::new(d.clone(), self.agent_key.clone())))
581 })
582 })
583 }
584
585 pub fn clone_cells_for_role_name(
587 &self,
588 role_name: &RoleName,
589 ) -> Option<impl Iterator<Item = (&CloneId, CellId)>> {
590 Some(
591 self.role_assignments
592 .get(role_name)?
593 .as_primary()?
594 .clones
595 .iter()
596 .map(|(id, dna_hash)| (id, CellId::new(dna_hash.clone(), self.agent_key.clone()))),
597 )
598 }
599
600 pub fn disabled_clone_cells_for_role_name(
602 &self,
603 role_name: &RoleName,
604 ) -> Option<impl Iterator<Item = (&CloneId, CellId)>> {
605 Some(
606 self.role_assignments
607 .get(role_name)?
608 .as_primary()?
609 .disabled_clones
610 .iter()
611 .map(|(id, dna_hash)| (id, CellId::new(dna_hash.clone(), self.agent_key.clone()))),
612 )
613 }
614
615 pub fn clone_cell_ids(&self) -> impl Iterator<Item = CellId> + '_ {
617 self.clone_cells().map(|(_, cell_id)| cell_id)
618 }
619
620 pub fn disabled_clone_cell_ids(&self) -> impl Iterator<Item = CellId> + '_ {
622 self.disabled_clone_cells().map(|(_, cell_id)| cell_id)
623 }
624
625 pub fn all_cells(&self) -> impl Iterator<Item = CellId> + '_ {
631 self.provisioned_cells()
632 .map(|(_, c)| c)
633 .chain(self.clone_cell_ids())
634 .chain(self.disabled_clone_cell_ids())
635 }
636
637 pub fn all_enabled_cells(&self) -> impl Iterator<Item = CellId> + '_ {
641 self.provisioned_cells()
642 .map(|(_, c)| c)
643 .chain(self.clone_cell_ids())
644 }
645
646 pub fn required_cells(&self) -> impl Iterator<Item = CellId> + '_ {
652 self.provisioned_cells().map(|(_, c)| c)
653 }
654
655 pub fn role(&self, role_name: &RoleName) -> AppResult<&AppRoleAssignment> {
657 self.role_assignments
658 .get(role_name)
659 .ok_or_else(|| AppError::RoleNameMissing(role_name.clone()))
660 }
661
662 pub fn primary_role(&self, role_name: &RoleName) -> AppResult<&AppRolePrimary> {
665 let app_id = self.installed_app_id.clone();
666 self.role(role_name)?
667 .as_primary()
668 .ok_or_else(|| AppError::NonPrimaryCell(app_id, role_name.clone()))
669 }
670
671 fn role_mut(&mut self, role_name: &RoleName) -> AppResult<&mut AppRoleAssignment> {
672 self.role_assignments
673 .get_mut(role_name)
674 .ok_or_else(|| AppError::RoleNameMissing(role_name.clone()))
675 }
676
677 fn primary_role_mut(&mut self, role_name: &RoleName) -> AppResult<&mut AppRolePrimary> {
678 let app_id = self.installed_app_id.clone();
679 self.role_mut(role_name)?
680 .as_primary_mut()
681 .ok_or_else(|| AppError::NonPrimaryCell(app_id, role_name.clone()))
682 }
683
684 pub fn roles(&self) -> &IndexMap<RoleName, AppRoleAssignment> {
686 &self.role_assignments
687 }
688
689 pub fn primary_roles(&self) -> impl Iterator<Item = (&RoleName, &AppRolePrimary)> {
691 self.role_assignments
692 .iter()
693 .filter_map(|(name, role)| Some((name, role.as_primary()?)))
694 }
695
696 pub fn add_clone(&mut self, role_name: &RoleName, dna_hash: &DnaHash) -> AppResult<CloneId> {
698 let app_role_assignment = self.primary_role_mut(role_name)?;
699
700 if app_role_assignment.is_clone_limit_reached() {
701 return Err(AppError::CloneLimitExceeded(
702 app_role_assignment.clone_limit,
703 app_role_assignment.clone(),
704 ));
705 }
706 let clone_id = CloneId::new(role_name, app_role_assignment.next_clone_index);
707 if app_role_assignment.clones.contains_key(&clone_id) {
708 return Err(AppError::DuplicateCloneIds(clone_id));
709 }
710
711 app_role_assignment
713 .clones
714 .insert(clone_id.clone(), dna_hash.clone());
715 app_role_assignment.next_clone_index += 1;
717 Ok(clone_id)
718 }
719
720 pub fn get_clone_dna_hash(&self, clone_cell_id: &CloneCellId) -> AppResult<DnaHash> {
722 let cell_id = match clone_cell_id {
723 CloneCellId::DnaHash(dna_hash) => dna_hash,
724 CloneCellId::CloneId(clone_id) => self
725 .primary_role(&clone_id.as_base_role_name())?
726 .clones
727 .get(clone_id)
728 .ok_or_else(|| {
729 AppError::CloneCellNotFound(CloneCellId::CloneId(clone_id.clone()))
730 })?,
731 };
732 Ok(cell_id.clone())
733 }
734
735 pub fn get_clone_id(&self, clone_cell_id: &CloneCellId) -> AppResult<CloneId> {
737 let clone_id = match clone_cell_id {
738 CloneCellId::CloneId(id) => id,
739 CloneCellId::DnaHash(id) => {
740 self.clone_cells()
741 .find(|(_, cell_id)| cell_id.dna_hash() == id)
742 .ok_or_else(|| AppError::CloneCellNotFound(CloneCellId::DnaHash(id.clone())))?
743 .0
744 }
745 };
746 Ok(clone_id.clone())
747 }
748
749 pub fn get_disabled_clone_id(&self, clone_cell_id: &CloneCellId) -> AppResult<CloneId> {
751 let clone_id = match clone_cell_id {
752 CloneCellId::CloneId(id) => id.clone(),
753 CloneCellId::DnaHash(id) => self
754 .role_assignments
755 .iter()
756 .flat_map(|(_, role_assignment)| {
757 role_assignment
758 .as_primary()
759 .into_iter()
760 .flat_map(|r| r.disabled_clones.iter())
761 })
762 .find(|(_, cell_id)| *cell_id == id)
763 .ok_or_else(|| AppError::CloneCellNotFound(CloneCellId::DnaHash(id.clone())))?
764 .0
765 .clone(),
766 };
767 Ok(clone_id)
768 }
769
770 pub fn disable_clone_cell(&mut self, clone_id: &CloneId) -> AppResult<()> {
775 let app_role_assignment = self.primary_role_mut(&clone_id.as_base_role_name())?;
776 match app_role_assignment.clones.remove(clone_id) {
778 None => {
779 if app_role_assignment.disabled_clones.contains_key(clone_id) {
780 Ok(())
781 } else {
782 Err(AppError::CloneCellNotFound(CloneCellId::CloneId(
783 clone_id.to_owned(),
784 )))
785 }
786 }
787 Some(cell_id) => {
788 let insert_result = app_role_assignment
790 .disabled_clones
791 .insert(clone_id.to_owned(), cell_id);
792 assert!(
793 insert_result.is_none(),
794 "disable: clone cell is already disabled"
795 );
796 Ok(())
797 }
798 }
799 }
800
801 pub fn enable_clone_cell(&mut self, clone_id: &CloneId) -> AppResult<InstalledCell> {
809 let app_role_assignment = self.primary_role_mut(&clone_id.as_base_role_name())?;
810 match app_role_assignment.disabled_clones.remove(clone_id) {
812 None => app_role_assignment
813 .clones
814 .get(clone_id)
815 .cloned()
816 .map(|dna_hash| {
817 Ok(InstalledCell {
818 role_name: clone_id.as_app_role_name().to_owned(),
819 cell_id: CellId::new(dna_hash, self.agent_key.clone()),
820 })
821 })
822 .unwrap_or_else(|| {
823 Err(AppError::CloneCellNotFound(CloneCellId::CloneId(
824 clone_id.to_owned(),
825 )))
826 }),
827 Some(dna_hash) => {
828 let insert_result = app_role_assignment
830 .clones
831 .insert(clone_id.to_owned(), dna_hash.clone());
832 assert!(
833 insert_result.is_none(),
834 "enable: clone cell already enabled"
835 );
836 Ok(InstalledCell {
837 role_name: clone_id.as_app_role_name().to_owned(),
838 cell_id: CellId::new(dna_hash, self.agent_key.clone()),
839 })
840 }
841 }
842 }
843
844 pub fn delete_clone_cell(&mut self, clone_id: &CloneId) -> AppResult<()> {
846 let app_role_assignment = self.primary_role_mut(&clone_id.as_base_role_name())?;
847 app_role_assignment
848 .disabled_clones
849 .remove(clone_id)
850 .map(|_| ())
851 .ok_or_else(|| {
852 if app_role_assignment.clones.contains_key(clone_id) {
853 AppError::CloneCellMustBeDisabledBeforeDeleting(CloneCellId::CloneId(
854 clone_id.to_owned(),
855 ))
856 } else {
857 AppError::CloneCellNotFound(CloneCellId::CloneId(clone_id.to_owned()))
858 }
859 })
860 }
861
862 pub fn agent_key(&self) -> &AgentPubKey {
864 &self.agent_key
865 }
866
867 #[cfg(feature = "test_utils")]
870 pub fn new_legacy<S: ToString, I: IntoIterator<Item = InstalledCell>>(
871 installed_app_id: S,
872 installed_cells: I,
873 ) -> AppResult<Self> {
874 use itertools::Itertools;
875
876 let installed_app_id = installed_app_id.to_string();
877 let installed_cells: Vec<_> = installed_cells.into_iter().collect();
878
879 let agent_key = installed_cells
882 .first()
883 .expect("Can't create app with 0 cells")
884 .cell_id
885 .agent_pubkey()
886 .to_owned();
887
888 if installed_cells
890 .iter()
891 .any(|c| *c.cell_id.agent_pubkey() != agent_key)
892 {
893 panic!(
894 "All cells in an app must use the same agent key. Cell data: {:#?}",
895 installed_cells
896 );
897 }
898
899 let duplicates: Vec<RoleName> = installed_cells
901 .iter()
902 .map(|c| c.role_name.to_owned())
903 .counts()
904 .into_iter()
905 .filter_map(|(role_name, count)| if count > 1 { Some(role_name) } else { None })
906 .collect();
907 if !duplicates.is_empty() {
908 return Err(AppError::DuplicateRoleNames(installed_app_id, duplicates));
909 }
910
911 let manifest = AppManifest::from_legacy(installed_cells.clone().into_iter());
912
913 let role_assignments = installed_cells
914 .into_iter()
915 .map(|InstalledCell { role_name, cell_id }| {
916 let role = AppRolePrimary {
917 base_dna_hash: cell_id.dna_hash().clone(),
918 is_provisioned: true,
919 clones: HashMap::new(),
920 clone_limit: 256,
921 next_clone_index: 0,
922 disabled_clones: HashMap::new(),
923 };
924 (role_name, role.into())
925 })
926 .collect();
927
928 Ok(Self {
929 installed_app_id,
930 agent_key,
931 role_assignments,
932 manifest,
933 installed_at: Timestamp::now(),
934 })
935 }
936
937 pub fn manifest(&self) -> &AppManifest {
941 &self.manifest
942 }
943
944 pub fn role_assignments(&self) -> &IndexMap<RoleName, AppRoleAssignment> {
946 &self.role_assignments
947 }
948
949 pub fn installed_at(&self) -> &Timestamp {
951 &self.installed_at
952 }
953}
954
955#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, SerializedBytes)]
963#[serde(tag = "type", content = "value", rename_all = "snake_case")]
964pub enum AppStatus {
965 Running,
967
968 Paused(PausedAppReason),
974
975 Disabled(DisabledAppReason),
979
980 AwaitingMemproofs,
983}
984
985#[derive(Clone, Debug, PartialEq, Eq)]
987#[allow(missing_docs)]
988pub enum AppStatusKind {
989 Running,
990 Paused,
991 Disabled,
992 AwaitingMemproofs,
993}
994
995impl From<AppStatus> for AppStatusKind {
996 fn from(status: AppStatus) -> Self {
997 match status {
998 AppStatus::Running => Self::Running,
999 AppStatus::Paused(_) => Self::Paused,
1000 AppStatus::Disabled(_) => Self::Disabled,
1001 AppStatus::AwaitingMemproofs => Self::AwaitingMemproofs,
1002 }
1003 }
1004}
1005
1006#[derive(Clone, Debug, PartialEq, Eq)]
1008pub enum AppStatusTransition {
1009 Start,
1011 Pause(PausedAppReason),
1013 Enable,
1015 Disable(DisabledAppReason),
1017}
1018
1019impl AppStatus {
1020 pub fn is_enabled(&self) -> bool {
1023 matches!(self, Self::Running | Self::Paused(_))
1024 }
1025
1026 pub fn is_running(&self) -> bool {
1029 matches!(self, Self::Running)
1030 }
1031
1032 pub fn is_paused(&self) -> bool {
1034 matches!(self, Self::Paused(_))
1035 }
1036
1037 pub fn transition(&mut self, transition: AppStatusTransition) -> AppStatusFx {
1040 use AppStatus::*;
1041 use AppStatusFx::*;
1042 use AppStatusTransition::*;
1043 match (&self, transition) {
1044 (Running, Pause(reason)) => Some((Paused(reason), SpinDown)),
1045 (Running, Disable(reason)) => Some((Disabled(reason), SpinDown)),
1046 (Running, Start) | (Running, Enable) => None,
1047
1048 (Paused(_), Start) => Some((Running, SpinUp)),
1049 (Paused(_), Enable) => Some((Running, SpinUp)),
1050 (Paused(_), Disable(reason)) => Some((Disabled(reason), SpinDown)),
1051 (Paused(_), Pause(_)) => None,
1052
1053 (Disabled(_), Enable) => Some((Running, SpinUp)),
1054 (Disabled(_), Pause(_)) | (Disabled(_), Disable(_)) | (Disabled(_), Start) => None,
1055
1056 (AwaitingMemproofs, Enable | Start) => Some((
1057 AwaitingMemproofs,
1058 Error("Cannot enable an app which is AwaitingMemproofs".to_string()),
1059 )),
1060 (AwaitingMemproofs, _) => None,
1061 }
1062 .map(|(new_status, delta)| {
1063 *self = new_status;
1064 delta
1065 })
1066 .unwrap_or(NoChange)
1067 }
1068}
1069
1070#[derive(Clone, Debug, PartialEq, Eq)]
1079#[must_use = "be sure to run this value through `process_app_status_fx` to handle any transition effects"]
1080pub enum AppStatusFx {
1081 NoChange,
1083 SpinDown,
1085 SpinUp,
1087 Both,
1089 Error(String),
1091}
1092
1093impl Default for AppStatusFx {
1094 fn default() -> Self {
1095 Self::NoChange
1096 }
1097}
1098
1099impl AppStatusFx {
1100 pub fn combine(self, other: Self) -> Self {
1102 use AppStatusFx::*;
1103 match (self, other) {
1104 (NoChange, a) | (a, NoChange) => a,
1105 (SpinDown, SpinDown) => SpinDown,
1106 (SpinUp, SpinUp) => SpinUp,
1107 (Both, _) | (_, Both) => Both,
1108 (SpinDown, SpinUp) | (SpinUp, SpinDown) => Both,
1109 (Error(err1), Error(err2)) => Error(format!("{err1}. {err2}")),
1110 (Error(err), _) | (_, Error(err)) => Error(err),
1111 }
1112 }
1113}
1114
1115#[derive(
1117 Clone,
1118 Debug,
1119 PartialEq,
1120 Eq,
1121 serde::Serialize,
1122 serde::Deserialize,
1123 SerializedBytes,
1124 derive_more::From,
1125)]
1126#[serde(rename_all = "snake_case")]
1127pub enum StoppedAppReason {
1128 Paused(PausedAppReason),
1130
1131 Disabled(DisabledAppReason),
1133
1134 AwaitingMemproofs,
1136}
1137
1138impl StoppedAppReason {
1139 pub fn from_status(status: &AppStatus) -> Option<Self> {
1142 match status {
1143 AppStatus::Paused(reason) => Some(Self::Paused(reason.clone())),
1144 AppStatus::Disabled(reason) => Some(Self::Disabled(reason.clone())),
1145 AppStatus::AwaitingMemproofs => Some(Self::AwaitingMemproofs),
1146 AppStatus::Running => None,
1147 }
1148 }
1149}
1150
1151impl From<StoppedAppReason> for AppStatus {
1152 fn from(reason: StoppedAppReason) -> Self {
1153 match reason {
1154 StoppedAppReason::Paused(reason) => Self::Paused(reason),
1155 StoppedAppReason::Disabled(reason) => Self::Disabled(reason),
1156 StoppedAppReason::AwaitingMemproofs => Self::AwaitingMemproofs,
1157 }
1158 }
1159}
1160
1161#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, SerializedBytes)]
1164#[serde(tag = "type", content = "value", rename_all = "snake_case")]
1165pub enum PausedAppReason {
1166 Error(String),
1168}
1169
1170#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, SerializedBytes)]
1172#[serde(tag = "type", content = "value", rename_all = "snake_case")]
1173pub enum DisabledAppReason {
1174 NeverStarted,
1176 NotStartedAfterProvidingMemproofs,
1181 User,
1183 DeletingAgentKey,
1185 Error(String),
1187}
1188
1189#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, derive_more::From)]
1191pub enum AppRoleAssignment {
1192 Primary(AppRolePrimary),
1196 Dependency(AppRoleDependency),
1199}
1200
1201impl AppRoleAssignment {
1202 pub fn as_primary(&self) -> Option<&AppRolePrimary> {
1204 match self {
1205 Self::Primary(p) => Some(p),
1206 Self::Dependency(_) => None,
1207 }
1208 }
1209
1210 pub fn as_primary_mut(&mut self) -> Option<&mut AppRolePrimary> {
1212 match self {
1213 Self::Primary(p) => Some(p),
1214 Self::Dependency(_) => None,
1215 }
1216 }
1217
1218 pub fn provisioned_dna_hash(&self) -> Option<&DnaHash> {
1220 match self {
1221 Self::Primary(p) => p.provisioned_dna_hash(),
1222 Self::Dependency(_) => None,
1223 }
1224 }
1225
1226 }
1234
1235#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1237pub struct AppRolePrimary {
1238 pub base_dna_hash: DnaHash,
1242 pub is_provisioned: bool,
1248 pub clone_limit: u32,
1250
1251 pub next_clone_index: u32,
1253
1254 pub clones: HashMap<CloneId, DnaHash>,
1257 pub disabled_clones: HashMap<CloneId, DnaHash>,
1261}
1262
1263impl AppRolePrimary {
1264 pub fn new(base_dna_hash: DnaHash, is_provisioned: bool, clone_limit: u32) -> Self {
1266 Self {
1267 base_dna_hash,
1268 is_provisioned,
1269 clone_limit,
1270 clones: HashMap::new(),
1271 next_clone_index: 0,
1272 disabled_clones: HashMap::new(),
1273 }
1274 }
1275
1276 pub fn dna_hash(&self) -> &DnaHash {
1278 &self.base_dna_hash
1279 }
1280
1281 pub fn provisioned_dna_hash(&self) -> Option<&DnaHash> {
1283 if self.is_provisioned {
1284 Some(&self.base_dna_hash)
1285 } else {
1286 None
1287 }
1288 }
1289
1290 pub fn clone_ids(&self) -> impl Iterator<Item = &CloneId> {
1292 self.clones.keys()
1293 }
1294
1295 pub fn clone_limit(&self) -> u32 {
1297 self.clone_limit
1298 }
1299
1300 pub fn is_clone_limit_reached(&self) -> bool {
1302 self.clones.len() as u32 == self.clone_limit
1303 }
1304}
1305
1306#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1308pub struct AppRoleDependency {
1309 pub cell_id: CellId,
1311 pub protected: bool,
1315}
1316
1317#[cfg(test)]
1318mod tests {
1319 use super::RunningApp;
1320 use crate::prelude::*;
1321 use ::fixt::prelude::*;
1322 use holo_hash::fixt::*;
1323 use serde_json;
1324 use std::collections::HashSet;
1325
1326 #[test]
1327 fn illegal_role_name_is_rejected() {
1328 let result = InstalledAppCommon::new(
1329 "test_app",
1330 fixt!(AgentPubKey),
1331 vec![(
1332 CLONE_ID_DELIMITER.into(),
1333 AppRolePrimary::new(fixt!(DnaHash), false, 0).into(),
1334 )],
1335 AppManifest::V1(AppManifestV1 {
1336 name: "test_app".to_string(),
1337 description: None,
1338 roles: vec![],
1339 allow_deferred_memproofs: false,
1340 }),
1341 Timestamp::now(),
1342 );
1343 assert!(result.is_err())
1344 }
1345
1346 #[test]
1347 fn clone_management() {
1348 let base_dna_hash = fixt!(DnaHash);
1349 let new_clone = || fixt!(DnaHash);
1350 let clone_limit = 3;
1351 let role1 = AppRolePrimary::new(base_dna_hash, false, clone_limit).into();
1352 let agent = fixt!(AgentPubKey);
1353 let role_name: RoleName = "role_name".into();
1354 let manifest = AppManifest::V1(AppManifestV1 {
1355 name: "test_app".to_string(),
1356 description: None,
1357 roles: vec![],
1358 allow_deferred_memproofs: false,
1359 });
1360 let mut app: RunningApp = InstalledAppCommon::new(
1361 "app",
1362 agent.clone(),
1363 vec![(role_name.clone(), role1)],
1364 manifest,
1365 Timestamp::now(),
1366 )
1367 .unwrap()
1368 .into();
1369
1370 let clones: Vec<_> = vec![new_clone(), new_clone(), new_clone()];
1372 let clone_id_0 = app.add_clone(&role_name, &clones[0]).unwrap();
1373 let clone_id_1 = app.add_clone(&role_name, &clones[1]).unwrap();
1374 let clone_id_2 = app.add_clone(&role_name, &clones[2]).unwrap();
1375
1376 assert_eq!(clone_id_0, CloneId::new(&role_name, 0));
1377 assert_eq!(clone_id_1, CloneId::new(&role_name, 1));
1378 assert_eq!(clone_id_2, CloneId::new(&role_name, 2));
1379
1380 assert_eq!(
1381 app.clone_cell_ids()
1382 .map(|id| id.dna_hash().clone())
1383 .collect::<HashSet<_>>(),
1384 clones.clone().into_iter().collect::<HashSet<_>>()
1385 );
1386 assert_eq!(app.clone_cells().count(), 3);
1387
1388 let result_add_clone_twice = app.add_clone(&role_name, &clones[0]);
1390 assert!(result_add_clone_twice.is_err());
1391
1392 matches::assert_matches!(
1394 app.add_clone(&role_name, &new_clone()),
1395 Err(AppError::CloneLimitExceeded(3, _))
1396 );
1397
1398 app.disable_clone_cell(&clone_id_0).unwrap();
1400 assert!(!app
1402 .clone_cells()
1403 .any(|(clone_id, _)| *clone_id == clone_id_0));
1404 assert_eq!(app.clone_cells().count(), 2);
1405 assert!(app
1406 .disabled_clone_cells()
1407 .any(|(clone_id, _)| *clone_id == clone_id_0));
1408
1409 let enabled_cell = app.enable_clone_cell(&clone_id_0).unwrap();
1411 assert_eq!(
1412 enabled_cell.role_name,
1413 clone_id_0.as_app_role_name().to_owned()
1414 );
1415
1416 let enabled_cell_2 = app.enable_clone_cell(&clone_id_0).unwrap();
1418 assert_eq!(enabled_cell_2, enabled_cell);
1419
1420 assert!(app
1422 .clone_cells()
1423 .any(|(clone_id, _)| *clone_id == clone_id_0));
1424 assert_eq!(
1425 app.clone_cell_ids()
1426 .map(|id| id.dna_hash().clone())
1427 .collect::<HashSet<_>>(),
1428 clones.clone().into_iter().collect::<HashSet<_>>()
1429 );
1430 assert_eq!(app.clone_cells().count(), 3);
1431
1432 app.disable_clone_cell(&clone_id_0).unwrap();
1434 app.disable_clone_cell(&clone_id_0).unwrap();
1436
1437 app.delete_clone_cell(&clone_id_0).unwrap();
1438 assert!(app.enable_clone_cell(&clone_id_0).is_err());
1440 }
1441
1442 #[test]
1443 fn dna_source_serialization() {
1444 use serde_json;
1445
1446 let dna_source: DnaSource = DnaSource::Path("is the goal".into());
1447
1448 assert_eq!(
1449 serde_json::to_string(&dna_source).unwrap(),
1450 "{\"type\":\"path\",\"value\":\"is the goal\"}"
1451 );
1452 }
1453
1454 #[test]
1455 fn coordinator_source_serialization() {
1456 let coordinator_source: CoordinatorSource = CoordinatorSource::Path("is the goal".into());
1457 assert_eq!(
1458 serde_json::to_string(&coordinator_source).unwrap(),
1459 "{\"type\":\"path\",\"value\":\"is the goal\"}"
1460 );
1461 }
1462
1463 #[test]
1464 fn role_settings_serialization() {
1465 let role_settings: RoleSettings = RoleSettings::Provisioned {
1466 membrane_proof: None,
1467 modifiers: None,
1468 };
1469 assert_eq!(
1470 serde_json::to_string(&role_settings).unwrap(),
1471 "{\"type\":\"provisioned\",\"value\":{\"membrane_proof\":null,\"modifiers\":null}}"
1472 );
1473 }
1474
1475 #[test]
1476 fn app_bundle_source_serialization() {
1477 let app_bundle_source: AppBundleSource = AppBundleSource::Path("is the goal".into());
1478 assert_eq!(
1479 serde_json::to_string(&app_bundle_source).unwrap(),
1480 "{\"type\":\"path\",\"value\":\"is the goal\"}"
1481 );
1482 }
1483
1484 #[test]
1485 fn app_status_serialization() {
1486 let app_status: AppStatus = AppStatus::Running;
1487 assert_eq!(
1488 serde_json::to_string(&app_status).unwrap(),
1489 "{\"type\":\"running\"}"
1490 );
1491
1492 let app_status: AppStatus = AppStatus::Disabled(DisabledAppReason::NeverStarted);
1493 assert_eq!(
1494 serde_json::to_string(&app_status).unwrap(),
1495 "{\"type\":\"disabled\",\"value\":{\"type\":\"never_started\"}}"
1496 );
1497 }
1498
1499 #[test]
1500 fn paused_app_reason_serialization() {
1501 let reason = PausedAppReason::Error("s are here to learn from".into());
1502 assert_eq!(
1503 serde_json::to_string(&reason).unwrap(),
1504 "{\"type\":\"error\",\"value\":\"s are here to learn from\"}"
1505 );
1506 }
1507
1508 #[test]
1509 fn disabled_app_reason_serialization() {
1510 let reason = DisabledAppReason::User;
1511 assert_eq!(
1512 serde_json::to_string(&reason).unwrap(),
1513 "{\"type\":\"user\"}"
1514 );
1515 }
1516}