1use crate::bindings::golem::api::oplog::OplogIndex;
16use crate::bindings::golem::durability::durability::{
17 CustomDurableInvocation, DurableFunctionType as RawDurableFunctionType,
18 LiveCustomDurableInvocation, OplogEntryVersion as RawOplogEntryVersion,
19 PersistedDurableFunctionInvocation, begin_custom_durable_invocation, observe_function_call,
20};
21use crate::schema::{FromSchema, IntoSchema, IntoTypedSchemaValue};
22use std::fmt::Display;
23use std::future::Future;
24use std::marker::PhantomData;
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoSchema, FromSchema)]
27pub enum DurableFunctionType {
28 ReadLocal,
29 WriteLocal,
30 ReadRemote,
31 WriteRemote,
32 WriteRemoteBatched(Option<OplogIndex>),
33 WriteRemoteTransaction(Option<OplogIndex>),
34}
35
36impl From<DurableFunctionType> for RawDurableFunctionType {
37 fn from(value: DurableFunctionType) -> Self {
38 match value {
39 DurableFunctionType::ReadLocal => Self::ReadLocal,
40 DurableFunctionType::WriteLocal => Self::WriteLocal,
41 DurableFunctionType::ReadRemote => Self::ReadRemote,
42 DurableFunctionType::WriteRemote => Self::WriteRemote,
43 DurableFunctionType::WriteRemoteBatched(index) => Self::WriteRemoteBatched(index),
44 DurableFunctionType::WriteRemoteTransaction(index) => {
45 Self::WriteRemoteTransaction(index)
46 }
47 }
48 }
49}
50
51#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoSchema, FromSchema)]
52pub enum OplogEntryVersion {
53 V1,
54 V2,
55}
56
57impl From<RawOplogEntryVersion> for OplogEntryVersion {
58 fn from(value: RawOplogEntryVersion) -> Self {
59 match value {
60 RawOplogEntryVersion::V1 => Self::V1,
61 RawOplogEntryVersion::V2 => Self::V2,
62 }
63 }
64}
65
66#[must_use = "a durability invocation must be run to completion"]
67pub struct Durability<SOk, SErr> {
68 interface: &'static str,
69 function: &'static str,
70 invocation: CustomDurableInvocation,
71 forced_commit: bool,
72 _sok: PhantomData<SOk>,
73 _serr: PhantomData<SErr>,
74}
75
76impl<SOk, SErr> Durability<SOk, SErr> {
77 pub fn new<SIn>(
78 interface: &'static str,
79 function: &'static str,
80 function_type: DurableFunctionType,
81 input: &SIn,
82 ) -> Self
83 where
84 SIn: Clone + IntoSchema,
85 {
86 observe_function_call(interface, function);
87 let function_type = RawDurableFunctionType::from(function_type);
88 let function_name = if interface.is_empty() {
89 function.to_string()
90 } else {
91 format!("{interface}::{function}")
92 };
93 let request = input
94 .clone()
95 .into_typed_schema_value()
96 .unwrap_or_else(|err| panic!("Failed serializing durable function input: {err}"));
97 let request = crate::encode_typed_schema_value(&request)
98 .unwrap_or_else(|err| panic!("Failed encoding durable function input: {err}"));
99 let invocation = begin_custom_durable_invocation(&function_name, request, function_type);
100
101 Self {
102 interface,
103 function,
104 invocation,
105 forced_commit: false,
106 _sok: PhantomData,
107 _serr: PhantomData,
108 }
109 }
110
111 pub fn enable_forced_commit(&mut self) {
112 self.forced_commit = true;
113 }
114
115 pub fn with_forced_commit(mut self) -> Self {
116 self.forced_commit = true;
117 self
118 }
119
120 pub fn run<Ok, Err, F>(self, body: F) -> Result<Ok, Err>
121 where
122 F: FnOnce() -> Result<Ok, Err>,
123 Ok: Clone + From<SOk>,
124 Err: From<SErr>,
125 SErr: FromSchema + for<'a> From<&'a Err>,
126 SOk: FromSchema + From<Ok>,
127 Result<SOk, SErr>: IntoSchema,
128 {
129 match self.invocation {
130 CustomDurableInvocation::Live(invocation) => {
131 let result = body();
132 let serializable_result = result
133 .as_ref()
134 .map(|result| result.clone().into())
135 .map_err(|err| err.into());
136 Self::finish(invocation, serializable_result, self.forced_commit);
137 result
138 }
139 CustomDurableInvocation::Replayed(ref invocation) => self
140 .replay_serializable(invocation)
141 .map(Into::into)
142 .map_err(Into::into),
143 }
144 }
145
146 pub async fn run_async<Ok, Err, F, Fut>(self, body: F) -> Result<Ok, Err>
147 where
148 F: FnOnce() -> Fut,
149 Fut: Future<Output = Result<Ok, Err>>,
150 Ok: Clone + From<SOk>,
151 Err: From<SErr>,
152 SErr: FromSchema + for<'a> From<&'a Err>,
153 SOk: FromSchema + From<Ok>,
154 Result<SOk, SErr>: IntoSchema,
155 {
156 match self.invocation {
157 CustomDurableInvocation::Live(invocation) => {
158 let result = body().await;
159 let serializable_result = result
160 .as_ref()
161 .map(|result| result.clone().into())
162 .map_err(|err| err.into());
163 Self::finish(invocation, serializable_result, self.forced_commit);
164 result
165 }
166 CustomDurableInvocation::Replayed(ref invocation) => self
167 .replay_serializable(invocation)
168 .map(Into::into)
169 .map_err(Into::into),
170 }
171 }
172
173 pub fn run_infallible<Ok, F>(self, body: F) -> Ok
174 where
175 F: FnOnce() -> Ok,
176 Ok: Clone + From<SOk>,
177 SOk: FromSchema + From<Ok>,
178 SErr: FromSchema + Display,
179 Result<SOk, SErr>: IntoSchema,
180 {
181 match self.invocation {
182 CustomDurableInvocation::Live(invocation) => {
183 let result = body();
184 let serializable_result = Ok(result.clone().into());
185 Self::finish(invocation, serializable_result, self.forced_commit);
186 result
187 }
188 CustomDurableInvocation::Replayed(ref invocation) => {
189 match self.replay_serializable(invocation) {
190 Ok(result) => result.into(),
191 Err(err) => panic!(
192 "Function {} previously failed with {}",
193 self.function_name(),
194 err
195 ),
196 }
197 }
198 }
199 }
200
201 pub async fn run_infallible_async<Ok, F, Fut>(self, body: F) -> Ok
202 where
203 F: FnOnce() -> Fut,
204 Fut: Future<Output = Ok>,
205 Ok: Clone + From<SOk>,
206 SOk: FromSchema + From<Ok>,
207 SErr: FromSchema + Display,
208 Result<SOk, SErr>: IntoSchema,
209 {
210 match self.invocation {
211 CustomDurableInvocation::Live(invocation) => {
212 let result = body().await;
213 let serializable_result = Ok(result.clone().into());
214 Self::finish(invocation, serializable_result, self.forced_commit);
215 result
216 }
217 CustomDurableInvocation::Replayed(ref invocation) => {
218 match self.replay_serializable(invocation) {
219 Ok(result) => result.into(),
220 Err(err) => panic!(
221 "Function {} previously failed with {}",
222 self.function_name(),
223 err
224 ),
225 }
226 }
227 }
228 }
229
230 fn finish(
231 invocation: LiveCustomDurableInvocation,
232 result: Result<SOk, SErr>,
233 forced_commit: bool,
234 ) where
235 Result<SOk, SErr>: IntoSchema,
236 {
237 let response = result
238 .into_typed_schema_value()
239 .unwrap_or_else(|err| panic!("Failed serializing durable function result: {err}"));
240 let response = crate::encode_typed_schema_value(&response)
241 .unwrap_or_else(|err| panic!("Failed encoding durable function result: {err}"));
242 LiveCustomDurableInvocation::finish(invocation, response, forced_commit);
243 }
244
245 fn replay_serializable(
246 &self,
247 invocation: &PersistedDurableFunctionInvocation,
248 ) -> Result<SOk, SErr>
249 where
250 SOk: FromSchema,
251 SErr: FromSchema,
252 {
253 Self::validate_oplog_entry(invocation, &self.function_name());
254 let response = crate::decode_typed_schema_value(&invocation.response)
255 .unwrap_or_else(|err| panic!("Failed decoding durable function response: {err}"));
256 FromSchema::from_value(response.value())
257 .unwrap_or_else(|err| panic!("Unexpected durable function payload: {err}"))
258 }
259
260 fn function_name(&self) -> String {
261 if self.interface.is_empty() {
262 self.function.to_string()
264 } else {
265 format!("{}::{}", self.interface, self.function)
266 }
267 }
268
269 fn validate_oplog_entry(
270 oplog_entry: &PersistedDurableFunctionInvocation,
271 expected_function_name: &str,
272 ) {
273 if oplog_entry.function_name != expected_function_name {
274 panic!(
275 "Unexpected imported function call entry in oplog: expected {}, got {}",
276 expected_function_name, oplog_entry.function_name
277 );
278 }
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use crate::durability::DurableFunctionType;
285 use crate::{FromSchema, IntoSchema};
286 use std::io::Error;
287
288 #[allow(dead_code)]
291 fn durability_interface_test() {
292 #[derive(Debug, IntoSchema, FromSchema)]
293 enum CustomError {
294 Error1,
295 Error2,
296 }
297
298 impl From<&std::io::Error> for CustomError {
299 fn from(_value: &Error) -> Self {
300 Self::Error1
301 }
302 }
303
304 impl From<CustomError> for std::io::Error {
305 fn from(value: CustomError) -> Self {
306 Error::other(format!("{value:?}"))
307 }
308 }
309
310 fn durable_fn() -> Result<u64, std::io::Error> {
311 let input = "input".to_string();
312 super::Durability::<u64, CustomError>::new(
313 "custom",
314 "random-number-generator",
315 DurableFunctionType::ReadLocal,
316 &input,
317 )
318 .run(|| Ok(1234))
319 }
320 }
321}