iroh_tickets/lib.rs
1//! Tickets is a serializable object combining information required for an operation.
2//! Typically tickets contain all information required for an operation, e.g. an iroh blob
3//! ticket would contain the hash of the data as well as information about how to reach the
4//! provider.
5use n0_error::{e, stack_error};
6
7pub mod endpoint;
8
9/// A ticket is a serializable object combining information required for an operation.
10///
11/// Tickets support serialization to a string using base32 encoding. The kind of
12/// ticket will be prepended to the string to make it somewhat self describing.
13///
14/// Versioning is left to the implementer. Some kinds of tickets might need
15/// versioning, others might not.
16///
17/// The serialization format for converting the ticket from and to bytes is left
18/// to the implementer. We recommend using [postcard] for serialization.
19///
20/// [postcard]: https://docs.rs/postcard/latest/postcard/
21pub trait Ticket: Sized {
22 /// String prefix describing the kind of iroh ticket.
23 ///
24 /// This should be lower case ascii characters.
25 const KIND: &'static str;
26
27 /// Serialize to bytes used in the base32 string representation.
28 fn to_bytes(&self) -> Vec<u8>;
29
30 /// Deserialize from the base32 string representation bytes.
31 fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError>;
32
33 /// Serialize to string.
34 fn serialize(&self) -> String {
35 let mut out = Self::KIND.to_string();
36 data_encoding::BASE32_NOPAD.encode_append(&self.to_bytes(), &mut out);
37 out.to_ascii_lowercase()
38 }
39
40 /// Deserialize from a string.
41 fn deserialize(str: &str) -> Result<Self, ParseError> {
42 let expected = Self::KIND;
43 let Some(rest) = str.strip_prefix(expected) else {
44 return Err(e!(ParseError::Kind { expected }));
45 };
46 let bytes = data_encoding::BASE32_NOPAD.decode(rest.to_ascii_uppercase().as_bytes())?;
47 let ticket = Self::from_bytes(&bytes)?;
48 Ok(ticket)
49 }
50}
51
52/// An error deserializing an iroh ticket.
53#[stack_error(derive, add_meta, from_sources)]
54#[allow(missing_docs)]
55#[non_exhaustive]
56pub enum ParseError {
57 /// Found a ticket with the wrong prefix, indicating the wrong kind.
58 #[error("wrong prefix, expected {expected}")]
59 Kind {
60 /// The expected prefix.
61 expected: &'static str,
62 },
63 /// This looks like a ticket, but postcard deserialization failed.
64 #[error(transparent)]
65 Postcard {
66 #[error(source, std_err)]
67 source: postcard::Error,
68 },
69 /// This looks like a ticket, but base32 decoding failed.
70 #[error(transparent)]
71 Encoding {
72 #[error(source, std_err)]
73 source: data_encoding::DecodeError,
74 },
75 /// Verification of the deserialized bytes failed.
76 #[error("verification failed: {message}")]
77 Verify { message: &'static str },
78}
79
80impl ParseError {
81 /// Returns a [`ParseError`] that indicates the given ticket has the wrong
82 /// prefix.
83 ///
84 /// Indicate the expected prefix.
85 pub fn wrong_prefix(expected: &'static str) -> Self {
86 e!(ParseError::Kind { expected })
87 }
88
89 /// Return a `ParseError` variant that indicates verification of the
90 /// deserialized bytes failed.
91 pub fn verification_failed(message: &'static str) -> Self {
92 e!(ParseError::Verify { message })
93 }
94}