Skip to main content

relaygate_destination/
destination.rs

1use std::{fmt, str::FromStr};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{DestinationError, DestinationName, Namespace};
6
7/// A fully specified exact routing key.
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9#[serde(try_from = "String", into = "String")]
10pub struct Destination {
11    namespace: Namespace,
12    name: DestinationName,
13}
14
15impl Destination {
16    /// Combines validated components without revalidating them.
17    #[must_use]
18    pub const fn new(namespace: Namespace, name: DestinationName) -> Self {
19        Self { namespace, name }
20    }
21
22    /// Borrows the exact-match routing namespace.
23    #[must_use]
24    pub const fn namespace(&self) -> &Namespace {
25        &self.namespace
26    }
27
28    /// Borrows the namespace-local exact-match name.
29    #[must_use]
30    pub const fn name(&self) -> &DestinationName {
31        &self.name
32    }
33
34    /// Collision-unambiguous bytes used by the shard authority hash.
35    ///
36    /// Format: namespace byte length as big-endian u16, namespace bytes,
37    /// name byte length as big-endian u16, name bytes.
38    #[must_use]
39    pub fn canonical_key(&self) -> Vec<u8> {
40        let namespace = self.namespace.as_str().as_bytes();
41        let name = self.name.as_str().as_bytes();
42        let mut key = Vec::with_capacity(4 + namespace.len() + name.len());
43        key.extend_from_slice(&(namespace.len() as u16).to_be_bytes());
44        key.extend_from_slice(namespace);
45        key.extend_from_slice(&(name.len() as u16).to_be_bytes());
46        key.extend_from_slice(name);
47        key
48    }
49}
50
51impl FromStr for Destination {
52    type Err = DestinationError;
53
54    fn from_str(value: &str) -> Result<Self, Self::Err> {
55        let (namespace, name) = value
56            .split_once('/')
57            .ok_or(DestinationError::InvalidDestination)?;
58        if name.contains('/') {
59            return Err(DestinationError::InvalidDestination);
60        }
61        Ok(Self::new(
62            Namespace::new(namespace)?,
63            DestinationName::new(name)?,
64        ))
65    }
66}
67
68impl TryFrom<String> for Destination {
69    type Error = DestinationError;
70
71    fn try_from(value: String) -> Result<Self, Self::Error> {
72        value.parse()
73    }
74}
75
76impl From<Destination> for String {
77    fn from(value: Destination) -> Self {
78        value.to_string()
79    }
80}
81
82impl fmt::Display for Destination {
83    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
84        write!(formatter, "{}/{}", self.namespace, self.name)
85    }
86}