Skip to main content

ledgence_worker_api/
execution.rs

1//! Portable invocation requests and observations shared by workers and delivery adapters.
2//!
3//! A report describes local execution; it does not certify durable orchestration
4//! acceptance or exactly-once application effects.
5
6use crate::{
7    CloudEvent, Digest, Error, InvocationIdentity, ProgramDescriptor, ProgramOutcome, ProgramRef,
8};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ExecutionRequest {
14    pub descriptor: ProgramDescriptor,
15    pub event: CloudEvent,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ExecutionContext {
20    #[serde(flatten)]
21    pub identity: InvocationIdentity,
22    pub program: ProgramRef,
23    pub digest: Digest,
24}
25impl From<&ExecutionRequest> for ExecutionContext {
26    fn from(request: &ExecutionRequest) -> Self {
27        Self {
28            identity: InvocationIdentity::from(&request.event),
29            program: request.descriptor.program.clone(),
30            digest: request.descriptor.digest.clone(),
31        }
32    }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ExecutionReport {
37    #[serde(flatten)]
38    pub context: Box<ExecutionContext>,
39    pub process_id: u32,
40    pub reused_process: bool,
41    pub outcome: ProgramOutcome,
42    pub elapsed_ms: u64,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case")]
47pub enum Phase {
48    Admission,
49    Preparation,
50    Startup,
51    Execution,
52    Cleanup,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct ExecutionFailure {
57    #[serde(flatten)]
58    pub context: Box<ExecutionContext>,
59    pub error: Error,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub cleanup_error: Option<Error>,
62    pub phase: Phase,
63    /// Conservative: a lost response must not be retried inside the runtime.
64    pub execution_may_have_started: bool,
65}
66impl std::fmt::Display for ExecutionFailure {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        write!(f, "{:?}: {}", self.phase, self.error)
69    }
70}
71impl std::error::Error for ExecutionFailure {}
72pub type ExecutionResult = std::result::Result<ExecutionReport, ExecutionFailure>;
73
74/// Ephemeral runtime input. The carrier belongs to the current execution span;
75/// it does not modify the durable request or its immutable CloudEvent.
76#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(deny_unknown_fields)]
78pub struct RuntimeInvocation {
79    pub event: CloudEvent,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub processing_context: Option<crate::TraceContext>,
82    /// Optional platform context, separate from the wholly user-owned event data.
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub extension: Option<RuntimeExtension>,
85}
86impl From<CloudEvent> for RuntimeInvocation {
87    fn from(event: CloudEvent) -> Self {
88        Self {
89            event,
90            processing_context: None,
91            extension: None,
92        }
93    }
94}
95
96/// Versioned, runtime-neutral context for an opt-in interactive invocation.
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
98#[serde(deny_unknown_fields)]
99pub struct RuntimeExtension {
100    pub schema: String,
101    pub payload: Value,
102}
103impl RuntimeExtension {
104    pub fn validate(&self) -> crate::Result<()> {
105        validate_name(&self.schema, "runtime extension schema")?;
106        crate::validate_runtime_payload(&self.payload, crate::RUNTIME_EXTENSION_MAX_BYTES)
107    }
108}
109
110/// One invocation-scoped request. The host, not these fields, supplies authority.
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
112#[serde(deny_unknown_fields)]
113pub struct RuntimeRequest {
114    pub id: u64,
115    pub operation: String,
116    pub payload: Value,
117}
118impl RuntimeRequest {
119    pub fn validate(&self) -> crate::Result<()> {
120        if self.id == 0 {
121            return Err(Error::new(
122                crate::ErrorKind::InvalidInput,
123                "runtime request ID must be positive",
124            ));
125        }
126        validate_name(&self.operation, "runtime request operation")?;
127        crate::validate_runtime_payload(&self.payload, crate::RUNTIME_REQUEST_MAX_BYTES)
128    }
129}
130
131/// An acknowledgement for exactly one request; its result schema belongs to the handler.
132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
133#[serde(deny_unknown_fields)]
134pub struct RuntimeReply {
135    pub id: u64,
136    pub result: Value,
137}
138
139fn validate_name(value: &str, label: &str) -> crate::Result<()> {
140    if value.is_empty() || value.len() > 256 || value.chars().any(char::is_control) {
141        return Err(Error::new(
142            crate::ErrorKind::InvalidInput,
143            format!("{label} must contain 1..=256 bytes without control characters"),
144        ));
145    }
146    Ok(())
147}