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 { boundary: DeliveryAcceptance },
58 Failed { code: String, message: String },
59 Unknown { message: String },
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub enum DeliveryAcceptance {
65 ConnectionQueue,
67 Provider,
69}
70
71impl DeliveryOutcome {
72 pub fn is_accepted(&self) -> bool {
73 matches!(self, Self::Accepted { .. })
74 }
75
76 pub fn connection(accepted: bool) -> Self {
77 if accepted {
78 Self::Accepted {
79 boundary: DeliveryAcceptance::ConnectionQueue,
80 }
81 } else {
82 Self::Failed {
83 code: "UNREACHABLE".into(),
84 message: "Connection did not accept delivery".into(),
85 }
86 }
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
93#[serde(
94 tag = "kind",
95 rename_all = "camelCase",
96 rename_all_fields = "camelCase",
97 deny_unknown_fields
98)]
99pub enum HostConnectionControl {
100 DisconnectNode { connection_key: String },
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn acceptance_is_not_a_boolean_receipt() {
109 let result = DeliveryOutcome::connection(true);
110 assert_eq!(
111 serde_json::to_value(result).unwrap(),
112 serde_json::json!({
113 "status": "accepted", "boundary": "connectionQueue"
114 })
115 );
116 assert!(
117 serde_json::from_value::<DeliveryOutcome>(serde_json::json!({"delivered": true}))
118 .is_err()
119 );
120 }
121
122 #[test]
123 fn obsolete_output_effects_are_rejected() {
124 assert!(serde_json::from_value::<crate::ServiceCoreOutput>(
125 serde_json::json!({"effects": []})
126 )
127 .is_err());
128 }
129
130 #[test]
131 fn unknown_is_not_failure_or_acceptance() {
132 let result = DeliveryOutcome::Unknown {
133 message: "Timed out after dispatch".into(),
134 };
135 assert!(!result.is_accepted());
136 let encoded = serde_json::to_value(&result).unwrap();
137 assert_eq!(
138 serde_json::from_value::<DeliveryOutcome>(encoded).unwrap(),
139 result
140 );
141 }
142}