1use crate::MwsMessage;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase", deny_unknown_fields)]
8pub struct HostDeliveryRequest {
9 pub delivery_id: String,
11 pub expires_at_unix_ms: i64,
12 pub destination: HostDeliveryDestination,
13}
14
15impl HostDeliveryRequest {
16 pub fn remaining(&self) -> std::time::Duration {
17 let now = std::time::SystemTime::now()
18 .duration_since(std::time::UNIX_EPOCH)
19 .map(|duration| duration.as_millis())
20 .unwrap_or(u128::MAX);
21 let remaining = (self.expires_at_unix_ms.max(0) as u128).saturating_sub(now);
22 std::time::Duration::from_millis(remaining.min(u64::MAX as u128) as u64)
23 }
24 pub fn is_expired(&self) -> bool {
25 let now = std::time::SystemTime::now()
26 .duration_since(std::time::UNIX_EPOCH)
27 .map(|duration| duration.as_millis())
28 .unwrap_or(u128::MAX);
29 self.expires_at_unix_ms <= 0 || now >= self.expires_at_unix_ms as u128
30 }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(
35 tag = "kind",
36 rename_all = "camelCase",
37 rename_all_fields = "camelCase",
38 deny_unknown_fields
39)]
40pub enum HostDeliveryDestination {
41 AppConnection {
42 client_id: String,
43 message: MwsMessage,
44 },
45 NodeConnection {
46 connection_key: String,
47 message: MwsMessage,
48 },
49 Messaging {
50 request: crate::MessagingDeliveryRequest,
51 },
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(tag = "status", rename_all = "camelCase", deny_unknown_fields)]
56pub enum DeliveryOutcome {
57 Accepted {
58 boundary: DeliveryAcceptance,
59 },
60 Skipped {
62 reason: String,
63 },
64 Failed {
65 code: String,
66 message: String,
67 },
68 Unknown {
69 message: String,
70 },
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub enum DeliveryAcceptance {
76 ConnectionQueue,
78 Provider,
80}
81
82impl DeliveryOutcome {
83 pub fn is_accepted(&self) -> bool {
84 matches!(self, Self::Accepted { .. })
85 }
86
87 pub fn is_handled(&self) -> bool {
88 matches!(self, Self::Accepted { .. } | Self::Skipped { .. })
89 }
90
91 pub fn failed(code: impl Into<String>, message: impl Into<String>) -> Self {
92 Self::Failed {
93 code: code.into(),
94 message: message.into(),
95 }
96 }
97
98 pub fn connection(accepted: bool) -> Self {
99 if accepted {
100 Self::Accepted {
101 boundary: DeliveryAcceptance::ConnectionQueue,
102 }
103 } else {
104 Self::Failed {
105 code: "UNREACHABLE".into(),
106 message: "Connection did not accept delivery".into(),
107 }
108 }
109 }
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
115#[serde(
116 tag = "kind",
117 rename_all = "camelCase",
118 rename_all_fields = "camelCase",
119 deny_unknown_fields
120)]
121pub enum HostConnectionControl {
122 DisconnectNode { connection_key: String },
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn acceptance_is_not_a_boolean_receipt() {
131 let result = DeliveryOutcome::connection(true);
132 assert_eq!(
133 serde_json::to_value(result).unwrap(),
134 serde_json::json!({
135 "status": "accepted", "boundary": "connectionQueue"
136 })
137 );
138 assert!(
139 serde_json::from_value::<DeliveryOutcome>(serde_json::json!({"delivered": true}))
140 .is_err()
141 );
142 }
143
144 #[test]
145 fn skipped_is_handled_but_never_accepted() {
146 let outcome = DeliveryOutcome::Skipped {
147 reason: "No provider message".into(),
148 };
149 assert!(outcome.is_handled());
150 assert!(!outcome.is_accepted());
151 let response = crate::MessagingDeliveryResponse {
152 outcome: outcome.clone(),
153 };
154 let json = serde_json::to_value(response).unwrap();
155 let decoded: crate::MessagingDeliveryResponse = serde_json::from_value(json).unwrap();
156 assert_eq!(decoded.outcome, outcome);
157 assert!(serde_json::from_value::<crate::MessagingDeliveryResponse>(
158 serde_json::json!({"delivered": true})
159 )
160 .is_err());
161 }
162
163 #[test]
164 fn obsolete_output_effects_are_rejected() {
165 assert!(serde_json::from_value::<crate::ServiceCoreOutput>(
166 serde_json::json!({"effects": []})
167 )
168 .is_err());
169 }
170
171 #[test]
172 fn unknown_is_not_failure_or_acceptance() {
173 let result = DeliveryOutcome::Unknown {
174 message: "Timed out after dispatch".into(),
175 };
176 assert!(!result.is_accepted());
177 let encoded = serde_json::to_value(&result).unwrap();
178 assert_eq!(
179 serde_json::from_value::<DeliveryOutcome>(encoded).unwrap(),
180 result
181 );
182 }
183}