holochain_zome_types/
dna_def.rs

1//! Defines DnaDef struct
2
3#[cfg(feature = "unstable-migration")]
4use std::collections::HashSet;
5
6use crate::prelude::*;
7
8#[cfg(feature = "full-dna-def")]
9use holochain_integrity_types::DnaModifiersBuilder;
10
11#[cfg(feature = "full-dna-def")]
12use crate::zome::ZomeError;
13#[cfg(feature = "full-dna-def")]
14use holo_hash::*;
15
16/// Ordered list of integrity zomes in this DNA.
17pub type IntegrityZomes = Vec<(ZomeName, IntegrityZomeDef)>;
18
19/// Ordered list of coordinator zomes in this DNA.
20pub type CoordinatorZomes = Vec<(ZomeName, CoordinatorZomeDef)>;
21
22/// The definition of a DNA: the hash of this data is what produces the DnaHash.
23///
24/// Historical note: This struct was written before `DnaManifest` appeared.
25/// It is included as part of a `DnaFile`. There is still a lot of code that uses
26/// this type, but in function, it has mainly been superseded by `DnaManifest`.
27/// Hence, this type can basically be thought of as a fully validated, normalized
28/// `DnaManifest`
29#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, SerializedBytes)]
30#[cfg_attr(feature = "full-dna-def", derive(derive_builder::Builder))]
31#[cfg_attr(feature = "full-dna-def", builder(public))]
32pub struct DnaDef {
33    /// The friendly "name" of a Holochain DNA.
34    #[cfg_attr(
35        feature = "full-dna-def",
36        builder(default = "\"Generated DnaDef\".to_string()")
37    )]
38    pub name: String,
39
40    /// Modifiers of this DNA - the network seed, properties - as opposed to
41    /// the actual DNA code. The modifiers are included in the DNA hash
42    /// computation.
43    pub modifiers: DnaModifiers,
44
45    /// A vector of zomes associated with your DNA.
46    pub integrity_zomes: IntegrityZomes,
47
48    /// A vector of zomes that do not affect
49    /// the [`DnaHash`].
50    pub coordinator_zomes: CoordinatorZomes,
51
52    /// A list of past "ancestors" of this DNA.
53    ///
54    /// Whenever a DNA is created which is intended to be used as a migration from
55    /// a previous DNA, the lineage should be updated to include the hash of the
56    /// DNA being migrated from. DNA hashes may also be removed from this list if
57    /// it is desired to remove them from the lineage.
58    ///
59    /// The meaning of the "ancestor" relationship is as follows:
60    /// - For any DNA, there is a migration path from any of its ancestors to itself.
61    /// - When an app depends on a DnaHash via UseExisting, it means that any installed
62    ///     DNA in the lineage which contains that DnaHash can be used.
63    /// - The app's Coordinator interface is expected to be compatible across the lineage.
64    ///     (Though this cannot be enforced, since Coordinators can be swapped out at
65    ///      will by the user, the intention is still there.)
66    ///
67    /// Holochain does nothing to ensure the correctness of the lineage, it is up to
68    /// the app developer to make the necessary guarantees.
69    #[cfg(feature = "unstable-migration")]
70    #[serde(default)]
71    #[cfg_attr(feature = "full-dna-def", builder(default))]
72    pub lineage: HashSet<DnaHash>,
73}
74
75#[cfg(feature = "full-dna-def")]
76#[derive(Serialize, Debug, PartialEq, Eq)]
77/// A reference to for creating the hash for [`DnaDef`].
78struct DnaDefHash<'a> {
79    modifiers: &'a DnaModifiers,
80    integrity_zomes: &'a IntegrityZomes,
81}
82
83#[cfg(feature = "test_utils")]
84impl DnaDef {
85    /// Create a DnaDef with a random network seed, useful for testing
86    pub fn unique_from_zomes(
87        integrity: Vec<IntegrityZome>,
88        coordinator: Vec<CoordinatorZome>,
89    ) -> DnaDef {
90        let integrity = integrity.into_iter().map(|z| z.into_inner()).collect();
91        let coordinator = coordinator.into_iter().map(|z| z.into_inner()).collect();
92        DnaDefBuilder::default()
93            .integrity_zomes(integrity)
94            .coordinator_zomes(coordinator)
95            .random_network_seed()
96            .build()
97            .unwrap()
98    }
99}
100
101impl DnaDef {
102    /// Get all zomes including the integrity and coordinator zomes.
103    #[cfg_attr(feature = "instrument", tracing::instrument(skip_all))]
104    pub fn all_zomes(&self) -> impl Iterator<Item = (&ZomeName, &ZomeDef)> {
105        self.integrity_zomes
106            .iter()
107            .map(|(n, def)| (n, def.as_any_zome_def()))
108            .chain(
109                self.coordinator_zomes
110                    .iter()
111                    .map(|(n, def)| (n, def.as_any_zome_def())),
112            )
113    }
114}
115
116#[cfg(feature = "full-dna-def")]
117impl DnaDef {
118    /// Find an integrity zome from a [`ZomeName`].
119    pub fn get_integrity_zome(&self, zome_name: &ZomeName) -> Result<IntegrityZome, ZomeError> {
120        self.integrity_zomes
121            .iter()
122            .find(|(name, _)| name == zome_name)
123            .cloned()
124            .map(|(name, def)| IntegrityZome::new(name, def))
125            .ok_or_else(|| {
126                tracing::error!(
127                    "ZomeNotFound: {zome_name}. (get_integrity_zome) Existing zomes: integrity={:?}, coordinator={:?}",
128                    self.integrity_zomes,
129                    self.coordinator_zomes,
130                );
131                ZomeError::ZomeNotFound(format!("Integrity zome '{}' not found", &zome_name,))
132            })
133    }
134
135    /// Check if a zome is an integrity zome.
136    #[cfg_attr(feature = "instrument", tracing::instrument(skip_all))]
137    pub fn is_integrity_zome(&self, zome_name: &ZomeName) -> bool {
138        self.integrity_zomes
139            .iter()
140            .any(|(name, _)| name == zome_name)
141    }
142
143    /// Find a coordinator zome from a [`ZomeName`].
144    pub fn get_coordinator_zome(&self, zome_name: &ZomeName) -> Result<CoordinatorZome, ZomeError> {
145        self.coordinator_zomes
146            .iter()
147            .find(|(name, _)| name == zome_name)
148            .cloned()
149            .map(|(name, def)| CoordinatorZome::new(name, def))
150            .ok_or_else(|| {
151                tracing::error!(
152                    "ZomeNotFound: {zome_name}. (get_coordinator_zome) Existing zomes: integrity={:?}, coordinator={:?}",
153                    self.integrity_zomes,
154                    self.coordinator_zomes,
155                );
156                ZomeError::ZomeNotFound(format!("Coordinator Zome '{}' not found", &zome_name,))
157            })
158    }
159
160    /// Find a any zome from a [`ZomeName`].
161    pub fn get_zome(&self, zome_name: &ZomeName) -> Result<Zome, ZomeError> {
162        self.integrity_zomes
163            .iter()
164            .find(|(name, _)| name == zome_name)
165            .cloned()
166            .map(|(name, def)| Zome::new(name, def.erase_type()))
167            .or_else(|| {
168                self.coordinator_zomes
169                    .iter()
170                    .find(|(name, _)| name == zome_name)
171                    .cloned()
172                    .map(|(name, def)| Zome::new(name, def.erase_type()))
173            })
174            .ok_or_else(|| {
175                tracing::error!(
176                    "ZomeNotFound: {zome_name}. (get_zome) Existing zomes: integrity={:?}, coordinator={:?}",
177                    self.integrity_zomes,
178                    self.coordinator_zomes,
179                );
180                ZomeError::ZomeNotFound(format!("Zome '{}' not found", &zome_name,))
181            })
182    }
183
184    /// Get all the [`CoordinatorZome`]s for this dna
185    pub fn get_all_coordinators(&self) -> Vec<CoordinatorZome> {
186        self.coordinator_zomes
187            .iter()
188            .cloned()
189            .map(|(name, def)| CoordinatorZome::new(name, def))
190            .collect()
191    }
192
193    /// Return a Zome, error if not a WasmZome
194    pub fn get_wasm_zome(&self, zome_name: &ZomeName) -> Result<&WasmZome, ZomeError> {
195        self.all_zomes()
196            .find(|(name, _)| *name == zome_name)
197            .map(|(_, def)| def)
198            .ok_or_else(|| {
199                tracing::error!(
200                    "ZomeNotFound: {zome_name}. (get_wasm_zome) Existing zomes: integrity={:?}, coordinator={:?}",
201                    self.integrity_zomes,
202                    self.coordinator_zomes,
203                );
204                ZomeError::ZomeNotFound(format!("Wasm zome '{}' not found", &zome_name,))
205            })
206            .and_then(|def| {
207                if let ZomeDef::Wasm(wasm_zome) = def {
208                    Ok(wasm_zome)
209                } else {
210                    Err(ZomeError::NonWasmZome(zome_name.clone()))
211                }
212            })
213    }
214
215    /// Return the Wasm Hash for Zome, error if not a Wasm type Zome
216    pub fn get_wasm_zome_hash(&self, zome_name: &ZomeName) -> Result<WasmHash, ZomeError> {
217        self.all_zomes()
218            .find(|(name, _)| *name == zome_name)
219            .map(|(_, def)| def)
220            .ok_or_else(|| {
221                tracing::error!(
222                    "ZomeNotFound: {zome_name}. (get_wasm_zome_hash) Existing zomes: integrity={:?}, coordinator={:?}",
223                    self.integrity_zomes,
224                    self.coordinator_zomes,
225                );
226                ZomeError::ZomeNotFound(format!("Hash for wasm zome '{}' not found", &zome_name,))
227            })
228            .and_then(|def| match def {
229                ZomeDef::Wasm(wasm_zome) => Ok(wasm_zome.wasm_hash.clone()),
230                _ => Err(ZomeError::NonWasmZome(zome_name.clone())),
231            })
232    }
233
234    /// Set the DNA's name.
235    pub fn set_name(&self, name: String) -> Self {
236        let mut clone = self.clone();
237        clone.name = name;
238        clone
239    }
240
241    /// Change the DNA modifiers -- the network seed, properties -- while
242    /// leaving the actual DNA code intact.
243    pub fn update_modifiers(&self, modifiers: DnaModifiersOpt) -> Self {
244        let mut clone = self.clone();
245        clone.modifiers = clone.modifiers.update(modifiers);
246        clone
247    }
248}
249
250/// Get a random network seed
251#[cfg(feature = "full-dna-def")]
252pub fn random_network_seed() -> String {
253    nanoid::nanoid!()
254}
255
256#[cfg(feature = "full-dna-def")]
257impl DnaDefBuilder {
258    /// Provide a random network seed
259    pub fn random_network_seed(&mut self) -> &mut Self {
260        self.modifiers = Some(
261            DnaModifiersBuilder::default()
262                .network_seed(random_network_seed())
263                .build()
264                .unwrap(),
265        );
266        self
267    }
268}
269
270/// A DnaDef paired with its DnaHash
271#[cfg(feature = "full-dna-def")]
272pub type DnaDefHashed = HoloHashed<DnaDef>;
273
274#[cfg(feature = "full-dna-def")]
275impl HashableContent for DnaDef {
276    type HashType = holo_hash::hash_type::Dna;
277
278    fn hash_type(&self) -> Self::HashType {
279        holo_hash::hash_type::Dna::new()
280    }
281
282    fn hashable_content(&self) -> HashableContentBytes {
283        let hash = DnaDefHash {
284            modifiers: &self.modifiers,
285            integrity_zomes: &self.integrity_zomes,
286        };
287        HashableContentBytes::Content(
288            holochain_serialized_bytes::UnsafeBytes::from(
289                holochain_serialized_bytes::encode(&hash)
290                    .expect("Could not serialize HashableContent"),
291            )
292            .into(),
293        )
294    }
295}
296
297#[cfg(test)]
298mod tests {
299
300    use super::*;
301    use holochain_serialized_bytes::prelude::*;
302
303    #[test]
304    fn test_update_modifiers() {
305        #[derive(Debug, Clone, Serialize, Deserialize, SerializedBytes)]
306        struct Props(u32);
307
308        let props = SerializedBytes::try_from(Props(42)).unwrap();
309
310        let mods = DnaModifiers {
311            network_seed: "seed".into(),
312            properties: ().try_into().unwrap(),
313        };
314
315        let opt = DnaModifiersOpt {
316            network_seed: None,
317            properties: Some(props.clone()),
318        };
319
320        let expected = DnaModifiers {
321            network_seed: "seed".into(),
322            properties: props.clone(),
323        };
324
325        assert_eq!(mods.update(opt), expected);
326    }
327}