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}