1use crate::effects::ExecutionMode;
12use crate::hash::hash;
13use crate::types::identifiers::{AuthorityId, ContextId, SessionId};
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::fmt;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
23#[serde(transparent)]
24pub struct OperationSessionId(SessionId);
25
26impl OperationSessionId {
27 #[must_use]
29 pub fn new(session_id: SessionId) -> Self {
30 Self(session_id)
31 }
32
33 #[must_use]
35 pub fn raw(self) -> SessionId {
36 self.0
37 }
38}
39
40impl fmt::Display for OperationSessionId {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 self.0.fmt(f)
43 }
44}
45
46impl From<SessionId> for OperationSessionId {
47 fn from(value: SessionId) -> Self {
48 Self::new(value)
49 }
50}
51
52impl From<OperationSessionId> for SessionId {
53 fn from(value: OperationSessionId) -> Self {
54 value.raw()
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct EffectContext {
61 authority_id: AuthorityId,
62 context_id: ContextId,
63 session_id: OperationSessionId,
64 execution_mode: ExecutionMode,
65 metadata: HashMap<String, String>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct ContextSnapshot {
71 authority_id: AuthorityId,
72 context_id: ContextId,
73 session_id: OperationSessionId,
74 execution_mode: ExecutionMode,
75}
76
77fn derive_session_id(
78 authority_id: AuthorityId,
79 context_id: ContextId,
80 execution_mode: ExecutionMode,
81) -> OperationSessionId {
82 let mut material = Vec::with_capacity(1 + 32 + 32 + 9);
83 material.extend_from_slice(b"aura-session");
84 material.extend_from_slice(&authority_id.to_bytes());
85 material.extend_from_slice(&context_id.to_bytes());
86 match execution_mode {
87 ExecutionMode::Testing => material.push(0),
88 ExecutionMode::Production => material.push(1),
89 ExecutionMode::Simulation { seed } => {
90 material.push(2);
91 material.extend_from_slice(&seed.to_le_bytes());
92 }
93 }
94 OperationSessionId::new(SessionId::new_from_entropy(hash(&material)))
95}
96
97impl EffectContext {
98 pub fn new(
102 authority_id: AuthorityId,
103 context_id: ContextId,
104 execution_mode: ExecutionMode,
105 ) -> Self {
106 Self {
107 authority_id,
108 context_id,
109 session_id: derive_session_id(authority_id, context_id, execution_mode),
110 execution_mode,
111 metadata: HashMap::new(),
112 }
113 }
114
115 #[must_use]
120 pub fn with_authority(authority_id: AuthorityId) -> Self {
121 let context_id = ContextId::new_from_entropy(hash(&authority_id.to_bytes()));
122 Self::new(authority_id, context_id, ExecutionMode::Production)
123 }
124
125 pub fn authority_id(&self) -> AuthorityId {
127 self.authority_id
128 }
129
130 pub fn context_id(&self) -> ContextId {
132 self.context_id
133 }
134
135 pub fn session_id(&self) -> OperationSessionId {
137 self.session_id
138 }
139
140 pub fn execution_mode(&self) -> ExecutionMode {
142 self.execution_mode
143 }
144
145 pub fn set_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
147 self.metadata.insert(key.into(), value.into());
148 }
149
150 pub fn get_metadata(&self, key: &str) -> Option<&String> {
152 self.metadata.get(key)
153 }
154
155 pub fn metadata(&self) -> &HashMap<String, String> {
157 &self.metadata
158 }
159
160 pub fn snapshot(&self) -> ContextSnapshot {
162 ContextSnapshot {
163 authority_id: self.authority_id,
164 context_id: self.context_id,
165 session_id: self.session_id,
166 execution_mode: self.execution_mode,
167 }
168 }
169
170 pub fn create_child(&self, context_id: ContextId) -> Self {
174 Self {
175 authority_id: self.authority_id,
176 context_id,
177 session_id: derive_session_id(self.authority_id, context_id, self.execution_mode),
178 execution_mode: self.execution_mode,
179 metadata: self.metadata.clone(),
180 }
181 }
182}
183
184impl ContextSnapshot {
185 pub fn new(
187 authority_id: AuthorityId,
188 context_id: ContextId,
189 execution_mode: ExecutionMode,
190 ) -> Self {
191 Self {
192 authority_id,
193 context_id,
194 session_id: derive_session_id(authority_id, context_id, execution_mode),
195 execution_mode,
196 }
197 }
198 pub fn authority_id(&self) -> AuthorityId {
200 self.authority_id
201 }
202
203 pub fn context_id(&self) -> ContextId {
205 self.context_id
206 }
207
208 pub fn session_id(&self) -> OperationSessionId {
210 self.session_id
211 }
212
213 pub fn execution_mode(&self) -> ExecutionMode {
215 self.execution_mode
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn operation_session_id_round_trips_raw_session_id() {
225 let raw = SessionId::new_from_entropy([7; 32]);
226 let operation = OperationSessionId::new(raw);
227 assert_eq!(operation.raw(), raw);
228 }
229
230 #[test]
231 fn effect_context_uses_operation_session_id_boundary() {
232 let authority_id = AuthorityId::new_from_entropy([1; 32]);
233 let context_id = ContextId::new_from_entropy([2; 32]);
234 let context = EffectContext::new(authority_id, context_id, ExecutionMode::Testing);
235
236 let operation_session = context.session_id();
237 let raw_session: SessionId = operation_session.into();
238
239 assert_eq!(operation_session.raw(), raw_session);
240 }
241}