qualia_core_db/entity_view/
package.rs1use super::entity_id::EntityId;
6use super::observer::RepresentationWing;
7use crate::hypermedia::fnv60;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct PackageWing {
13 pub wing: RepresentationWing,
14 pub entity_ids: Vec<u64>,
16 pub payload_digest: u64,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub struct BifurcatedPackage {
23 pub version: u32,
24 pub package_id: u64,
25 pub private_wing: PackageWing,
26 pub offered_wing: PackageWing,
27 pub commons_wing: PackageWing,
28 pub package_digest: u64,
30}
31
32impl BifurcatedPackage {
33 pub const VERSION: u32 = 1;
34
35 pub fn new(
36 package_key: &str,
37 private_ids: &[EntityId],
38 offered_ids: &[EntityId],
39 commons_ids: &[EntityId],
40 ) -> Self {
41 let private_wing = wing(RepresentationWing::Private, private_ids);
42 let offered_wing = wing(RepresentationWing::Offered, offered_ids);
43 let commons_wing = wing(RepresentationWing::Commons, commons_ids);
44 let package_id = fnv60(package_key.as_bytes());
45 let package_digest = fold_digests(&[
46 private_wing.payload_digest,
47 offered_wing.payload_digest,
48 commons_wing.payload_digest,
49 package_id,
50 ]);
51 Self {
52 version: Self::VERSION,
53 package_id,
54 private_wing,
55 offered_wing,
56 commons_wing,
57 package_digest,
58 }
59 }
60
61 pub fn wing_for(&self, wing: RepresentationWing) -> &PackageWing {
63 match wing {
64 RepresentationWing::Private => &self.private_wing,
65 RepresentationWing::Offered => &self.offered_wing,
66 RepresentationWing::Commons => &self.commons_wing,
67 }
68 }
69}
70
71fn wing(kind: RepresentationWing, ids: &[EntityId]) -> PackageWing {
72 let entity_ids: Vec<u64> = ids.iter().map(|e| e.raw()).collect();
73 let mut bytes = Vec::with_capacity(entity_ids.len() * 8 + 1);
74 bytes.push(kind as u8);
75 for id in &entity_ids {
76 bytes.extend_from_slice(&id.to_le_bytes());
77 }
78 PackageWing {
79 wing: kind,
80 payload_digest: if entity_ids.is_empty() {
81 0
82 } else {
83 fnv60(&bytes)
84 },
85 entity_ids,
86 }
87}
88
89fn fold_digests(parts: &[u64]) -> u64 {
90 let mut bytes = Vec::with_capacity(parts.len() * 8);
91 for p in parts {
92 bytes.extend_from_slice(&p.to_le_bytes());
93 }
94 fnv60(&bytes)
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::entity_view::entity_id::EntityId;
101
102 #[test]
103 fn bifurcate_package_stable_digest() {
104 let a = EntityId::from_uri("urn:a");
105 let b = EntityId::from_uri("urn:b");
106 let p1 = BifurcatedPackage::new("pkg:1", &[a], &[b], &[]);
107 let p2 = BifurcatedPackage::new("pkg:1", &[a], &[b], &[]);
108 assert_eq!(p1.package_digest, p2.package_digest);
109 assert_eq!(p1.private_wing.entity_ids.len(), 1);
110 assert_eq!(p1.offered_wing.entity_ids.len(), 1);
111 assert!(p1.commons_wing.entity_ids.is_empty());
112 }
113}