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]) {
self.write_packet(&CustomPayloadS2c {
channel,
channel: channel.into(),
data: data.into(),
});
}
@ -682,13 +682,13 @@ fn initial_join(
let dimension_names = server
.dimensions()
.map(|(_, dim)| dim.name.as_str_ident())
.map(|(_, dim)| dim.name.as_str_ident().into())
.collect();
let dimension_name = server.dimension(instance.dimension()).name.as_str_ident();
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,
});
@ -701,8 +701,8 @@ fn initial_join(
previous_game_mode: q.prev_game_mode.0.map(|g| g as i8).unwrap_or(-1),
dimension_names,
registry_codec: Cow::Borrowed(server.registry_codec()),
dimension_type_name: dimension_name,
dimension_name,
dimension_type_name: dimension_name.into(),
dimension_name: dimension_name.into(),
hashed_seed: q.hashed_seed.0 as i64,
max_players: VarInt(0), // Ignored by clients.
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 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,
});
client.write_packet(&PlayerRespawnS2c {
dimension_type_name: dimension_name,
dimension_name,
dimension_type_name: dimension_name.into(),
dimension_name: dimension_name.into(),
hashed_seed: hashed_seed.0,
game_mode: (*game_mode).into(),
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)]
pub struct CustomPayload {
pub client: Entity,
pub channel: Ident<Box<str>>,
pub channel: Ident<String>,
pub data: Box<[u8]>,
}
@ -263,7 +263,7 @@ pub struct PickFromInventory {
pub struct CraftRequest {
pub client: Entity,
pub window_id: i8,
pub recipe: Ident<Box<str>>,
pub recipe: Ident<String>,
pub make_all: bool,
}
@ -354,7 +354,7 @@ pub struct RecipeCategoryOptions {
#[derive(Clone, Debug)]
pub struct RecipeBookData {
pub client: Entity,
pub recipe_id: Ident<Box<str>>,
pub recipe_id: Ident<String>,
}
#[derive(Clone, Debug)]
@ -395,7 +395,7 @@ pub struct ResourcePackStatusChange {
#[derive(Clone, Debug)]
pub struct OpenAdvancementTab {
pub client: Entity,
pub tab_id: Ident<Box<str>>,
pub tab_id: Ident<String>,
}
#[derive(Clone, Debug)]
@ -452,9 +452,9 @@ pub struct CreativeInventoryAction {
pub struct UpdateJigsaw {
pub client: Entity,
pub position: BlockPos,
pub name: Ident<Box<str>>,
pub target: Ident<Box<str>>,
pub pool: Ident<Box<str>>,
pub name: Ident<String>,
pub target: Ident<String>,
pub pool: Ident<String>,
pub final_state: 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 uuid::Uuid;
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::HandshakeC2s;
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::types::Property;
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::server::connection::InitialConnection;
@ -440,7 +439,7 @@ pub(super) async fn login_velocity(
// Send Player Info Request into the Plugin Channel
conn.send_packet(&LoginQueryRequestS2c {
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]),
})
.await?;

View file

@ -1,3 +1,5 @@
use std::borrow::Cow;
use num_integer::{div_ceil, Integer};
use thiserror::Error;
use valence::biome::BiomeId;
@ -210,11 +212,11 @@ where
converted_biome_palette.clear();
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)
};
converted_biome_palette.push(map_biome(ident));
converted_biome_palette.push(map_biome(ident.as_str_ident()));
}
if converted_biome_palette.len() == 1 {
@ -274,7 +276,7 @@ where
let Ok(ident) = Ident::new(&ident[..]) else {
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()));
};
let block_entity = BlockEntity {

View file

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

View file

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

View file

@ -1,17 +1,15 @@
//! Resource identifiers.
use std::borrow::Cow;
use std::borrow::{Borrow, Cow};
use std::cmp::Ordering;
use std::error::Error;
use std::fmt;
use std::fmt::Formatter;
use std::hash::{Hash, Hasher};
use std::io::Write;
use std::str::FromStr;
use anyhow::anyhow;
use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
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
/// "path" part. For instance `minecraft:apple` and `valence:frobnicator` are
/// 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)
/// the namespace is considered to be "minecraft" for the purposes of equality,
/// ordering, and hashing.
///
/// # 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)]
/// While parsing, if the namespace part is left off (the part before and
/// including the colon) then "minecraft:" is inserted at the beginning of the
/// string.
#[derive(Copy, Clone, Eq, Ord, Hash)]
pub struct Ident<S> {
string: S,
path_start: usize,
}
/// The error type created when an [`Ident`] cannot be parsed from a
/// string. Contains the string that failed to parse.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Error)]
#[error("invalid resource identifier \"{0}\"")]
pub struct IdentError(pub String);
impl<'a> Ident<Cow<'a, str>> {
pub fn new(string: impl Into<Cow<'a, str>>) -> Result<Self, IdentError> {
parse(string.into())
}
}
impl<S> Ident<S> {
/// Returns an Ident with the given fields
///
/// # Safety
/// This function does not check for the validity of the Ident.
/// For a safe version use [`Ident::new`]
/// Internal API. Do not use.
#[doc(hidden)]
pub const fn new_unchecked(string: S, path_start: usize) -> Self {
Self { string, path_start }
}
}
impl<S: AsRef<str>> Ident<S> {
pub fn new(string: S) -> Result<Self, IdentError<S>> {
let check_namespace = |s: &str| {
!s.is_empty()
&& s.chars()
.all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-'))
};
let check_path = |s: &str| {
!s.is_empty()
&& s.chars()
.all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-' | '/'))
};
let str = string.as_ref();
match str.split_once(':') {
Some((namespace, path)) if check_namespace(namespace) && check_path(path) => {
let path_start = namespace.len() + 1;
Ok(Self { string, path_start })
}
None if check_path(str) => Ok(Self {
string,
path_start: 0,
}),
_ => Err(IdentError(string)),
}
pub const fn new_unchecked(string: S) -> Self {
Self { string }
}
/// 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 {
pub fn as_str(&self) -> &str
where
S: AsRef<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> {
pub fn as_str_ident(&self) -> Ident<&str>
where
S: AsRef<str>,
{
Ident {
string: self.string.as_ref(),
path_start: self.path_start,
string: self.as_str(),
}
}
/// Consumes the identifier and returns the underlying string.
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")
}
}
impl<'a, S: ?Sized> Ident<&'a S> {
/// Converts the underlying string to its owned representation and returns
/// it as an `Ident`. This operation is infallible and no checks need to be
/// 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,
fn parse(string: Cow<str>) -> Result<Ident<Cow<str>>, IdentError> {
let check_namespace = |s: &str| {
!s.is_empty()
&& s.chars()
.all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-'))
};
let check_path = |s: &str| {
!s.is_empty()
&& s.chars()
.all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-' | '/'))
};
match string.split_once(':') {
Some((namespace, path)) if check_namespace(namespace) && check_path(path) => {
Ok(Ident { string })
}
None if check_path(&string) => Ok(Ident {
string: format!("minecraft:{string}").into(),
}),
_ => Err(IdentError(string.into())),
}
}
impl<S: AsRef<str>> AsRef<str> for Ident<S> {
fn as_ref(&self) -> &str {
self.string.as_ref()
}
}
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> {
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>,
{
impl<S: fmt::Display> fmt::Display for Ident<S> {
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>
where
S: AsRef<str>,
T: AsRef<str>,
S: PartialEq<T>,
{
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>
where
S: AsRef<str>,
T: AsRef<str>,
S: PartialOrd<T>,
{
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>
where
S: AsRef<str>,
{
fn cmp(&self, other: &Self) -> Ordering {
(self.namespace(), self.path()).cmp(&(other.namespace(), other.path()))
impl<S: Encode> Encode for Ident<S> {
fn encode(&self, w: impl Write) -> anyhow::Result<()> {
self.as_ref().encode(w)
}
}
impl<S> Hash for Ident<S>
impl<'a, S> Decode<'a> for Ident<S>
where
S: AsRef<str>,
S: Decode<'a>,
Ident<S>: TryFrom<S, Error = IdentError>,
{
fn hash<H: Hasher>(&self, state: &mut H) {
(self.namespace(), self.path()).hash(state);
fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
Ok(Ident::try_from(S::decode(r)?)?)
}
}
impl<T> Serialize for Ident<T>
impl<S> From<Ident<S>> for nbt::Value
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>
where
S: Serializer,
@ -261,67 +310,19 @@ where
}
}
impl<'de, T> Deserialize<'de> for Ident<T>
impl<'de, S> Deserialize<'de> for Ident<S>
where
T: Deserialize<'de> + AsRef<str>,
S: Deserialize<'de>,
Ident<S>: TryFrom<S, Error = IdentError>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
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
/// string.
///
@ -350,17 +351,12 @@ impl<S> Error for IdentError<S> where S: AsRef<str> {}
#[macro_export]
macro_rules! ident {
($($arg:tt)*) => {{
$crate::ident::Ident::new(::std::format!($($arg)*)).unwrap()
$crate::ident::Ident::<String>::try_from(::std::format!($($arg)*)).unwrap()
}}
}
#[cfg(test)]
mod tests {
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
use super::*;
#[test]
fn check_namespace_and_path() {
let id = ident!("namespace:path");
@ -397,15 +393,4 @@ mod tests {
fn equality() {
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::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
#[derive(Clone, Debug, Encode, Decode)]
pub enum AdvancementTabC2s<'a> {
OpenedTab { tab_id: Ident<&'a str> },
OpenedTab { tab_id: Ident<Cow<'a, str>> },
ClosedScreen,
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,9 @@
use std::borrow::Cow;
use crate::ident::Ident;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
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,
/// Same values as `game_mode` but with -1 to indicate no previous.
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 dimension_type_name: Ident<&'a str>,
pub dimension_name: Ident<&'a str>,
pub dimension_type_name: Ident<Cow<'a, str>>,
pub dimension_name: Ident<Cow<'a, str>>,
pub hashed_seed: i64,
pub max_players: VarInt,
pub view_distance: VarInt,

View file

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

View file

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

View file

@ -1,7 +1,9 @@
use std::borrow::Cow;
use crate::ident::Ident;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
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 crate::ident::Ident;
@ -7,12 +8,12 @@ use crate::{Decode, Encode};
#[derive(Clone, PartialEq, Debug)]
pub struct StopSoundS2c<'a> {
pub source: Option<SoundCategory>,
pub sound: Option<Ident<&'a str>>,
pub sound: Option<Ident<Cow<'a, str>>>,
}
impl Encode for StopSoundS2c<'_> {
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)) => {
3i8.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)? {
3 => (
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),
_ => (None, None),
};

View file

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

View file

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

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::io::Write;
use anyhow::bail;
@ -17,12 +18,14 @@ pub struct UnlockRecipesS2c<'a> {
pub blast_furnace_recipe_book_filter_active: bool,
pub smoker_recipe_book_open: 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)]
pub enum UpdateRecipeBookAction<'a> {
Init { recipe_ids: Vec<Ident<&'a str>> },
Init {
recipe_ids: Vec<Ident<Cow<'a, str>>>,
},
Add,
Remove,
}

View file

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

View file

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

View file

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

View file

@ -16,26 +16,22 @@ fn check_path(s: &str) -> bool {
pub fn ident_str(item: TokenStream) -> Result<TokenStream> {
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(':') {
Some(("minecraft", path)) if check_path(path) => {
ident = path;
0
match ident.split_once(':') {
Some((namespace, path)) if check_namespace(namespace) && check_path(path) => {}
None if check_path(&ident) => {
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(
ident_lit.span(),
"string cannot be parsed as ident",
"string cannot be parsed as a resource identifier",
))
}
};
}
Ok(quote! {
::valence_protocol::ident::Ident::new_unchecked(#ident, #path_start)
::valence_protocol::ident::Ident::new_unchecked(#ident)
})
}