diff --git a/packages/lib-roblox/src/datatypes/attributes.rs b/packages/lib-roblox/src/datatypes/attributes.rs new file mode 100644 index 0000000..b42acdb --- /dev/null +++ b/packages/lib-roblox/src/datatypes/attributes.rs @@ -0,0 +1,56 @@ +use mlua::prelude::*; + +use rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType}; + +use super::extension::DomValueExt; + +pub fn ensure_valid_attribute_name(name: impl AsRef) -> LuaResult<()> { + let name = name.as_ref(); + if name.to_ascii_uppercase().starts_with("RBX") { + Err(LuaError::RuntimeError( + "Attribute names must not start with the prefix \"RBX\"".to_string(), + )) + } else if !name.chars().all(|c| c == '_' || c.is_alphanumeric()) { + Err(LuaError::RuntimeError( + "Attribute names must only use alphanumeric characters and underscore".to_string(), + )) + } else if name.len() > 100 { + Err(LuaError::RuntimeError( + "Attribute names must be 100 characters or less in length".to_string(), + )) + } else { + Ok(()) + } +} + +pub fn ensure_valid_attribute_value(value: &DomValue) -> LuaResult<()> { + let is_valid = matches!( + value.ty(), + DomType::Bool + | DomType::BrickColor + | DomType::CFrame + | DomType::Color3 + | DomType::ColorSequence + | DomType::Float32 + | DomType::Float64 + | DomType::Int32 + | DomType::Int64 + | DomType::NumberRange + | DomType::NumberSequence + | DomType::Rect + | DomType::String + | DomType::UDim + | DomType::UDim2 + | DomType::Vector2 + | DomType::Vector3 + | DomType::Font + ); + if is_valid { + Ok(()) + } else { + Err(LuaError::RuntimeError(format!( + "'{}' is not a valid attribute type", + value.ty().variant_name() + ))) + } +} diff --git a/packages/lib-roblox/src/datatypes/extension.rs b/packages/lib-roblox/src/datatypes/extension.rs index 4328ef0..ed66a3a 100644 --- a/packages/lib-roblox/src/datatypes/extension.rs +++ b/packages/lib-roblox/src/datatypes/extension.rs @@ -1,6 +1,6 @@ use super::*; -pub(crate) trait DomValueExt { +pub(super) trait DomValueExt { fn variant_name(&self) -> &'static str; } diff --git a/packages/lib-roblox/src/datatypes/mod.rs b/packages/lib-roblox/src/datatypes/mod.rs index 86bb01f..9f3ced6 100644 --- a/packages/lib-roblox/src/datatypes/mod.rs +++ b/packages/lib-roblox/src/datatypes/mod.rs @@ -1,5 +1,6 @@ pub(crate) use rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType}; +pub mod attributes; pub mod conversion; pub mod extension; pub mod result; diff --git a/packages/lib-roblox/src/instance/mod.rs b/packages/lib-roblox/src/instance/mod.rs index fde459a..5762314 100644 --- a/packages/lib-roblox/src/instance/mod.rs +++ b/packages/lib-roblox/src/instance/mod.rs @@ -6,14 +6,14 @@ use std::{ use mlua::prelude::*; use rbx_dom_weak::{ - types::{Ref as DomRef, Variant as DomValue, VariantType as DomType}, + types::{Ref as DomRef, Variant as DomValue}, Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom, }; use crate::{ datatypes::{ + attributes::{ensure_valid_attribute_name, ensure_valid_attribute_value}, conversion::{DomValueToLua, LuaToDomValue}, - extension::DomValueExt, types::EnumItem, userdata_impl_eq, userdata_impl_to_string, }, @@ -400,40 +400,12 @@ impl Instance { .insert(name.as_ref().to_string(), value); } - fn ensure_valid_attribute_value(&self, value: &DomValue) -> LuaResult<()> { - let is_valid = matches!( - value.ty(), - DomType::Bool - | DomType::BrickColor - | DomType::CFrame - | DomType::Color3 - | DomType::ColorSequence - | DomType::Float32 - | DomType::Float64 - | DomType::Int32 - | DomType::Int64 - | DomType::NumberRange - | DomType::NumberSequence - | DomType::Rect - | DomType::String - | DomType::UDim - | DomType::UDim2 - | DomType::Vector2 - | DomType::Vector3 - | DomType::Font - ); - if is_valid { - Ok(()) - } else { - Err(LuaError::RuntimeError(format!( - "'{}' is not a valid attribute type", - value.ty().variant_name() - ))) - } - } - /** Gets an attribute for the instance, if it exists. + + ### See Also + * [`GetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttribute) + on the Roblox Developer Hub */ pub fn get_attribute(&self, name: impl AsRef) -> Option { let dom = INTERNAL_DOM @@ -451,6 +423,10 @@ impl Instance { /** Gets all known attributes for the instance. + + ### See Also + * [`GetAttributes`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttributes) + on the Roblox Developer Hub */ pub fn get_attributes(&self) -> BTreeMap { let dom = INTERNAL_DOM @@ -468,6 +444,10 @@ impl Instance { /** Sets an attribute for the instance. + + ### See Also + * [`SetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#SetAttribute) + on the Roblox Developer Hub */ pub fn set_attribute(&self, name: impl AsRef, value: DomValue) { let mut dom = INTERNAL_DOM @@ -968,9 +948,10 @@ impl LuaUserData for Instance { "SetAttribute", |lua, this, (attribute_name, lua_value): (String, LuaValue)| { this.ensure_not_destroyed()?; + ensure_valid_attribute_name(&attribute_name)?; match lua_value.lua_to_dom_value(lua, None) { Ok(dom_value) => { - this.ensure_valid_attribute_value(&dom_value)?; + ensure_valid_attribute_value(&dom_value)?; this.set_attribute(attribute_name, dom_value); Ok(()) }