Skip to main content

mcproto_types/
lib.rs

1//! Minecraft protocol types and their wire-codec interface.
2//!
3//! The crate models protocol values and provides their encoding and decoding
4//! through [`TypeCodec`] and [`ContextualCodec`].
5
6extern crate self as mcproto_types;
7
8pub mod basic;
9pub mod chat_type;
10pub mod component;
11pub mod contextual;
12pub mod debug;
13pub mod entity_metadata;
14pub mod json_text_component;
15pub mod lightdata;
16pub mod nbt;
17pub mod profile;
18pub mod recipe;
19pub mod slot;
20pub mod sound_event;
21pub mod teleport_flags;
22pub mod text_component;
23
24/// Re-exports all protocol types at the crate root.
25///
26/// The original module paths remain available, so both
27/// `mcproto_types::basic::VarInt` and `mcproto_types::VarInt` are supported.
28pub use basic::*;
29pub use chat_type::*;
30pub use component::*;
31pub use contextual::*;
32pub use debug::*;
33pub use entity_metadata::*;
34pub use json_text_component::*;
35pub use lightdata::*;
36pub use nbt::*;
37pub use profile::*;
38pub use recipe::*;
39pub use slot::*;
40pub use sound_event::*;
41pub use teleport_flags::*;
42pub use text_component::*;
43
44/// Re-exports of protocol codec derive macros.
45///
46/// [`ProtocolEnum`] implements both [`ProtocolEnum`](trait@ProtocolEnum) and
47/// [`TypeCodec`] for a fieldless enum. [`TypeStructCodec`] implements
48/// [`TypeCodec`] for a structure by processing its fields in declaration order.
49pub use mcproto_derive::{ProtocolEnum, TypeStructCodec};
50
51/// Encodes and decodes a value whose wire representation depends on external
52/// protocol context.
53///
54/// Unlike [`TypeCodec`], this trait receives a [`Context`](contextual::Context)
55/// supplied by the enclosing packet or data structure. The context itself is
56/// not written to or read from the wire. For an optional field, the caller must
57/// determine whether the field is present from the surrounding protocol data.
58///
59/// # Examples
60///
61/// A generic helper can encode any contextual value using context supplied by
62/// its enclosing packet:
63///
64/// ```
65/// use mcproto_codec::error::CodecError;
66/// use mcproto_types::{ContextualCodec, contextual::Context};
67///
68/// fn encode_contextual<T: ContextualCodec>(
69///     value: &T,
70///     context: &Context,
71/// ) -> Result<Vec<u8>, CodecError> {
72///     let mut encoded = Vec::new();
73///     value.encode_with_context(&mut encoded, context)?;
74///     Ok(encoded)
75/// }
76/// ```
77pub trait ContextualCodec {
78    /// Encodes this value using context supplied by its enclosing structure.
79    fn encode_with_context(
80        &self,
81        writer: &mut impl std::io::Write,
82        context: &contextual::Context,
83    ) -> Result<(), mcproto_codec::error::CodecError>;
84
85    /// Decodes this value using context supplied by its enclosing structure.
86    fn decode_with_context(
87        reader: &mut impl std::io::Read,
88        context: &contextual::Context,
89    ) -> Result<Self, mcproto_codec::error::CodecError>
90    where
91        Self: Sized;
92}
93
94/// Encodes and decodes a value with a context-independent wire representation.
95pub trait TypeCodec {
96    /// Encodes this value to the writer.
97    fn encode(
98        &self,
99        writer: &mut impl std::io::Write,
100    ) -> Result<(), mcproto_codec::error::CodecError>;
101
102    /// Decodes this value from the reader.
103    fn decode(reader: &mut impl std::io::Read) -> Result<Self, mcproto_codec::error::CodecError>
104    where
105        Self: Sized;
106}
107
108/// A numeric protocol type that can represent an enum discriminant.
109///
110/// This trait is implemented by the numeric types in [`basic`]. It is used by
111/// [`ProtocolEnum`] to map an enum's discriminants to its wire representation.
112/// Custom numeric protocol types may implement this trait to support them in
113/// `#[derive(ProtocolEnum)]`.
114pub trait EnumRepr: TypeCodec {
115    /// Converts a Rust enum discriminant into this wire representation.
116    #[must_use]
117    fn from_discriminant(value: i128) -> Option<Self>
118    where
119        Self: Sized;
120
121    /// Returns this representation as a numeric enum discriminant.
122    #[must_use]
123    fn discriminant(&self) -> i128;
124}
125
126/// Maps a fieldless Rust enum to a numeric Minecraft protocol representation.
127///
128/// Implement this trait with [`ProtocolEnum`] derive:
129///
130/// ```
131/// use mcproto_types::{ProtocolEnum, TypeCodec, basic::VarInt};
132///
133/// #[derive(Debug, Clone, Copy, PartialEq, Eq, ProtocolEnum)]
134/// #[protocol_enum(repr = VarInt)]
135/// enum GameMode {
136///     Survival = 0,
137///     Creative = 1,
138/// }
139///
140/// let mut encoded = Vec::new();
141/// GameMode::Creative.encode(&mut encoded)?;
142/// assert_eq!(encoded, [0x01]);
143/// # Ok::<(), mcproto_codec::error::CodecError>(())
144/// ```
145pub trait ProtocolEnum: Sized {
146    /// The numeric protocol type used for this enum on the wire.
147    type Repr: EnumRepr;
148
149    /// Returns this variant's numeric discriminant.
150    #[must_use]
151    fn discriminant(&self) -> i128;
152
153    /// Converts this variant to its wire representation.
154    ///
155    /// Returns [`None`] when the discriminant does not fit in [`Self::Repr`].
156    #[must_use]
157    fn to_repr(&self) -> Option<Self::Repr>;
158
159    /// Maps a decoded wire representation to a declared enum variant.
160    ///
161    /// Returns [`None`] for a value that does not name a declared variant.
162    #[must_use]
163    fn from_repr(repr: Self::Repr) -> Option<Self>;
164}
165
166/// Implementation details used by derive macros.
167///
168/// This module is public only so [`ProtocolEnum`] can be expanded in crates
169/// that depend on `mcproto-types`; it is not part of the stable public API.
170#[doc(hidden)]
171pub mod __private {
172    use std::io::{self, Read, Write};
173
174    pub use mcproto_codec::error::{CodecError, CodecKind};
175
176    use super::{EnumRepr, ProtocolEnum, TypeCodec};
177    use mcproto_codec::error::{CodecOperation, InvalidEncodingReason};
178
179    /// Encodes a value generated by [`ProtocolEnum`] derive.
180    pub fn encode_protocol_enum<E: ProtocolEnum>(
181        value: &E,
182        writer: &mut impl Write,
183    ) -> Result<(), CodecError> {
184        let repr = value.to_repr().ok_or_else(|| {
185            CodecError::invalid_encoding_for_operation(
186                CodecKind::Enum,
187                CodecOperation::Write,
188                0,
189                InvalidEncodingReason::EnumDiscriminantOutOfRange {
190                    value: value.discriminant(),
191                },
192            )
193        })?;
194        repr.encode(writer)
195            .map_err(|error| error.with_context(CodecKind::Enum))
196    }
197
198    /// Decodes a value generated by [`ProtocolEnum`] derive.
199    pub fn decode_protocol_enum<E: ProtocolEnum>(reader: &mut impl Read) -> Result<E, CodecError> {
200        let mut reader = CountingReader {
201            reader,
202            bytes_processed: 0,
203        };
204        let repr =
205            E::Repr::decode(&mut reader).map_err(|error| error.with_context(CodecKind::Enum))?;
206        let value = repr.discriminant();
207
208        E::from_repr(repr).ok_or_else(|| {
209            CodecError::invalid_encoding(
210                CodecKind::Enum,
211                reader.bytes_processed,
212                InvalidEncodingReason::InvalidEnumValue { value },
213            )
214        })
215    }
216
217    struct CountingReader<'a, R> {
218        reader: &'a mut R,
219        bytes_processed: usize,
220    }
221
222    impl<R: Read> Read for CountingReader<'_, R> {
223        fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
224            let read = self.reader.read(buffer)?;
225            self.bytes_processed += read;
226            Ok(read)
227        }
228    }
229}
230
231/// Adapts a context-independent [`TypeCodec`] to [`ContextualCodec`].
232///
233/// The supplied context is ignored because the value's wire representation is
234/// already complete without external information. Context-sensitive types
235/// should implement [`ContextualCodec`] directly instead of [`TypeCodec`].
236impl<T> ContextualCodec for T
237where
238    T: TypeCodec,
239{
240    fn encode_with_context(
241        &self,
242        writer: &mut impl std::io::Write,
243        _context: &contextual::Context,
244    ) -> Result<(), mcproto_codec::error::CodecError> {
245        self.encode(writer)
246    }
247
248    fn decode_with_context(
249        reader: &mut impl std::io::Read,
250        _context: &contextual::Context,
251    ) -> Result<Self, mcproto_codec::error::CodecError>
252    where
253        Self: Sized,
254    {
255        Self::decode(reader)
256    }
257}
258
259/// Encodes a boxed protocol value using the value's own codec.
260impl<T> TypeCodec for Box<T>
261where
262    T: TypeCodec,
263{
264    fn encode(
265        &self,
266        writer: &mut impl std::io::Write,
267    ) -> Result<(), mcproto_codec::error::CodecError> {
268        self.as_ref().encode(writer)
269    }
270
271    fn decode(reader: &mut impl std::io::Read) -> Result<Self, mcproto_codec::error::CodecError> {
272        T::decode(reader).map(Self::new)
273    }
274}