summaryrefslogtreecommitdiff
path: root/systems/sheet/src/mapping.rs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-02-24 12:33:51 +0800
committer魏曹先生 <1992414357@qq.com>2026-02-24 12:34:15 +0800
commit2f251facf156b6c89e6be3ba690261556baa02fa (patch)
treeea5aeaff34551575536ea7a46de125ac4293fe59 /systems/sheet/src/mapping.rs
parent554cd69f91bb98eef9033531d9b1c3daee305c53 (diff)
Implement SheetSystem core library
Add IndexSource type for resource addressing and implement mapping system with LocalMapping, Mapping, and MappingBuf types. Create Sheet and SheetData structs for managing sheet data with editing capabilities. Implement binary format serialization/deserialization with reader and writer modules. Add constants for file format layout and comprehensive test suite for roundtrip verification.
Diffstat (limited to 'systems/sheet/src/mapping.rs')
-rw-r--r--systems/sheet/src/mapping.rs391
1 files changed, 281 insertions, 110 deletions
diff --git a/systems/sheet/src/mapping.rs b/systems/sheet/src/mapping.rs
index b31315d..0a72a69 100644
--- a/systems/sheet/src/mapping.rs
+++ b/systems/sheet/src/mapping.rs
@@ -1,20 +1,23 @@
-use string_proc::{
- format_path::{PathFormatConfig, format_path_str, format_path_str_with_config},
- snake_case,
-};
+use just_fmt::fmt_path::{PathFormatConfig, fmt_path_str, fmt_path_str_custom};
+
+use crate::{index_source::IndexSource, mapping::error::ParseMappingError};
+
+pub mod error;
+
+// Validation rules for LocalMapping
+// LocalMapping is a key component for writing and reading SheetData
+// According to the SheetData protocol specification, all variable-length fields store length information using the u8 type
+// Therefore, the lengths of the index_id, version, mapping_value, and forward fields must not exceed `u8::MAX`
/// Local mapping
/// It is stored inside a Sheet and will be exposed externally as Mapping or MappingBuf
-#[derive(Debug, PartialEq, Eq, Clone)]
+#[derive(Debug, Clone)]
pub struct LocalMapping {
/// The value of the local mapping
val: Vec<String>,
- /// The ID of the local mapping
- id: String,
-
- /// The version of the local mapping
- ver: String,
+ /// Index source
+ source: IndexSource,
/// The version direction of the local mapping
forward: LocalMappingForward,
@@ -22,9 +25,10 @@ pub struct LocalMapping {
/// The forward direction of the current Mapping
/// It indicates the expected asset update method for the current Mapping
-#[derive(Debug, PartialEq, Eq, Clone)]
+#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub enum LocalMappingForward {
/// Expect the current index version to be the latest
+ #[default]
Latest,
/// Expect the current index version to point to a specific Ref
@@ -33,28 +37,78 @@ pub enum LocalMappingForward {
Ref { sheet_name: String },
/// Expect the current index version to point to a specific version
- Version { version_name: String },
+ Version { version: u16 },
}
/// Mapping
/// It stores basic mapping information and only participates in comparison and parsing
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Mapping<'a> {
sheet_name: &'a str,
val: &'a str,
- id: &'a str,
- ver: &'a str,
+ source: IndexSource,
}
/// MappingBuf
/// It stores complete mapping information and participates in complex mapping editing operations like storage and modification
-#[derive(Debug, PartialEq, Eq, Clone)]
+#[derive(Debug, PartialEq, Clone)]
pub struct MappingBuf {
sheet_name: String,
val: Vec<String>,
val_joined: String,
- id: String,
- ver: String,
+ source: IndexSource,
+}
+
+// Implement conversions for LocalMappingForward
+
+impl LocalMappingForward {
+ /// Check if the forward direction length is valid
+ pub fn is_len_valid(&self) -> bool {
+ match self {
+ LocalMappingForward::Latest => true,
+ LocalMappingForward::Ref { sheet_name } => sheet_name.len() <= u8::MAX as usize,
+ LocalMappingForward::Version { version: _ } => true,
+ }
+ }
+
+ /// Get the forward direction type ID
+ pub fn forward_type_id(&self) -> u8 {
+ match self {
+ LocalMappingForward::Latest => 0,
+ LocalMappingForward::Ref { sheet_name: _ } => 1,
+ LocalMappingForward::Version { version: _ } => 2,
+ }
+ }
+
+ /// Unpack into raw information
+ /// (id, len, bytes)
+ pub fn unpack(&self) -> (u8, u8, Vec<u8>) {
+ let b = match self {
+ LocalMappingForward::Latest => vec![],
+ LocalMappingForward::Ref { sheet_name } => sheet_name.as_bytes().to_vec(),
+ LocalMappingForward::Version {
+ version: version_name,
+ } => version_name.to_be_bytes().to_vec(),
+ };
+ (self.forward_type_id(), b.len() as u8, b)
+ }
+
+ /// Reconstruct into a forward direction
+ pub fn pack(id: u8, bytes: &[u8]) -> Option<Self> {
+ if bytes.len() > u8::MAX as usize {
+ return None;
+ }
+ match id {
+ 0 => Some(Self::Latest),
+ 1 => Some(Self::Ref {
+ sheet_name: String::from_utf8(bytes.to_vec()).ok()?,
+ }),
+ 2 => Some(Self::Version {
+ version: u16::from_be_bytes(bytes.try_into().ok()?),
+ }),
+ _ => None,
+ }
+ }
}
// Implement creation and mutual conversion for MappingBuf, LocalMapping and Mapping
@@ -63,15 +117,23 @@ impl LocalMapping {
/// Create a new LocalMapping
pub fn new(
val: Vec<String>,
- id: impl Into<String>,
- ver: impl Into<String>,
+ source: IndexSource,
forward: LocalMappingForward,
- ) -> Self {
- Self {
- val,
- id: id.into(),
- ver: ver.into(),
- forward,
+ ) -> Option<Self> {
+ // Note:
+ // LocalMapping will be stored in SheetData
+ // Strict validity checks are required; the lengths of forward, and even val must not exceed limits
+ // Otherwise, errors will occur that prevent correct writing to SheetData
+ let valid = forward.is_len_valid() && val.join("/").len() <= u8::MAX as usize;
+
+ if valid {
+ Some(Self {
+ val,
+ source,
+ forward,
+ })
+ } else {
+ None
}
}
@@ -80,14 +142,19 @@ impl LocalMapping {
&self.val
}
+ /// Get the IndexSource of LocalMapping
+ pub fn index_source(&self) -> IndexSource {
+ self.source
+ }
+
/// Get the mapped index ID of LocalMapping
- pub fn mapped_id(&self) -> &String {
- &self.id
+ pub fn mapped_id(&self) -> u32 {
+ self.source.id()
}
/// Get the mapped index version of LocalMapping
- pub fn mapped_version(&self) -> &String {
- &self.ver
+ pub fn mapped_version(&self) -> u16 {
+ self.source.version()
}
/// Get the forward direction of LocalMapping
@@ -97,35 +164,24 @@ impl LocalMapping {
/// Clone and generate a MappingBuf from LocalMapping
pub fn to_mapping_buf_cloned(&self, sheet_name: impl Into<String>) -> MappingBuf {
- MappingBuf::new(
- sheet_name.into(),
- self.val.clone(),
- self.id.clone(),
- self.ver.clone(),
- )
+ MappingBuf::new(sheet_name.into(), self.val.clone(), self.source.clone())
}
/// Generate a MappingBuf from LocalMapping
pub fn to_mapping_buf(self, sheet_name: impl Into<String>) -> MappingBuf {
- MappingBuf::new(sheet_name.into(), self.val, self.id, self.ver)
+ MappingBuf::new(sheet_name.into(), self.val, self.source)
}
}
impl MappingBuf {
/// Create a new MappingBuf
- pub fn new(
- sheet_name: impl Into<String>,
- val: Vec<String>,
- id: impl Into<String>,
- ver: impl Into<String>,
- ) -> Self {
+ pub fn new(sheet_name: impl Into<String>, val: Vec<String>, source: IndexSource) -> Self {
let val_joined = val.join("/");
Self {
sheet_name: sheet_name.into(),
val,
val_joined,
- id: id.into(),
- ver: ver.into(),
+ source,
}
}
@@ -144,45 +200,56 @@ impl MappingBuf {
&self.val_joined
}
+ /// Get the IndexSource of MappingBuf
+ pub fn index_source(&self) -> IndexSource {
+ self.source
+ }
+
/// Get the mapped index ID of MappingBuf
- pub fn mapped_id(&self) -> &String {
- &self.id
+ pub fn mapped_id(&self) -> u32 {
+ self.source.id()
}
/// Get the mapped index version of MappingBuf
- pub fn mapped_version(&self) -> &String {
- &self.ver
+ pub fn mapped_version(&self) -> u16 {
+ self.source.version()
}
/// Generate a Mapping from MappingBuf
pub fn as_mapping(&self) -> Mapping<'_> {
- Mapping::new(&self.sheet_name, &self.val_joined, &self.id, &self.ver)
+ Mapping::new(&self.sheet_name, &self.val_joined, self.source)
}
/// Clone and generate a LocalMapping from MappingBuf
- pub fn to_local_mapping_cloned(&self, forward: &LocalMappingForward) -> LocalMapping {
- LocalMapping::new(
- self.val.clone(),
- self.id.clone(),
- self.ver.clone(),
- forward.clone(),
- )
+ ///
+ /// If any of the following conditions exist in MappingBuf or its members,
+ /// the conversion will be invalid and return None
+ /// - The final length of the recorded mapping value exceeds `u8::MAX`
+ ///
+ /// Additionally, if the length of the given forward value exceeds `u8::MAX`, it will also return None
+ pub fn to_local_mapping_cloned(&self, forward: &LocalMappingForward) -> Option<LocalMapping> {
+ LocalMapping::new(self.val.clone(), self.source.clone(), forward.clone())
}
/// Generate a LocalMapping from MappingBuf
- pub fn to_local_mapping(self, forward: LocalMappingForward) -> LocalMapping {
- LocalMapping::new(self.val, self.id, self.ver, forward)
+ ///
+ /// If any of the following conditions exist in MappingBuf or its members,
+ /// the conversion will be invalid and return None
+ /// - The final length of the recorded mapping value exceeds `u8::MAX`
+ ///
+ /// Additionally, if the length of the given forward value exceeds `u8::MAX`, it will also return None
+ pub fn to_local_mapping(self, forward: LocalMappingForward) -> Option<LocalMapping> {
+ LocalMapping::new(self.val, self.source, forward)
}
}
impl<'a> Mapping<'a> {
/// Create a new Mapping
- pub fn new(sheet_name: &'a str, val: &'a str, id: &'a str, ver: &'a str) -> Self {
+ pub fn new(sheet_name: &'a str, val: &'a str, source: IndexSource) -> Self {
Self {
sheet_name,
val,
- id,
- ver,
+ source,
}
}
@@ -193,7 +260,7 @@ impl<'a> Mapping<'a> {
/// Build a Vec of Mapping values from the stored address
pub fn value(&self) -> Vec<String> {
- format_path_str(self.val.to_string())
+ fmt_path_str(self.val.to_string())
.unwrap_or_default()
.split("/")
.map(|s| s.to_string())
@@ -205,53 +272,90 @@ impl<'a> Mapping<'a> {
&self.val
}
+ /// Get the IndexSource of Mapping
+ pub fn index_source(&self) -> IndexSource {
+ self.source
+ }
+
/// Get the mapped index ID of Mapping
- pub fn mapped_id(&self) -> &str {
- &self.id
+ pub fn mapped_id(&self) -> u32 {
+ self.source.id()
}
/// Get the mapped index version of Mapping
- pub fn mapped_version(&self) -> &str {
- &self.ver
+ pub fn mapped_version(&self) -> u16 {
+ self.source.version()
}
/// Generate a MappingBuf from Mapping
pub fn to_mapping_buf(&self) -> MappingBuf {
MappingBuf::new(
self.sheet_name.to_string(),
- format_path_str(self.val)
+ fmt_path_str(self.val)
.unwrap_or_default()
.split('/')
.into_iter()
.map(|s| s.to_string())
.collect(),
- self.id.to_string(),
- self.ver.to_string(),
+ self.source,
)
}
- /// Generate a LocalMapping from MappingBuf
- pub fn to_local_mapping(self, forward: LocalMappingForward) -> LocalMapping {
+ /// Generate a LocalMapping from Mapping
+ ///
+ /// If any of the following conditions exist in Mapping or its members,
+ /// the conversion will be invalid and return None
+ /// - The final length of the recorded mapping value exceeds `u8::MAX`
+ ///
+ /// Additionally, if the length of the given forward value exceeds `u8::MAX`, it will also return None
+ pub fn to_local_mapping(self, forward: LocalMappingForward) -> Option<LocalMapping> {
LocalMapping::new(
- format_path_str(self.val)
+ fmt_path_str(self.val)
.unwrap_or_default()
.split("/")
.into_iter()
.map(|s| s.to_string())
.collect(),
- self.id.to_string(),
- self.ver.to_string(),
+ self.source,
forward,
)
}
}
+impl<'a> From<Mapping<'a>> for Vec<String> {
+ fn from(mapping: Mapping<'a>) -> Vec<String> {
+ mapping.value()
+ }
+}
+
impl<'a> From<Mapping<'a>> for MappingBuf {
fn from(mapping: Mapping<'a>) -> Self {
mapping.to_mapping_buf()
}
}
+impl<'a> TryFrom<Mapping<'a>> for LocalMapping {
+ type Error = ParseMappingError;
+
+ fn try_from(value: Mapping<'a>) -> Result<Self, Self::Error> {
+ match value.to_local_mapping(LocalMappingForward::Latest) {
+ Some(m) => Ok(m),
+ None => Err(ParseMappingError::InvalidMapping),
+ }
+ }
+}
+
+impl TryFrom<MappingBuf> for LocalMapping {
+ type Error = ParseMappingError;
+
+ fn try_from(value: MappingBuf) -> Result<Self, Self::Error> {
+ match value.to_local_mapping(LocalMappingForward::Latest) {
+ Some(m) => Ok(m),
+ None => Err(ParseMappingError::InvalidMapping),
+ }
+ }
+}
+
// Implement the Display trait for Mapping, LocalMapping and MappingBuf for formatted output.
//
// The Display implementation only shows path information, not the complete structure information.
@@ -261,31 +365,73 @@ impl<'a> From<Mapping<'a>> for MappingBuf {
// When presenting, only the snake_case converted sheet_name and the path formed by joining val are shown.
macro_rules! fmt_mapping {
- ($f:expr, $sheet_name:expr, $val:expr) => {
+ ($f:expr, $sheet_name:expr, $val:expr, $source:expr) => {
write!(
$f,
- "{}:/{}",
- snake_case!($sheet_name),
- format_path_str($val).unwrap_or_default()
+ "\"{}:/{}\" => \"{}\"",
+ just_fmt::snake_case!($sheet_name),
+ just_fmt::fmt_path::fmt_path_str($val).unwrap_or_default(),
+ $source
)
};
}
impl<'a> std::fmt::Display for Mapping<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- fmt_mapping!(f, self.sheet_name, self.val)
+ fmt_mapping!(f, self.sheet_name, self.val, self.source.to_string())
}
}
impl std::fmt::Display for MappingBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- fmt_mapping!(f, self.sheet_name.to_string(), &self.val.join("/"))
+ fmt_mapping!(
+ f,
+ self.sheet_name.to_string(),
+ &self.val.join("/"),
+ self.source.to_string()
+ )
}
}
impl std::fmt::Display for LocalMapping {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.val.join("/"))
+ match &self.forward {
+ LocalMappingForward::Latest => {
+ write!(
+ f,
+ "\"{}\" => \"{}\"",
+ self.val.join("/"),
+ self.source.to_string()
+ )
+ }
+ LocalMappingForward::Ref { sheet_name } => {
+ write!(
+ f,
+ "\"{}\" => \"{}\" => \"{}\"",
+ self.val.join("/"),
+ self.source.to_string(),
+ sheet_name
+ )
+ }
+ LocalMappingForward::Version { version } => {
+ if &self.mapped_version() == version {
+ write!(
+ f,
+ "\"{}\" == \"{}\"",
+ self.val.join("/"),
+ self.source.to_string(),
+ )
+ } else {
+ write!(
+ f,
+ "\"{}\" => \"{}\" == \"{}\"",
+ self.val.join("/"),
+ self.source.to_string(),
+ version
+ )
+ }
+ }
+ }
}
}
@@ -312,14 +458,19 @@ impl MappingBuf {
self.val_joined = self.val.join("/");
}
+ /// Replace the current IndexSource
+ pub fn replace_source(&mut self, new_source: IndexSource) {
+ self.source = new_source;
+ }
+
/// Set the mapped index ID of the current MappingBuf
- pub fn set_mapped_id(&mut self, id: impl Into<String>) {
- self.id = id.into();
+ pub fn set_mapped_id(&mut self, id: u32) {
+ self.source.set_id(id);
}
/// Set the mapped index version of the current MappingBuf
- pub fn set_mapped_version(&mut self, version: impl Into<String>) {
- self.ver = version.into();
+ pub fn set_mapped_version(&mut self, version: u16) {
+ self.source.set_version(version);
}
}
@@ -337,14 +488,19 @@ impl LocalMapping {
self.val = val;
}
+ /// Replace the current IndexSource
+ pub fn replace_source(&mut self, new_source: IndexSource) {
+ self.source = new_source;
+ }
+
/// Set the mapped index ID of the current LocalMapping
- pub fn set_mapped_id(&mut self, id: impl Into<String>) {
- self.id = id.into();
+ pub fn set_mapped_id(&mut self, id: u32) {
+ self.source.set_id(id);
}
/// Set the mapped index version of the current LocalMapping
- pub fn set_mapped_version(&mut self, version: impl Into<String>) {
- self.ver = version.into();
+ pub fn set_mapped_version(&mut self, version: u16) {
+ self.source.set_version(version);
}
/// Set the forward direction of the current LocalMapping
@@ -355,7 +511,7 @@ impl LocalMapping {
#[inline(always)]
fn join_helper(nodes: String, mut mapping_buf_val: Vec<String>) -> Vec<String> {
- let formatted = format_path_str_with_config(
+ let formatted = fmt_path_str_custom(
nodes,
&PathFormatConfig {
// Do not process ".." because it is used to go up one level
@@ -383,40 +539,55 @@ fn join_helper(nodes: String, mut mapping_buf_val: Vec<String>) -> Vec<String> {
return mapping_buf_val;
}
-// Implement mutual comparison for LocalMapping, MappingBuf, and Mapping
+// Implement mutual comparison for MappingBuf and Mapping
+//
+// Note:
+// When either side's ID or Version is None, it indicates an invalid Source
+// The comparison result is false
-impl<'a> PartialEq<Mapping<'a>> for LocalMapping {
- fn eq(&self, other: &Mapping<'a>) -> bool {
- self.val.join("/") == other.val && self.id == other.id && self.ver == other.ver
+impl<'a> PartialEq<MappingBuf> for Mapping<'a> {
+ fn eq(&self, other: &MappingBuf) -> bool {
+ self.val == other.val_joined
+ && self.source.id() == other.source.id()
+ && self.source.version() == other.source.version()
}
}
-impl<'a> PartialEq<LocalMapping> for Mapping<'a> {
- fn eq(&self, other: &LocalMapping) -> bool {
- other == self
+// Implement comparison between LocalMappings
+//
+// Note:
+// LocalMappings are considered equal as long as their val (Node) values are the same
+
+impl PartialEq for LocalMapping {
+ fn eq(&self, other: &Self) -> bool {
+ self.val == other.val
}
}
-impl PartialEq<MappingBuf> for LocalMapping {
- fn eq(&self, other: &MappingBuf) -> bool {
- self.val == other.val && self.id == other.id && self.ver == other.ver
+impl PartialEq<Vec<String>> for LocalMapping {
+ fn eq(&self, other: &Vec<String>) -> bool {
+ &self.val == other
}
}
-impl PartialEq<LocalMapping> for MappingBuf {
- fn eq(&self, other: &LocalMapping) -> bool {
- other == self
+impl Eq for LocalMapping {}
+
+impl std::hash::Hash for LocalMapping {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.val.hash(state);
}
}
-impl<'a> PartialEq<MappingBuf> for Mapping<'a> {
- fn eq(&self, other: &MappingBuf) -> bool {
- self.val == other.val_joined && self.id == other.id && self.ver == other.ver
+// Implement borrowing for LocalMapping and MappingBuf
+
+impl std::borrow::Borrow<Vec<String>> for LocalMapping {
+ fn borrow(&self) -> &Vec<String> {
+ &self.val
}
}
-impl<'a> PartialEq<Mapping<'a>> for MappingBuf {
- fn eq(&self, other: &Mapping<'a>) -> bool {
- other == self
+impl std::borrow::Borrow<Vec<String>> for MappingBuf {
+ fn borrow(&self) -> &Vec<String> {
+ &self.val
}
}