Skip to main content

vox_types/
retry_support.rs

1use crate::{Metadata, MetadataEntry, MetadataFlags, MetadataValue};
2
3pub const RETRY_SUPPORT_METADATA_KEY: &str = "vox-retry-support";
4pub const OPERATION_ID_METADATA_KEY: &str = "vox-operation-id";
5pub const CHANNEL_RETRY_MODE_METADATA_KEY: &str = "vox-channel-retry-mode";
6pub const RETRY_SUPPORT_VERSION: u64 = 1;
7
8#[derive(Clone, Copy, PartialEq, Eq, Debug)]
9pub enum ChannelRetryMode {
10    None = 0,
11    NonIdem = 1,
12    Idem = 2,
13}
14
15/// A unique operation identifier for exactly-once delivery.
16///
17/// Operation IDs are assigned by the client and carried in request metadata.
18/// They survive across disconnects — the operation store uses them to
19/// deduplicate and replay sealed responses after session resumption.
20#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
21pub struct OperationId(pub u64);
22
23impl std::fmt::Display for OperationId {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}", self.0)
26    }
27}
28
29/// Postcard-encoded bytes for a response payload (without schemas).
30///
31/// This is the format stored in the operation store. Schemas are stored
32/// separately, deduplicated by SchemaHash.
33#[derive(Clone, Debug)]
34pub struct PostcardPayload(pub Vec<u8>);
35
36impl PostcardPayload {
37    pub fn as_bytes(&self) -> &[u8] {
38        &self.0
39    }
40}
41
42pub fn append_retry_support_metadata(metadata: &mut Metadata<'_>) {
43    if metadata_supports_retry(metadata) {
44        return;
45    }
46    metadata.push(MetadataEntry {
47        key: RETRY_SUPPORT_METADATA_KEY,
48        value: MetadataValue::U64(RETRY_SUPPORT_VERSION),
49        flags: MetadataFlags::NONE,
50    });
51}
52
53pub fn metadata_supports_retry(metadata: &[MetadataEntry<'_>]) -> bool {
54    metadata.iter().any(|entry| {
55        entry.key == RETRY_SUPPORT_METADATA_KEY
56            && matches!(entry.value, MetadataValue::U64(RETRY_SUPPORT_VERSION))
57    })
58}
59
60pub fn metadata_operation_id(metadata: &[MetadataEntry<'_>]) -> Option<OperationId> {
61    metadata.iter().find_map(|entry| {
62        if entry.key != OPERATION_ID_METADATA_KEY {
63            return None;
64        }
65        match entry.value {
66            MetadataValue::U64(value) => Some(OperationId(value)),
67            _ => None,
68        }
69    })
70}
71
72pub fn ensure_operation_id(metadata: &mut Metadata<'_>, operation_id: OperationId) {
73    if metadata_operation_id(metadata).is_some() {
74        return;
75    }
76    metadata.push(MetadataEntry {
77        key: OPERATION_ID_METADATA_KEY,
78        value: MetadataValue::U64(operation_id.0),
79        flags: MetadataFlags::NONE,
80    });
81}
82
83pub fn metadata_channel_retry_mode(metadata: &[MetadataEntry<'_>]) -> ChannelRetryMode {
84    metadata
85        .iter()
86        .find_map(|entry| {
87            if entry.key != CHANNEL_RETRY_MODE_METADATA_KEY {
88                return None;
89            }
90            match entry.value {
91                MetadataValue::U64(1) => Some(ChannelRetryMode::NonIdem),
92                MetadataValue::U64(2) => Some(ChannelRetryMode::Idem),
93                _ => Some(ChannelRetryMode::None),
94            }
95        })
96        .unwrap_or(ChannelRetryMode::None)
97}
98
99pub fn ensure_channel_retry_mode(metadata: &mut Metadata<'_>, mode: ChannelRetryMode) {
100    if matches!(mode, ChannelRetryMode::None) {
101        return;
102    }
103    if metadata
104        .iter()
105        .any(|entry| entry.key == CHANNEL_RETRY_MODE_METADATA_KEY)
106    {
107        return;
108    }
109    metadata.push(MetadataEntry {
110        key: CHANNEL_RETRY_MODE_METADATA_KEY,
111        value: MetadataValue::U64(mode as u64),
112        flags: MetadataFlags::NONE,
113    });
114}