Make Ident consistent with vanilla. (#309)

## Description

Makes `Ident` consistent with vanilla by prepending the default
namespace if none is provided in the constructor.

Previously, the constructor did not normalize `foo` to `minecraft:foo`.
This could lead to subtle bugs when the ident is eventually unwrapped
with `Ident::as_str`. (comparing `foo` with `minecraft:foo` while inside
the `Ident` was still handled correctly).

## Test Plan

Steps:
1. `cargo test`
This commit is contained in:
Ryan Johnson 2023-03-25 18:44:45 -07:00 committed by GitHub
parent ba625b3217
commit 53573642ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 411 additions and 379 deletions

View file

@ -221,7 +221,7 @@ impl Client {
pub fn send_custom_payload(&mut self, channel: Ident<&str>, data: &[u8]) { pub fn send_custom_payload(&mut self, channel: Ident<&str>, data: &[u8]) {
self.write_packet(&CustomPayloadS2c { self.write_packet(&CustomPayloadS2c {
channel, channel: channel.into(),
data: data.into(), data: data.into(),
}); });
} }
@ -682,13 +682,13 @@ fn initial_join(
let dimension_names = server let dimension_names = server
.dimensions() .dimensions()
.map(|(_, dim)| dim.name.as_str_ident()) .map(|(_, dim)| dim.name.as_str_ident().into())
.collect(); .collect();
let dimension_name = server.dimension(instance.dimension()).name.as_str_ident(); let dimension_name = server.dimension(instance.dimension()).name.as_str_ident();
let last_death_location = q.death_loc.0.map(|(id, pos)| GlobalPos { let last_death_location = q.death_loc.0.map(|(id, pos)| GlobalPos {
dimension_name: server.dimension(id).name.as_str_ident(), dimension_name: server.dimension(id).name.as_str_ident().into(),
position: pos, position: pos,
}); });
@ -701,8 +701,8 @@ fn initial_join(
previous_game_mode: q.prev_game_mode.0.map(|g| g as i8).unwrap_or(-1), previous_game_mode: q.prev_game_mode.0.map(|g| g as i8).unwrap_or(-1),
dimension_names, dimension_names,
registry_codec: Cow::Borrowed(server.registry_codec()), registry_codec: Cow::Borrowed(server.registry_codec()),
dimension_type_name: dimension_name, dimension_type_name: dimension_name.into(),
dimension_name, dimension_name: dimension_name.into(),
hashed_seed: q.hashed_seed.0 as i64, hashed_seed: q.hashed_seed.0 as i64,
max_players: VarInt(0), // Ignored by clients. max_players: VarInt(0), // Ignored by clients.
view_distance: VarInt(q.view_distance.0 as i32), view_distance: VarInt(q.view_distance.0 as i32),
@ -756,13 +756,13 @@ fn respawn(
let dimension_name = server.dimension(instance.dimension()).name.as_str_ident(); let dimension_name = server.dimension(instance.dimension()).name.as_str_ident();
let last_death_location = death_loc.0.map(|(id, pos)| GlobalPos { let last_death_location = death_loc.0.map(|(id, pos)| GlobalPos {
dimension_name: server.dimension(id).name.as_str_ident(), dimension_name: server.dimension(id).name.as_str_ident().into(),
position: pos, position: pos,
}); });
client.write_packet(&PlayerRespawnS2c { client.write_packet(&PlayerRespawnS2c {
dimension_type_name: dimension_name, dimension_type_name: dimension_name.into(),
dimension_name, dimension_name: dimension_name.into(),
hashed_seed: hashed_seed.0, hashed_seed: hashed_seed.0,
game_mode: (*game_mode).into(), game_mode: (*game_mode).into(),
previous_game_mode: prev_game_mode.0.map(|g| g as i8).unwrap_or(-1), previous_game_mode: prev_game_mode.0.map(|g| g as i8).unwrap_or(-1),

View file

@ -138,7 +138,7 @@ pub struct CloseHandledScreen {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CustomPayload { pub struct CustomPayload {
pub client: Entity, pub client: Entity,
pub channel: Ident<Box<str>>, pub channel: Ident<String>,
pub data: Box<[u8]>, pub data: Box<[u8]>,
} }
@ -263,7 +263,7 @@ pub struct PickFromInventory {
pub struct CraftRequest { pub struct CraftRequest {
pub client: Entity, pub client: Entity,
pub window_id: i8, pub window_id: i8,
pub recipe: Ident<Box<str>>, pub recipe: Ident<String>,
pub make_all: bool, pub make_all: bool,
} }
@ -354,7 +354,7 @@ pub struct RecipeCategoryOptions {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RecipeBookData { pub struct RecipeBookData {
pub client: Entity, pub client: Entity,
pub recipe_id: Ident<Box<str>>, pub recipe_id: Ident<String>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -395,7 +395,7 @@ pub struct ResourcePackStatusChange {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct OpenAdvancementTab { pub struct OpenAdvancementTab {
pub client: Entity, pub client: Entity,
pub tab_id: Ident<Box<str>>, pub tab_id: Ident<String>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -452,9 +452,9 @@ pub struct CreativeInventoryAction {
pub struct UpdateJigsaw { pub struct UpdateJigsaw {
pub client: Entity, pub client: Entity,
pub position: BlockPos, pub position: BlockPos,
pub name: Ident<Box<str>>, pub name: Ident<String>,
pub target: Ident<Box<str>>, pub target: Ident<String>,
pub pool: Ident<Box<str>>, pub pool: Ident<String>,
pub final_state: Box<str>, pub final_state: Box<str>,
pub joint_type: Box<str>, pub joint_type: Box<str>,
} }

View file

@ -22,7 +22,6 @@ use tokio::sync::OwnedSemaphorePermit;
use tracing::{error, info, instrument, trace, warn}; use tracing::{error, info, instrument, trace, warn};
use uuid::Uuid; use uuid::Uuid;
use valence_protocol::codec::{PacketDecoder, PacketEncoder}; use valence_protocol::codec::{PacketDecoder, PacketEncoder};
use valence_protocol::ident::Ident;
use valence_protocol::packet::c2s::handshake::handshake::NextState; use valence_protocol::packet::c2s::handshake::handshake::NextState;
use valence_protocol::packet::c2s::handshake::HandshakeC2s; use valence_protocol::packet::c2s::handshake::HandshakeC2s;
use valence_protocol::packet::c2s::login::{LoginHelloC2s, LoginKeyC2s, LoginQueryResponseC2s}; use valence_protocol::packet::c2s::login::{LoginHelloC2s, LoginKeyC2s, LoginQueryResponseC2s};
@ -35,7 +34,7 @@ use valence_protocol::raw::RawBytes;
use valence_protocol::text::Text; use valence_protocol::text::Text;
use valence_protocol::types::Property; use valence_protocol::types::Property;
use valence_protocol::var_int::VarInt; use valence_protocol::var_int::VarInt;
use valence_protocol::{translation_key, Decode, MINECRAFT_VERSION, PROTOCOL_VERSION}; use valence_protocol::{ident_str, translation_key, Decode, MINECRAFT_VERSION, PROTOCOL_VERSION};
use crate::config::{AsyncCallbacks, ConnectionMode, ServerListPing}; use crate::config::{AsyncCallbacks, ConnectionMode, ServerListPing};
use crate::server::connection::InitialConnection; use crate::server::connection::InitialConnection;
@ -440,7 +439,7 @@ pub(super) async fn login_velocity(
// Send Player Info Request into the Plugin Channel // Send Player Info Request into the Plugin Channel
conn.send_packet(&LoginQueryRequestS2c { conn.send_packet(&LoginQueryRequestS2c {
message_id: VarInt(message_id), message_id: VarInt(message_id),
channel: Ident::new("velocity:player_info").unwrap(), channel: ident_str!("velocity:player_info").into(),
data: RawBytes(&[VELOCITY_MIN_SUPPORTED_VERSION]), data: RawBytes(&[VELOCITY_MIN_SUPPORTED_VERSION]),
}) })
.await?; .await?;

View file

@ -1,3 +1,5 @@
use std::borrow::Cow;
use num_integer::{div_ceil, Integer}; use num_integer::{div_ceil, Integer};
use thiserror::Error; use thiserror::Error;
use valence::biome::BiomeId; use valence::biome::BiomeId;
@ -210,11 +212,11 @@ where
converted_biome_palette.clear(); converted_biome_palette.clear();
for biome_name in palette { for biome_name in palette {
let Ok(ident) = Ident::new(biome_name.as_str()) else { let Ok(ident) = Ident::<Cow<str>>::new(biome_name) else {
return Err(ToValenceError::BadBiomeName) return Err(ToValenceError::BadBiomeName)
}; };
converted_biome_palette.push(map_biome(ident)); converted_biome_palette.push(map_biome(ident.as_str_ident()));
} }
if converted_biome_palette.len() == 1 { if converted_biome_palette.len() == 1 {
@ -274,7 +276,7 @@ where
let Ok(ident) = Ident::new(&ident[..]) else { let Ok(ident) = Ident::new(&ident[..]) else {
return Err(ToValenceError::UnknownBlockEntityIdent(ident.clone())); return Err(ToValenceError::UnknownBlockEntityIdent(ident.clone()));
}; };
let Some(kind) = BlockEntityKind::from_ident(ident) else { let Some(kind) = BlockEntityKind::from_ident(ident.as_str_ident()) else {
return Err(ToValenceError::UnknownBlockEntityIdent(ident.as_str().to_string())); return Err(ToValenceError::UnknownBlockEntityIdent(ident.as_str().to_string()));
}; };
let block_entity = BlockEntity { let block_entity = BlockEntity {

View file

@ -58,7 +58,7 @@ pub fn build() -> anyhow::Result<TokenStream> {
let str_name = &sound.name; let str_name = &sound.name;
let name = ident(str_name.to_pascal_case()); let name = ident(str_name.to_pascal_case());
quote! { quote! {
Self::#name => #str_name, Self::#name => ident_str!(#str_name),
} }
}) })
.collect::<TokenStream>(); .collect::<TokenStream>();
@ -105,8 +105,8 @@ pub fn build() -> anyhow::Result<TokenStream> {
} }
} }
/// Gets the snake_case name of this sound. /// Gets the identifier of this sound.
pub const fn to_str(self) -> &'static str { pub const fn to_ident(self) -> Ident<&'static str> {
match self { match self {
#sound_to_str_arms #sound_to_str_arms
} }

View file

@ -499,6 +499,8 @@ impl PacketDecoder {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::borrow::Cow;
use super::*; use super::*;
use crate::block_pos::BlockPos; use crate::block_pos::BlockPos;
use crate::ident::Ident; use crate::ident::Ident;
@ -521,7 +523,7 @@ mod tests {
e: f64, e: f64,
f: BlockPos, f: BlockPos,
g: Hand, g: Hand,
h: Ident<&'a str>, h: Ident<Cow<'a, str>>,
i: Option<ItemStack>, i: Option<ItemStack>,
j: Text, j: Text,
k: VarInt, k: VarInt,

View file

@ -1,17 +1,15 @@
//! Resource identifiers. //! Resource identifiers.
use std::borrow::Cow; use std::borrow::{Borrow, Cow};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::error::Error;
use std::fmt; use std::fmt;
use std::fmt::Formatter; use std::fmt::Formatter;
use std::hash::{Hash, Hasher};
use std::io::Write; use std::io::Write;
use std::str::FromStr; use std::str::FromStr;
use anyhow::anyhow;
use serde::de::Error as _; use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
use crate::{nbt, Decode, Encode}; use crate::{nbt, Decode, Encode};
@ -21,114 +19,230 @@ use crate::{nbt, Decode, Encode};
/// A resource identifier is a string divided into a "namespace" part and a /// A resource identifier is a string divided into a "namespace" part and a
/// "path" part. For instance `minecraft:apple` and `valence:frobnicator` are /// "path" part. For instance `minecraft:apple` and `valence:frobnicator` are
/// both valid identifiers. A string must match the regex /// both valid identifiers. A string must match the regex
/// `^([a-z0-9_.-]+:)?[a-z0-9_.-\/]+$` to be considered valid. /// `^([a-z0-9_.-]+:)?[a-z0-9_.-\/]+$` to be successfully parsed.
/// ///
/// If the namespace part is left off (the part before and including the colon) /// While parsing, if the namespace part is left off (the part before and
/// the namespace is considered to be "minecraft" for the purposes of equality, /// including the colon) then "minecraft:" is inserted at the beginning of the
/// ordering, and hashing. /// string.
/// #[derive(Copy, Clone, Eq, Ord, Hash)]
/// # Contract
///
/// The type `S` must meet the following criteria:
/// - All calls to [`AsRef::as_ref`] and [`Borrow::borrow`][borrow] while the
/// string is wrapped in `Ident` must return the same value.
///
/// [borrow]: std::borrow::Borrow::borrow
#[derive(Copy, Clone)]
pub struct Ident<S> { pub struct Ident<S> {
string: S, string: S,
path_start: usize,
} }
impl<S> Ident<S> { /// The error type created when an [`Ident`] cannot be parsed from a
/// Returns an Ident with the given fields /// string. Contains the string that failed to parse.
/// #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Error)]
/// # Safety #[error("invalid resource identifier \"{0}\"")]
/// This function does not check for the validity of the Ident. pub struct IdentError(pub String);
/// For a safe version use [`Ident::new`]
#[doc(hidden)] impl<'a> Ident<Cow<'a, str>> {
pub const fn new_unchecked(string: S, path_start: usize) -> Self { pub fn new(string: impl Into<Cow<'a, str>>) -> Result<Self, IdentError> {
Self { string, path_start } parse(string.into())
} }
} }
impl<S: AsRef<str>> Ident<S> { impl<S> Ident<S> {
pub fn new(string: S) -> Result<Self, IdentError<S>> { /// Internal API. Do not use.
#[doc(hidden)]
pub const fn new_unchecked(string: S) -> Self {
Self { string }
}
pub fn as_str(&self) -> &str
where
S: AsRef<str>,
{
self.string.as_ref()
}
pub fn as_str_ident(&self) -> Ident<&str>
where
S: AsRef<str>,
{
Ident {
string: self.as_str(),
}
}
pub fn into_inner(self) -> S {
self.string
}
/// Returns the namespace part of this resource identifier (the part before
/// the colon).
pub fn namespace(&self) -> &str
where
S: AsRef<str>,
{
self.namespace_and_path().0
}
/// Returns the path part of this resource identifier (the part after the
/// colon).
pub fn path(&self) -> &str
where
S: AsRef<str>,
{
self.namespace_and_path().1
}
pub fn namespace_and_path(&self) -> (&str, &str)
where
S: AsRef<str>,
{
self.as_str()
.split_once(':')
.expect("invalid resource identifier")
}
}
fn parse(string: Cow<str>) -> Result<Ident<Cow<str>>, IdentError> {
let check_namespace = |s: &str| { let check_namespace = |s: &str| {
!s.is_empty() !s.is_empty()
&& s.chars() && s.chars()
.all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-')) .all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-'))
}; };
let check_path = |s: &str| { let check_path = |s: &str| {
!s.is_empty() !s.is_empty()
&& s.chars() && s.chars()
.all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-' | '/')) .all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-' | '/'))
}; };
let str = string.as_ref(); match string.split_once(':') {
match str.split_once(':') {
Some((namespace, path)) if check_namespace(namespace) && check_path(path) => { Some((namespace, path)) if check_namespace(namespace) && check_path(path) => {
let path_start = namespace.len() + 1; Ok(Ident { string })
Ok(Self { string, path_start })
} }
None if check_path(str) => Ok(Self { None if check_path(&string) => Ok(Ident {
string, string: format!("minecraft:{string}").into(),
path_start: 0,
}), }),
_ => Err(IdentError(string)), _ => Err(IdentError(string.into())),
}
}
/// Returns the namespace part of this resource identifier.
///
/// If the underlying string does not contain a namespace followed by a
/// ':' character, `"minecraft"` is returned.
pub fn namespace(&self) -> &str {
if self.path_start == 0 {
"minecraft"
} else {
&self.string.as_ref()[..self.path_start - 1]
}
}
pub fn path(&self) -> &str {
&self.string.as_ref()[self.path_start..]
}
/// Returns the underlying string as a `str`.
pub fn as_str(&self) -> &str {
self.string.as_ref()
}
/// Borrows the underlying string and returns it as an `Ident`. This
/// operation is infallible and no checks need to be performed.
pub fn as_str_ident(&self) -> Ident<&str> {
Ident {
string: self.string.as_ref(),
path_start: self.path_start,
}
}
/// Consumes the identifier and returns the underlying string.
pub fn into_inner(self) -> S {
self.string
} }
} }
impl<'a, S: ?Sized> Ident<&'a S> { impl<S: AsRef<str>> AsRef<str> for Ident<S> {
/// Converts the underlying string to its owned representation and returns fn as_ref(&self) -> &str {
/// it as an `Ident`. This operation is infallible and no checks need to be self.string.as_ref()
/// performed.
pub fn to_owned_ident(&self) -> Ident<S::Owned>
where
S: ToOwned,
S::Owned: AsRef<str>,
{
Ident {
string: self.string.to_owned(),
path_start: self.path_start,
} }
}
impl<S> AsRef<S> for Ident<S> {
fn as_ref(&self) -> &S {
&self.string
}
}
impl<S: Borrow<str>> Borrow<str> for Ident<S> {
fn borrow(&self) -> &str {
self.string.borrow()
}
}
impl From<Ident<String>> for String {
fn from(value: Ident<String>) -> Self {
value.into_inner()
}
}
impl<'a> From<Ident<Cow<'a, str>>> for Cow<'a, str> {
fn from(value: Ident<Cow<'a, str>>) -> Self {
value.into_inner()
}
}
impl<'a> From<Ident<Cow<'a, str>>> for Ident<String> {
fn from(value: Ident<Cow<'a, str>>) -> Self {
Self {
string: value.string.into(),
}
}
}
impl<'a> From<Ident<String>> for Ident<Cow<'a, str>> {
fn from(value: Ident<String>) -> Self {
Self {
string: value.string.into(),
}
}
}
impl<'a> From<Ident<&'a str>> for Ident<Cow<'a, str>> {
fn from(value: Ident<&'a str>) -> Self {
Ident {
string: value.string.into(),
}
}
}
impl<'a> From<Ident<&'a str>> for Ident<String> {
fn from(value: Ident<&'a str>) -> Self {
Ident {
string: value.string.into(),
}
}
}
impl FromStr for Ident<String> {
type Err = IdentError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Ident::new(s)?.into())
}
}
impl FromStr for Ident<Cow<'static, str>> {
type Err = IdentError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ident::<String>::try_from(s).map(From::from)
}
}
impl<'a> TryFrom<&'a str> for Ident<String> {
type Error = IdentError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Ok(Ident::new(value)?.into())
}
}
impl TryFrom<String> for Ident<String> {
type Error = IdentError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Ok(Ident::new(value)?.into())
}
}
impl<'a> TryFrom<Cow<'a, str>> for Ident<String> {
type Error = IdentError;
fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> {
Ok(Ident::new(value)?.into())
}
}
impl<'a> TryFrom<&'a str> for Ident<Cow<'a, str>> {
type Error = IdentError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl<'a> TryFrom<String> for Ident<Cow<'a, str>> {
type Error = IdentError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl<'a> TryFrom<Cow<'a, str>> for Ident<Cow<'a, str>> {
type Error = IdentError;
fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> {
Self::new(value)
} }
} }
@ -138,121 +252,56 @@ impl<S: fmt::Debug> fmt::Debug for Ident<S> {
} }
} }
impl<'a> From<Ident<&'a str>> for Ident<String> { impl<S: fmt::Display> fmt::Display for Ident<S> {
fn from(value: Ident<&'a str>) -> Self {
value.to_owned_ident()
}
}
impl<'a> From<Ident<&'a str>> for Ident<Box<str>> {
fn from(value: Ident<&'a str>) -> Self {
Ident {
string: value.string.into(),
path_start: value.path_start,
}
}
}
impl<'a> From<Ident<&'a str>> for Ident<Cow<'a, str>> {
fn from(value: Ident<&'a str>) -> Self {
Ident {
string: Cow::Borrowed(value.string),
path_start: value.path_start,
}
}
}
impl<'a> From<Ident<Cow<'a, str>>> for Ident<String> {
fn from(value: Ident<Cow<'a, str>>) -> Self {
Ident {
string: value.string.into_owned(),
path_start: value.path_start,
}
}
}
impl FromStr for Ident<String> {
type Err = IdentError<String>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ident::new(s.to_owned())
}
}
impl TryFrom<String> for Ident<String> {
type Error = IdentError<String>;
fn try_from(value: String) -> Result<Self, Self::Error> {
Ident::new(value)
}
}
impl<S> From<Ident<S>> for String
where
S: Into<String> + AsRef<str>,
{
fn from(id: Ident<S>) -> Self {
if id.path_start == 0 {
format!("minecraft:{}", id.string.as_ref())
} else {
id.string.into()
}
}
}
impl<S> fmt::Display for Ident<S>
where
S: AsRef<str>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.namespace(), self.path()) self.string.fmt(f)
} }
} }
impl<S, T> PartialEq<Ident<T>> for Ident<S> impl<S, T> PartialEq<Ident<T>> for Ident<S>
where where
S: AsRef<str>, S: PartialEq<T>,
T: AsRef<str>,
{ {
fn eq(&self, other: &Ident<T>) -> bool { fn eq(&self, other: &Ident<T>) -> bool {
self.namespace() == other.namespace() && self.path() == other.path() self.string == other.string
} }
} }
impl<S> Eq for Ident<S> where S: AsRef<str> {}
impl<S, T> PartialOrd<Ident<T>> for Ident<S> impl<S, T> PartialOrd<Ident<T>> for Ident<S>
where where
S: AsRef<str>, S: PartialOrd<T>,
T: AsRef<str>,
{ {
fn partial_cmp(&self, other: &Ident<T>) -> Option<Ordering> { fn partial_cmp(&self, other: &Ident<T>) -> Option<Ordering> {
(self.namespace(), self.path()).partial_cmp(&(other.namespace(), other.path())) self.string.partial_cmp(&other.string)
} }
} }
impl<S> Ord for Ident<S> impl<S: Encode> Encode for Ident<S> {
where fn encode(&self, w: impl Write) -> anyhow::Result<()> {
S: AsRef<str>, self.as_ref().encode(w)
{
fn cmp(&self, other: &Self) -> Ordering {
(self.namespace(), self.path()).cmp(&(other.namespace(), other.path()))
} }
} }
impl<S> Hash for Ident<S> impl<'a, S> Decode<'a> for Ident<S>
where where
S: AsRef<str>, S: Decode<'a>,
Ident<S>: TryFrom<S, Error = IdentError>,
{ {
fn hash<H: Hasher>(&self, state: &mut H) { fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
(self.namespace(), self.path()).hash(state); Ok(Ident::try_from(S::decode(r)?)?)
} }
} }
impl<T> Serialize for Ident<T> impl<S> From<Ident<S>> for nbt::Value
where where
T: Serialize, S: Into<nbt::Value>,
{ {
fn from(value: Ident<S>) -> Self {
value.into_inner().into()
}
}
impl<T: Serialize> Serialize for Ident<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
@ -261,67 +310,19 @@ where
} }
} }
impl<'de, T> Deserialize<'de> for Ident<T> impl<'de, S> Deserialize<'de> for Ident<S>
where where
T: Deserialize<'de> + AsRef<str>, S: Deserialize<'de>,
Ident<S>: TryFrom<S, Error = IdentError>,
{ {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
Ident::new(T::deserialize(deserializer)?).map_err(D::Error::custom) Ident::try_from(S::deserialize(deserializer)?).map_err(D::Error::custom)
} }
} }
impl<S: Encode> Encode for Ident<S> {
fn encode(&self, w: impl Write) -> anyhow::Result<()> {
self.string.encode(w)
}
}
impl<'a, S> Decode<'a> for Ident<S>
where
S: Decode<'a> + AsRef<str>,
{
fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
Ident::new(S::decode(r)?).map_err(|e| anyhow!("{e:#}"))
}
}
impl<S> From<Ident<S>> for nbt::Value
where
S: Into<nbt::Value>,
{
fn from(id: Ident<S>) -> Self {
id.string.into()
}
}
/// The error type created when an [`Ident`] cannot be parsed from a
/// string. Contains the offending string.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IdentError<S>(pub S);
impl<S> fmt::Debug for IdentError<S>
where
S: AsRef<str>,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_tuple("IdentError").field(&self.0.as_ref()).finish()
}
}
impl<S> fmt::Display for IdentError<S>
where
S: AsRef<str>,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "invalid resource identifier \"{}\"", self.0.as_ref())
}
}
impl<S> Error for IdentError<S> where S: AsRef<str> {}
/// Convenience macro for constructing an [`Ident<String>`] from a format /// Convenience macro for constructing an [`Ident<String>`] from a format
/// string. /// string.
/// ///
@ -350,17 +351,12 @@ impl<S> Error for IdentError<S> where S: AsRef<str> {}
#[macro_export] #[macro_export]
macro_rules! ident { macro_rules! ident {
($($arg:tt)*) => {{ ($($arg:tt)*) => {{
$crate::ident::Ident::new(::std::format!($($arg)*)).unwrap() $crate::ident::Ident::<String>::try_from(::std::format!($($arg)*)).unwrap()
}} }}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
use super::*;
#[test] #[test]
fn check_namespace_and_path() { fn check_namespace_and_path() {
let id = ident!("namespace:path"); let id = ident!("namespace:path");
@ -397,15 +393,4 @@ mod tests {
fn equality() { fn equality() {
assert_eq!(ident!("minecraft:my.identifier"), ident!("my.identifier")); assert_eq!(ident!("minecraft:my.identifier"), ident!("my.identifier"));
} }
#[test]
fn equal_hash() {
let mut h1 = DefaultHasher::new();
ident!("minecraft:my.identifier").hash(&mut h1);
let mut h2 = DefaultHasher::new();
ident!("my.identifier").hash(&mut h2);
assert_eq!(h1.finish(), h2.finish());
}
} }

View file

@ -1,8 +1,10 @@
use std::borrow::Cow;
use crate::ident::Ident; use crate::ident::Ident;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub enum AdvancementTabC2s<'a> { pub enum AdvancementTabC2s<'a> {
OpenedTab { tab_id: Ident<&'a str> }, OpenedTab { tab_id: Ident<Cow<'a, str>> },
ClosedScreen, ClosedScreen,
} }

View file

@ -1,9 +1,11 @@
use std::borrow::Cow;
use crate::ident::Ident; use crate::ident::Ident;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub struct CraftRequestC2s<'a> { pub struct CraftRequestC2s<'a> {
pub window_id: i8, pub window_id: i8,
pub recipe: Ident<&'a str>, pub recipe: Ident<Cow<'a, str>>,
pub make_all: bool, pub make_all: bool,
} }

View file

@ -1,9 +1,11 @@
use std::borrow::Cow;
use crate::ident::Ident; use crate::ident::Ident;
use crate::raw::RawBytes; use crate::raw::RawBytes;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub struct CustomPayloadC2s<'a> { pub struct CustomPayloadC2s<'a> {
pub channel: Ident<&'a str>, pub channel: Ident<Cow<'a, str>>,
pub data: RawBytes<'a>, pub data: RawBytes<'a>,
} }

View file

@ -1,7 +1,9 @@
use std::borrow::Cow;
use crate::ident::Ident; use crate::ident::Ident;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub struct RecipeBookDataC2s<'a> { pub struct RecipeBookDataC2s<'a> {
pub recipe_id: Ident<&'a str>, pub recipe_id: Ident<Cow<'a, str>>,
} }

View file

@ -1,13 +1,15 @@
use std::borrow::Cow;
use crate::block_pos::BlockPos; use crate::block_pos::BlockPos;
use crate::ident::Ident; use crate::ident::Ident;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub struct UpdateJigsawC2s<'a> { pub struct UpdateJigsawC2s<'a> {
pub position: BlockPos, pub position: BlockPos,
pub name: Ident<&'a str>, pub name: Ident<Cow<'a, str>>,
pub target: Ident<&'a str>, pub target: Ident<Cow<'a, str>>,
pub pool: Ident<&'a str>, pub pool: Ident<Cow<'a, str>>,
pub final_state: &'a str, pub final_state: &'a str,
pub joint_type: &'a str, pub joint_type: &'a str,
} }

View file

@ -1,11 +1,13 @@
use std::borrow::Cow;
use crate::ident::Ident; use crate::ident::Ident;
use crate::raw::RawBytes; use crate::raw::RawBytes;
use crate::var_int::VarInt; use crate::var_int::VarInt;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub struct LoginQueryRequestS2c<'a> { pub struct LoginQueryRequestS2c<'a> {
pub message_id: VarInt, pub message_id: VarInt,
pub channel: Ident<&'a str>, pub channel: Ident<Cow<'a, str>>,
pub data: RawBytes<'a>, pub data: RawBytes<'a>,
} }

View file

@ -10,16 +10,16 @@ use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub struct AdvancementUpdateS2c<'a> { pub struct AdvancementUpdateS2c<'a> {
pub reset: bool, pub reset: bool,
pub advancement_mapping: Vec<(Ident<&'a str>, Advancement<'a>)>, pub advancement_mapping: Vec<(Ident<Cow<'a, str>>, Advancement<'a>)>,
pub identifiers: Vec<Ident<&'a str>>, pub identifiers: Vec<Ident<Cow<'a, str>>>,
pub progress_mapping: Vec<(Ident<&'a str>, Vec<AdvancementCriteria<'a>>)>, pub progress_mapping: Vec<(Ident<Cow<'a, str>>, Vec<AdvancementCriteria<'a>>)>,
} }
#[derive(Clone, PartialEq, Debug, Encode, Decode)] #[derive(Clone, PartialEq, Debug, Encode, Decode)]
pub struct Advancement<'a> { pub struct Advancement<'a> {
pub parent_id: Option<Ident<&'a str>>, pub parent_id: Option<Ident<Cow<'a, str>>>,
pub display_data: Option<AdvancementDisplay<'a>>, pub display_data: Option<AdvancementDisplay<'a>>,
pub criteria: Vec<(Ident<&'a str>, ())>, pub criteria: Vec<(Ident<Cow<'a, str>>, ())>,
pub requirements: Vec<AdvancementRequirements<'a>>, pub requirements: Vec<AdvancementRequirements<'a>>,
} }
@ -35,14 +35,14 @@ pub struct AdvancementDisplay<'a> {
pub icon: Option<ItemStack>, pub icon: Option<ItemStack>,
pub frame_type: VarInt, pub frame_type: VarInt,
pub flags: i32, pub flags: i32,
pub background_texture: Option<Ident<&'a str>>, pub background_texture: Option<Ident<Cow<'a, str>>>,
pub x_coord: f32, pub x_coord: f32,
pub y_coord: f32, pub y_coord: f32,
} }
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub struct AdvancementCriteria<'a> { pub struct AdvancementCriteria<'a> {
pub criterion_identifier: Ident<&'a str>, pub criterion_identifier: Ident<Cow<'a, str>>,
/// If present, the criteria has been achieved at the /// If present, the criteria has been achieved at the
/// time wrapped; time represented as millis since epoch /// time wrapped; time represented as millis since epoch
pub criterion_progress: Option<i64>, pub criterion_progress: Option<i64>,
@ -56,7 +56,7 @@ impl Encode for AdvancementDisplay<'_> {
self.frame_type.encode(&mut w)?; self.frame_type.encode(&mut w)?;
self.flags.encode(&mut w)?; self.flags.encode(&mut w)?;
match self.background_texture { match self.background_texture.as_ref() {
None => {} None => {}
Some(texture) => texture.encode(&mut w)?, Some(texture) => texture.encode(&mut w)?,
} }
@ -77,7 +77,7 @@ impl<'a> Decode<'a> for AdvancementDisplay<'a> {
let flags = i32::decode(r)?; let flags = i32::decode(r)?;
let background_texture = if flags & 1 == 1 { let background_texture = if flags & 1 == 1 {
Some(Ident::<&'a str>::decode(r)?) Some(Ident::decode(r)?)
} else { } else {
None None
}; };

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::io::Write; use std::io::Write;
use anyhow::bail; use anyhow::bail;
@ -86,10 +87,10 @@ pub enum Parser<'a> {
Dimension, Dimension,
GameMode, GameMode,
Time, Time,
ResourceOrTag { registry: Ident<&'a str> }, ResourceOrTag { registry: Ident<Cow<'a, str>> },
ResourceOrTagKey { registry: Ident<&'a str> }, ResourceOrTagKey { registry: Ident<Cow<'a, str>> },
Resource { registry: Ident<&'a str> }, Resource { registry: Ident<Cow<'a, str>> },
ResourceKey { registry: Ident<&'a str> }, ResourceKey { registry: Ident<Cow<'a, str>> },
TemplateMirror, TemplateMirror,
TemplateRotation, TemplateRotation,
Uuid, Uuid,
@ -182,12 +183,12 @@ impl<'a> Decode<'a> for Node<'a> {
name: <&str>::decode(r)?, name: <&str>::decode(r)?,
parser: Parser::decode(r)?, parser: Parser::decode(r)?,
suggestion: if flags & 0x10 != 0 { suggestion: if flags & 0x10 != 0 {
Some(match Ident::<&str>::decode(r)?.path() { Some(match Ident::<Cow<str>>::decode(r)?.as_str() {
"ask_server" => Suggestion::AskServer, "minecraft:ask_server" => Suggestion::AskServer,
"all_recipes" => Suggestion::AllRecipes, "minecraft:all_recipes" => Suggestion::AllRecipes,
"available_sounds" => Suggestion::AvailableSounds, "minecraft:available_sounds" => Suggestion::AvailableSounds,
"available_biomes" => Suggestion::AvailableBiomes, "minecraft:available_biomes" => Suggestion::AvailableBiomes,
"summonable_entities" => Suggestion::SummonableEntities, "minecraft:summonable_entities" => Suggestion::SummonableEntities,
other => bail!("unknown command suggestion type of \"{other}\""), other => bail!("unknown command suggestion type of \"{other}\""),
}) })
} else { } else {

View file

@ -1,8 +1,10 @@
use std::borrow::Cow;
use crate::ident::Ident; use crate::ident::Ident;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub struct CraftFailedResponseS2c<'a> { pub struct CraftFailedResponseS2c<'a> {
pub window_id: u8, pub window_id: u8,
pub recipe: Ident<&'a str>, pub recipe: Ident<Cow<'a, str>>,
} }

View file

@ -1,9 +1,11 @@
use std::borrow::Cow;
use crate::ident::Ident; use crate::ident::Ident;
use crate::raw::RawBytes; use crate::raw::RawBytes;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub struct CustomPayloadS2c<'a> { pub struct CustomPayloadS2c<'a> {
pub channel: Ident<&'a str>, pub channel: Ident<Cow<'a, str>>,
pub data: RawBytes<'a>, pub data: RawBytes<'a>,
} }

View file

@ -1,3 +1,5 @@
use std::borrow::Cow;
use uuid::Uuid; use uuid::Uuid;
use crate::ident::Ident; use crate::ident::Ident;
@ -12,7 +14,7 @@ pub struct EntityAttributesS2c<'a> {
#[derive(Clone, PartialEq, Debug, Encode, Decode)] #[derive(Clone, PartialEq, Debug, Encode, Decode)]
pub struct AttributeProperty<'a> { pub struct AttributeProperty<'a> {
pub key: Ident<&'a str>, pub key: Ident<Cow<'a, str>>,
pub value: f64, pub value: f64,
pub modifiers: Vec<AttributeModifier>, pub modifiers: Vec<AttributeModifier>,
} }

View file

@ -1,9 +1,11 @@
use std::borrow::Cow;
use crate::ident::Ident; use crate::ident::Ident;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub struct ExplosionS2c<'a> { pub struct ExplosionS2c<'a> {
pub window_id: u8, pub window_id: u8,
pub recipe: Ident<&'a str>, pub recipe: Ident<Cow<'a, str>>,
pub make_all: bool, pub make_all: bool,
} }

View file

@ -1,7 +1,9 @@
use std::borrow::Cow;
use crate::ident::Ident; use crate::ident::Ident;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub struct FeaturesS2c<'a> { pub struct FeaturesS2c<'a> {
pub features: Vec<Ident<&'a str>>, pub features: Vec<Ident<Cow<'a, str>>>,
} }

View file

@ -14,10 +14,10 @@ pub struct GameJoinS2c<'a> {
pub game_mode: GameMode, pub game_mode: GameMode,
/// Same values as `game_mode` but with -1 to indicate no previous. /// Same values as `game_mode` but with -1 to indicate no previous.
pub previous_game_mode: i8, pub previous_game_mode: i8,
pub dimension_names: Vec<Ident<&'a str>>, pub dimension_names: Vec<Ident<Cow<'a, str>>>,
pub registry_codec: Cow<'a, Compound>, pub registry_codec: Cow<'a, Compound>,
pub dimension_type_name: Ident<&'a str>, pub dimension_type_name: Ident<Cow<'a, str>>,
pub dimension_name: Ident<&'a str>, pub dimension_name: Ident<Cow<'a, str>>,
pub hashed_seed: i64, pub hashed_seed: i64,
pub max_players: VarInt, pub max_players: VarInt,
pub view_distance: VarInt, pub view_distance: VarInt,

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::io::Write; use std::io::Write;
use crate::ident::Ident; use crate::ident::Ident;
@ -5,7 +6,7 @@ use crate::types::SoundCategory;
use crate::var_int::VarInt; use crate::var_int::VarInt;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub struct PlaySoundS2c<'a> { pub struct PlaySoundS2c<'a> {
pub id: SoundId<'a>, pub id: SoundId<'a>,
pub category: SoundCategory, pub category: SoundCategory,
@ -15,10 +16,10 @@ pub struct PlaySoundS2c<'a> {
pub seed: i64, pub seed: i64,
} }
#[derive(Copy, Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum SoundId<'a> { pub enum SoundId<'a> {
Direct { Direct {
id: Ident<&'a str>, id: Ident<Cow<'a, str>>,
range: Option<f32>, range: Option<f32>,
}, },
Reference { Reference {
@ -47,7 +48,7 @@ impl<'a> Decode<'a> for SoundId<'a> {
if i == 0 { if i == 0 {
Ok(SoundId::Direct { Ok(SoundId::Direct {
id: <Ident<&'a str>>::decode(r)?, id: Ident::decode(r)?,
range: <Option<f32>>::decode(r)?, range: <Option<f32>>::decode(r)?,
}) })
} else { } else {

View file

@ -1,11 +1,13 @@
use std::borrow::Cow;
use crate::ident::Ident; use crate::ident::Ident;
use crate::types::{GameMode, GlobalPos}; use crate::types::{GameMode, GlobalPos};
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Clone, PartialEq, Debug, Encode, Decode)] #[derive(Clone, PartialEq, Debug, Encode, Decode)]
pub struct PlayerRespawnS2c<'a> { pub struct PlayerRespawnS2c<'a> {
pub dimension_type_name: Ident<&'a str>, pub dimension_type_name: Ident<Cow<'a, str>>,
pub dimension_name: Ident<&'a str>, pub dimension_name: Ident<Cow<'a, str>>,
pub hashed_seed: u64, pub hashed_seed: u64,
pub game_mode: GameMode, pub game_mode: GameMode,
pub previous_game_mode: i8, pub previous_game_mode: i8,

View file

@ -1,7 +1,9 @@
use std::borrow::Cow;
use crate::ident::Ident; use crate::ident::Ident;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode)]
pub struct SelectAdvancementsTabS2c<'a> { pub struct SelectAdvancementsTabS2c<'a> {
pub identifier: Option<Ident<&'a str>>, pub identifier: Option<Ident<Cow<'a, str>>>,
} }

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::io::Write; use std::io::Write;
use crate::ident::Ident; use crate::ident::Ident;
@ -7,12 +8,12 @@ use crate::{Decode, Encode};
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct StopSoundS2c<'a> { pub struct StopSoundS2c<'a> {
pub source: Option<SoundCategory>, pub source: Option<SoundCategory>,
pub sound: Option<Ident<&'a str>>, pub sound: Option<Ident<Cow<'a, str>>>,
} }
impl Encode for StopSoundS2c<'_> { impl Encode for StopSoundS2c<'_> {
fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
match (self.source, self.sound) { match (self.source, self.sound.as_ref()) {
(Some(source), Some(sound)) => { (Some(source), Some(sound)) => {
3i8.encode(&mut w)?; 3i8.encode(&mut w)?;
source.encode(&mut w)?; source.encode(&mut w)?;
@ -38,9 +39,9 @@ impl<'a> Decode<'a> for StopSoundS2c<'a> {
let (source, sound) = match i8::decode(r)? { let (source, sound) = match i8::decode(r)? {
3 => ( 3 => (
Some(SoundCategory::decode(r)?), Some(SoundCategory::decode(r)?),
Some(<Ident<&'a str>>::decode(r)?), Some(<Ident<Cow<'a, str>>>::decode(r)?),
), ),
2 => (None, Some(<Ident<&'a str>>::decode(r)?)), 2 => (None, Some(<Ident<Cow<'a, str>>>::decode(r)?)),
1 => (Some(SoundCategory::decode(r)?), None), 1 => (Some(SoundCategory::decode(r)?), None),
_ => (None, None), _ => (None, None),
}; };

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::io::Write; use std::io::Write;
use anyhow::{bail, ensure}; use anyhow::{bail, ensure};
@ -15,14 +16,14 @@ pub struct SynchronizeRecipesS2c<'a> {
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum Recipe<'a> { pub enum Recipe<'a> {
CraftingShapeless { CraftingShapeless {
recipe_id: Ident<&'a str>, recipe_id: Ident<Cow<'a, str>>,
group: &'a str, group: &'a str,
category: CraftingCategory, category: CraftingCategory,
ingredients: Vec<Ingredient>, ingredients: Vec<Ingredient>,
result: Option<ItemStack>, result: Option<ItemStack>,
}, },
CraftingShaped { CraftingShaped {
recipe_id: Ident<&'a str>, recipe_id: Ident<Cow<'a, str>>,
width: VarInt, width: VarInt,
height: VarInt, height: VarInt,
group: &'a str, group: &'a str,
@ -32,11 +33,11 @@ pub enum Recipe<'a> {
}, },
CraftingSpecial { CraftingSpecial {
kind: SpecialCraftingKind, kind: SpecialCraftingKind,
recipe_id: Ident<&'a str>, recipe_id: Ident<Cow<'a, str>>,
category: CraftingCategory, category: CraftingCategory,
}, },
Smelting { Smelting {
recipe_id: Ident<&'a str>, recipe_id: Ident<Cow<'a, str>>,
group: &'a str, group: &'a str,
category: SmeltCategory, category: SmeltCategory,
ingredient: Ingredient, ingredient: Ingredient,
@ -45,7 +46,7 @@ pub enum Recipe<'a> {
cooking_time: VarInt, cooking_time: VarInt,
}, },
Blasting { Blasting {
recipe_id: Ident<&'a str>, recipe_id: Ident<Cow<'a, str>>,
group: &'a str, group: &'a str,
category: SmeltCategory, category: SmeltCategory,
ingredient: Ingredient, ingredient: Ingredient,
@ -54,7 +55,7 @@ pub enum Recipe<'a> {
cooking_time: VarInt, cooking_time: VarInt,
}, },
Smoking { Smoking {
recipe_id: Ident<&'a str>, recipe_id: Ident<Cow<'a, str>>,
group: &'a str, group: &'a str,
category: SmeltCategory, category: SmeltCategory,
ingredient: Ingredient, ingredient: Ingredient,
@ -63,7 +64,7 @@ pub enum Recipe<'a> {
cooking_time: VarInt, cooking_time: VarInt,
}, },
CampfireCooking { CampfireCooking {
recipe_id: Ident<&'a str>, recipe_id: Ident<Cow<'a, str>>,
group: &'a str, group: &'a str,
category: SmeltCategory, category: SmeltCategory,
ingredient: Ingredient, ingredient: Ingredient,
@ -72,13 +73,13 @@ pub enum Recipe<'a> {
cooking_time: VarInt, cooking_time: VarInt,
}, },
Stonecutting { Stonecutting {
recipe_id: Ident<&'a str>, recipe_id: Ident<Cow<'a, str>>,
group: &'a str, group: &'a str,
ingredient: Ingredient, ingredient: Ingredient,
result: Option<ItemStack>, result: Option<ItemStack>,
}, },
Smithing { Smithing {
recipe_id: Ident<&'a str>, recipe_id: Ident<Cow<'a, str>>,
base: Ingredient, base: Ingredient,
addition: Ingredient, addition: Ingredient,
result: Option<ItemStack>, result: Option<ItemStack>,
@ -294,16 +295,16 @@ impl<'a> Encode for Recipe<'a> {
impl<'a> Decode<'a> for Recipe<'a> { impl<'a> Decode<'a> for Recipe<'a> {
fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> { fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
Ok(match Ident::<&str>::decode(r)?.path() { Ok(match Ident::<Cow<str>>::decode(r)?.as_str() {
"crafting_shapeless" => Self::CraftingShapeless { "minecraft:crafting_shapeless" => Self::CraftingShapeless {
recipe_id: Decode::decode(r)?, recipe_id: Decode::decode(r)?,
group: Decode::decode(r)?, group: Decode::decode(r)?,
category: Decode::decode(r)?, category: Decode::decode(r)?,
ingredients: Decode::decode(r)?, ingredients: Decode::decode(r)?,
result: Decode::decode(r)?, result: Decode::decode(r)?,
}, },
"crafting_shaped" => { "minecraft:crafting_shaped" => {
let recipe_id = Ident::<&str>::decode(r)?; let recipe_id = Ident::decode(r)?;
let width = VarInt::decode(r)?.0; let width = VarInt::decode(r)?.0;
let height = VarInt::decode(r)?.0; let height = VarInt::decode(r)?.0;
let group = <&str>::decode(r)?; let group = <&str>::decode(r)?;
@ -324,7 +325,7 @@ impl<'a> Decode<'a> for Recipe<'a> {
result: Decode::decode(r)?, result: Decode::decode(r)?,
} }
} }
"smelting" => Self::Smelting { "minecraft:smelting" => Self::Smelting {
recipe_id: Decode::decode(r)?, recipe_id: Decode::decode(r)?,
group: Decode::decode(r)?, group: Decode::decode(r)?,
category: Decode::decode(r)?, category: Decode::decode(r)?,
@ -333,7 +334,7 @@ impl<'a> Decode<'a> for Recipe<'a> {
experience: Decode::decode(r)?, experience: Decode::decode(r)?,
cooking_time: Decode::decode(r)?, cooking_time: Decode::decode(r)?,
}, },
"blasting" => Self::Blasting { "minecraft:blasting" => Self::Blasting {
recipe_id: Decode::decode(r)?, recipe_id: Decode::decode(r)?,
group: Decode::decode(r)?, group: Decode::decode(r)?,
category: Decode::decode(r)?, category: Decode::decode(r)?,
@ -342,7 +343,7 @@ impl<'a> Decode<'a> for Recipe<'a> {
experience: Decode::decode(r)?, experience: Decode::decode(r)?,
cooking_time: Decode::decode(r)?, cooking_time: Decode::decode(r)?,
}, },
"smoking" => Self::Smoking { "minecraft:smoking" => Self::Smoking {
recipe_id: Decode::decode(r)?, recipe_id: Decode::decode(r)?,
group: Decode::decode(r)?, group: Decode::decode(r)?,
category: Decode::decode(r)?, category: Decode::decode(r)?,
@ -351,7 +352,7 @@ impl<'a> Decode<'a> for Recipe<'a> {
experience: Decode::decode(r)?, experience: Decode::decode(r)?,
cooking_time: Decode::decode(r)?, cooking_time: Decode::decode(r)?,
}, },
"campfire_cooking" => Self::CampfireCooking { "minecraft:campfire_cooking" => Self::CampfireCooking {
recipe_id: Decode::decode(r)?, recipe_id: Decode::decode(r)?,
group: Decode::decode(r)?, group: Decode::decode(r)?,
category: Decode::decode(r)?, category: Decode::decode(r)?,
@ -360,13 +361,13 @@ impl<'a> Decode<'a> for Recipe<'a> {
experience: Decode::decode(r)?, experience: Decode::decode(r)?,
cooking_time: Decode::decode(r)?, cooking_time: Decode::decode(r)?,
}, },
"stonecutting" => Self::Stonecutting { "minecraft:stonecutting" => Self::Stonecutting {
recipe_id: Decode::decode(r)?, recipe_id: Decode::decode(r)?,
group: Decode::decode(r)?, group: Decode::decode(r)?,
ingredient: Decode::decode(r)?, ingredient: Decode::decode(r)?,
result: Decode::decode(r)?, result: Decode::decode(r)?,
}, },
"smithing" => Self::Smithing { "minecraft:smithing" => Self::Smithing {
recipe_id: Decode::decode(r)?, recipe_id: Decode::decode(r)?,
base: Decode::decode(r)?, base: Decode::decode(r)?,
addition: Decode::decode(r)?, addition: Decode::decode(r)?,
@ -374,22 +375,34 @@ impl<'a> Decode<'a> for Recipe<'a> {
}, },
other => Self::CraftingSpecial { other => Self::CraftingSpecial {
kind: match other { kind: match other {
"crafting_special_armordye" => SpecialCraftingKind::ArmorDye, "minecraft:crafting_special_armordye" => SpecialCraftingKind::ArmorDye,
"crafting_special_bookcloning" => SpecialCraftingKind::BookCloning, "minecraft:crafting_special_bookcloning" => SpecialCraftingKind::BookCloning,
"crafting_special_mapcloning" => SpecialCraftingKind::MapCloning, "minecraft:crafting_special_mapcloning" => SpecialCraftingKind::MapCloning,
"crafting_special_mapextending" => SpecialCraftingKind::MapExtending, "minecraft:crafting_special_mapextending" => SpecialCraftingKind::MapExtending,
"crafting_special_firework_rocket" => SpecialCraftingKind::FireworkRocket, "minecraft:crafting_special_firework_rocket" => {
"crafting_special_firework_star" => SpecialCraftingKind::FireworkStar, SpecialCraftingKind::FireworkRocket
"crafting_special_firework_star_fade" => SpecialCraftingKind::FireworkStarFade, }
"crafting_special_repairitem" => SpecialCraftingKind::RepairItem, "minecraft:crafting_special_firework_star" => SpecialCraftingKind::FireworkStar,
"crafting_special_tippedarrow" => SpecialCraftingKind::TippedArrow, "minecraft:crafting_special_firework_star_fade" => {
"crafting_special_bannerduplicate" => SpecialCraftingKind::BannerDuplicate, SpecialCraftingKind::FireworkStarFade
"crafting_special_banneraddpattern" => SpecialCraftingKind::BannerAddPattern, }
"crafting_special_shielddecoration" => SpecialCraftingKind::ShieldDecoration, "minecraft:crafting_special_repairitem" => SpecialCraftingKind::RepairItem,
"crafting_special_shulkerboxcoloring" => { "minecraft:crafting_special_tippedarrow" => SpecialCraftingKind::TippedArrow,
"minecraft:crafting_special_bannerduplicate" => {
SpecialCraftingKind::BannerDuplicate
}
"minecraft:crafting_special_banneraddpattern" => {
SpecialCraftingKind::BannerAddPattern
}
"minecraft:crafting_special_shielddecoration" => {
SpecialCraftingKind::ShieldDecoration
}
"minecraft:crafting_special_shulkerboxcoloring" => {
SpecialCraftingKind::ShulkerBoxColoring SpecialCraftingKind::ShulkerBoxColoring
} }
"crafting_special_suspiciousstew" => SpecialCraftingKind::SuspiciousStew, "minecraft:crafting_special_suspiciousstew" => {
SpecialCraftingKind::SuspiciousStew
}
_ => bail!("unknown recipe type \"{other}\""), _ => bail!("unknown recipe type \"{other}\""),
}, },
recipe_id: Decode::decode(r)?, recipe_id: Decode::decode(r)?,

View file

@ -1,3 +1,5 @@
use std::borrow::Cow;
use crate::ident::Ident; use crate::ident::Ident;
use crate::var_int::VarInt; use crate::var_int::VarInt;
use crate::{Decode, Encode}; use crate::{Decode, Encode};
@ -9,12 +11,12 @@ pub struct SynchronizeTagsS2c<'a> {
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub struct TagGroup<'a> { pub struct TagGroup<'a> {
pub kind: Ident<&'a str>, pub kind: Ident<Cow<'a, str>>,
pub tags: Vec<Tag<'a>>, pub tags: Vec<Tag<'a>>,
} }
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub struct Tag<'a> { pub struct Tag<'a> {
pub name: Ident<&'a str>, pub name: Ident<Cow<'a, str>>,
pub entries: Vec<VarInt>, pub entries: Vec<VarInt>,
} }

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::io::Write; use std::io::Write;
use anyhow::bail; use anyhow::bail;
@ -17,12 +18,14 @@ pub struct UnlockRecipesS2c<'a> {
pub blast_furnace_recipe_book_filter_active: bool, pub blast_furnace_recipe_book_filter_active: bool,
pub smoker_recipe_book_open: bool, pub smoker_recipe_book_open: bool,
pub smoker_recipe_book_filter_active: bool, pub smoker_recipe_book_filter_active: bool,
pub recipe_ids: Vec<Ident<&'a str>>, pub recipe_ids: Vec<Ident<Cow<'a, str>>>,
} }
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub enum UpdateRecipeBookAction<'a> { pub enum UpdateRecipeBookAction<'a> {
Init { recipe_ids: Vec<Ident<&'a str>> }, Init {
recipe_ids: Vec<Ident<Cow<'a, str>>>,
},
Add, Add,
Remove, Remove,
} }

View file

@ -1,4 +1,5 @@
use crate::ident::Ident; use crate::ident::Ident;
use crate::ident_str;
use crate::packet::s2c::play::play_sound::SoundId; use crate::packet::s2c::play::play_sound::SoundId;
include!(concat!(env!("OUT_DIR"), "/sound.rs")); include!(concat!(env!("OUT_DIR"), "/sound.rs"));
@ -6,7 +7,7 @@ include!(concat!(env!("OUT_DIR"), "/sound.rs"));
impl Sound { impl Sound {
pub fn to_id(self) -> SoundId<'static> { pub fn to_id(self) -> SoundId<'static> {
SoundId::Direct { SoundId::Direct {
id: Ident::new(self.to_str()).unwrap(), // TODO: use ident_str. id: self.to_ident().into(),
range: None, range: None,
} }
} }

View file

@ -1084,8 +1084,7 @@ mod tests {
let txt = Text::storage_nbt(ident!("foo"), "bar", Some(true), Some("baz".into())); let txt = Text::storage_nbt(ident!("foo"), "bar", Some(true), Some("baz".into()));
let serialized = serde_json::to_string(&txt).unwrap(); let serialized = serde_json::to_string(&txt).unwrap();
let deserialized: Text = serde_json::from_str(&serialized).unwrap(); let deserialized: Text = serde_json::from_str(&serialized).unwrap();
let expected = let expected = r#"{"storage":"minecraft:foo","nbt":"bar","interpret":true,"separator":{"text":"baz"}}"#;
r#"{"storage":"foo","nbt":"bar","interpret":true,"separator":{"text":"baz"}}"#;
assert_eq!(serialized, expected); assert_eq!(serialized, expected);
assert_eq!(txt, deserialized); assert_eq!(txt, deserialized);
} }

View file

@ -1,5 +1,6 @@
//! Miscellaneous type definitions used in packets. //! Miscellaneous type definitions used in packets.
use std::borrow::Cow;
use std::io::Write; use std::io::Write;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -61,9 +62,9 @@ pub enum GameMode {
Spectator, Spectator,
} }
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub struct GlobalPos<'a> { pub struct GlobalPos<'a> {
pub dimension_name: Ident<&'a str>, pub dimension_name: Ident<Cow<'a, str>>,
pub position: BlockPos, pub position: BlockPos,
} }

View file

@ -16,26 +16,22 @@ fn check_path(s: &str) -> bool {
pub fn ident_str(item: TokenStream) -> Result<TokenStream> { pub fn ident_str(item: TokenStream) -> Result<TokenStream> {
let ident_lit: LitStr = parse2(item)?; let ident_lit: LitStr = parse2(item)?;
let mut ident = &ident_lit.value()[..]; let mut ident = ident_lit.value();
let path_start = match ident.split_once(':') { match ident.split_once(':') {
Some(("minecraft", path)) if check_path(path) => { Some((namespace, path)) if check_namespace(namespace) && check_path(path) => {}
ident = path; None if check_path(&ident) => {
0 ident = format!("minecraft:{ident}");
} }
Some((namespace, path)) if check_namespace(namespace) && check_path(path) => {
namespace.len() + 1
}
None if check_path(ident) => 0,
_ => { _ => {
return Err(syn::Error::new( return Err(syn::Error::new(
ident_lit.span(), ident_lit.span(),
"string cannot be parsed as ident", "string cannot be parsed as a resource identifier",
)) ))
} }
}; }
Ok(quote! { Ok(quote! {
::valence_protocol::ident::Ident::new_unchecked(#ident, #path_start) ::valence_protocol::ident::Ident::new_unchecked(#ident)
}) })
} }