Skip to main content

r402_protocol/payment/
codec.rs

1//! JSON-safe primitives used by x402 payment envelopes.
2
3use std::fmt::{self, Display, Formatter};
4use std::ops::Add;
5use std::str::FromStr;
6use std::time::SystemTime;
7
8use base64::Engine;
9use base64::engine::general_purpose::STANDARD as B64;
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use serde_with::{DisplayFromStr, serde_as};
12
13/// Compile-time protocol-version marker that serializes as a bare integer.
14#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
15pub struct Version<const N: u8>;
16
17impl<const N: u8> Version<N> {
18    /// Numeric value of this version marker.
19    pub const VALUE: u8 = N;
20}
21
22impl<const N: u8> PartialEq<u8> for Version<N> {
23    fn eq(&self, other: &u8) -> bool {
24        *other == N
25    }
26}
27
28impl<const N: u8> From<Version<N>> for u8 {
29    fn from(_: Version<N>) -> Self {
30        N
31    }
32}
33
34impl<const N: u8> Display for Version<N> {
35    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
36        write!(f, "{N}")
37    }
38}
39
40impl<const N: u8> Serialize for Version<N> {
41    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
42        serializer.serialize_u8(N)
43    }
44}
45
46impl<'de, const N: u8> Deserialize<'de> for Version<N> {
47    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
48        let v = u8::deserialize(deserializer)?;
49        if v == N {
50            Ok(Self)
51        } else {
52            Err(serde::de::Error::custom(format!(
53                "expected protocol version {N}, got {v}"
54            )))
55        }
56    }
57}
58
59/// x402 v2 version marker.
60pub type Version2 = Version<2>;
61
62/// Singleton value for [`Version2`].
63pub const V2: Version2 = Version;
64
65/// Raw bytes holding the base64 ASCII of some payload.
66///
67/// Encoding is eager; decoding is deferred.
68///
69/// # Examples
70///
71/// ```
72/// use r402_protocol::payment::Base64Bytes;
73///
74/// let encoded = Base64Bytes::encode(b"hello world");
75/// assert!(encoded.decode().is_ok());
76/// ```
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct Base64Bytes(pub Vec<u8>);
79
80impl Base64Bytes {
81    /// Decodes the inner base64 bytes into raw binary.
82    ///
83    /// # Errors
84    ///
85    /// Returns a [`base64::DecodeError`] when the stored bytes are not valid
86    /// base64.
87    pub fn decode(&self) -> Result<Vec<u8>, base64::DecodeError> {
88        B64.decode(&self.0)
89    }
90
91    /// Encodes arbitrary bytes into a [`Base64Bytes`] wrapper.
92    #[must_use]
93    pub fn encode<T: AsRef<[u8]>>(input: T) -> Self {
94        Self(B64.encode(input.as_ref()).into_bytes())
95    }
96}
97
98impl AsRef<[u8]> for Base64Bytes {
99    fn as_ref(&self) -> &[u8] {
100        &self.0
101    }
102}
103
104impl From<&[u8]> for Base64Bytes {
105    fn from(slice: &[u8]) -> Self {
106        Self(slice.to_vec())
107    }
108}
109
110impl Display for Base64Bytes {
111    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
112        f.write_str(&String::from_utf8_lossy(&self.0))
113    }
114}
115
116/// A `u64` that serializes as a JSON string.
117///
118/// # Examples
119///
120/// ```
121/// use r402_protocol::payment::U64String;
122///
123/// let value = U64String::from(42_u64);
124/// assert_eq!(value.inner(), 42);
125/// assert_eq!(serde_json::to_string(&value).unwrap(), r#""42""#);
126/// ```
127#[serde_as]
128#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
129#[repr(transparent)]
130pub struct U64String(#[serde_as(as = "DisplayFromStr")] u64);
131
132impl U64String {
133    /// Wrapped `u64`.
134    #[must_use]
135    pub const fn inner(self) -> u64 {
136        self.0
137    }
138}
139
140impl Display for U64String {
141    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
142        Display::fmt(&self.0, f)
143    }
144}
145
146impl FromStr for U64String {
147    type Err = <u64 as FromStr>::Err;
148    fn from_str(s: &str) -> Result<Self, Self::Err> {
149        s.parse::<u64>().map(Self)
150    }
151}
152
153impl From<u64> for U64String {
154    fn from(value: u64) -> Self {
155        Self(value)
156    }
157}
158
159impl From<U64String> for u64 {
160    fn from(value: U64String) -> Self {
161        value.0
162    }
163}
164
165/// Unix timestamp in seconds since 1970-01-01T00:00:00Z.
166///
167/// Serializes as a JSON string; deserializes from a string or number.
168///
169/// # Examples
170///
171/// ```
172/// use r402_protocol::payment::UnixTimestamp;
173///
174/// let ts = UnixTimestamp::from_secs(1_700_000_000);
175/// assert_eq!(ts.as_secs(), 1_700_000_000);
176/// assert_eq!((ts + 3600).as_secs(), 1_700_003_600);
177/// assert_eq!(serde_json::to_string(&ts).unwrap(), r#""1700000000""#);
178/// ```
179#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
180pub struct UnixTimestamp(u64);
181
182impl UnixTimestamp {
183    /// Constructs a timestamp from raw Unix seconds.
184    #[must_use]
185    pub const fn from_secs(secs: u64) -> Self {
186        Self(secs)
187    }
188
189    /// Current system clock as a [`UnixTimestamp`].
190    ///
191    /// Falls back to the epoch if the clock is before 1970.
192    #[must_use]
193    pub fn now() -> Self {
194        let secs = SystemTime::now()
195            .duration_since(SystemTime::UNIX_EPOCH)
196            .map(|d| d.as_secs())
197            .unwrap_or_default();
198        Self(secs)
199    }
200
201    /// Timestamp as raw Unix seconds.
202    #[must_use]
203    pub const fn as_secs(self) -> u64 {
204        self.0
205    }
206}
207
208impl Display for UnixTimestamp {
209    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
210        Display::fmt(&self.0, f)
211    }
212}
213
214impl Add<u64> for UnixTimestamp {
215    type Output = Self;
216    fn add(self, rhs: u64) -> Self::Output {
217        Self(self.0.saturating_add(rhs))
218    }
219}
220
221impl Serialize for UnixTimestamp {
222    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
223        serializer.collect_str(&self.0)
224    }
225}
226
227impl<'de> Deserialize<'de> for UnixTimestamp {
228    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
229        struct Visitor;
230
231        impl serde::de::Visitor<'_> for Visitor {
232            type Value = UnixTimestamp;
233
234            fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result {
235                f.write_str("a non-negative integer or its string representation")
236            }
237
238            fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Self::Value, E> {
239                Ok(UnixTimestamp(v))
240            }
241
242            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
243                v.parse::<u64>()
244                    .map(UnixTimestamp)
245                    .map_err(|_| E::custom("expected non-negative integer"))
246            }
247        }
248
249        deserializer.deserialize_any(Visitor)
250    }
251}