wow_cdbc/
schema.rs

1//! Schema definitions for DBC files
2
3/// Represents the type of a field in a DBC record
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum FieldType {
6    /// 32-bit signed integer
7    Int32,
8    /// 32-bit unsigned integer
9    UInt32,
10    /// 32-bit floating point number
11    Float32,
12    /// String reference (offset into the string block)
13    String,
14    /// Boolean value (represented as a 32-bit integer)
15    Bool,
16    /// 8-bit unsigned integer
17    UInt8,
18    /// 8-bit signed integer
19    Int8,
20    /// 16-bit unsigned integer
21    UInt16,
22    /// 16-bit signed integer
23    Int16,
24}
25
26impl FieldType {
27    /// Get the size of the field type in bytes
28    pub fn size(&self) -> usize {
29        match self {
30            FieldType::Int32 => 4,
31            FieldType::UInt32 => 4,
32            FieldType::Float32 => 4,
33            FieldType::String => 4, // String references are 32-bit offsets
34            FieldType::Bool => 4,   // Booleans are represented as 32-bit integers
35            FieldType::UInt8 => 1,
36            FieldType::Int8 => 1,
37            FieldType::UInt16 => 2,
38            FieldType::Int16 => 2,
39        }
40    }
41}
42
43/// Represents a field in a DBC schema
44#[derive(Debug, Clone)]
45pub struct SchemaField {
46    /// Name of the field
47    pub name: String,
48    /// Type of the field
49    pub field_type: FieldType,
50    /// Whether the field is an array
51    pub is_array: bool,
52    /// Size of the array, if the field is an array
53    pub array_size: Option<usize>,
54}
55
56impl SchemaField {
57    /// Create a new schema field
58    pub fn new(name: impl Into<String>, field_type: FieldType) -> Self {
59        Self {
60            name: name.into(),
61            field_type,
62            is_array: false,
63            array_size: None,
64        }
65    }
66
67    /// Create a new array schema field
68    pub fn new_array(name: impl Into<String>, field_type: FieldType, array_size: usize) -> Self {
69        Self {
70            name: name.into(),
71            field_type,
72            is_array: true,
73            array_size: Some(array_size),
74        }
75    }
76
77    /// Get the total size of the field in bytes
78    pub fn size(&self) -> usize {
79        if self.is_array {
80            self.field_type.size() * self.array_size.unwrap_or(0)
81        } else {
82            self.field_type.size()
83        }
84    }
85}
86
87/// Represents a schema for a DBC file
88#[derive(Debug, Clone)]
89pub struct Schema {
90    /// Name of the schema
91    pub name: String,
92    /// Fields in the schema
93    pub fields: Vec<SchemaField>,
94    /// Index of the key field, if any
95    pub key_field_index: Option<usize>,
96    /// Whether the schema is validated
97    pub is_validated: bool,
98}
99
100impl Schema {
101    /// Create a new schema
102    pub fn new(name: impl Into<String>) -> Self {
103        Self {
104            name: name.into(),
105            fields: Vec::new(),
106            key_field_index: None,
107            is_validated: false,
108        }
109    }
110
111    /// Add a field to the schema
112    pub fn add_field(&mut self, field: SchemaField) -> &mut Self {
113        self.fields.push(field);
114        self.is_validated = false;
115        self
116    }
117
118    /// Set the key field by index
119    pub fn set_key_field_index(&mut self, index: usize) -> &mut Self {
120        self.key_field_index = Some(index);
121        self.is_validated = false;
122        self
123    }
124
125    /// Set the key field by name
126    ///
127    /// # Panics
128    ///
129    /// Panics if the field with the given name is not found in the schema.
130    /// This is intentional as it indicates a programming error in schema definition.
131    pub fn set_key_field(&mut self, name: &str) -> &mut Self {
132        let index = self
133            .fields
134            .iter()
135            .position(|f| f.name == name)
136            .unwrap_or_else(|| panic!("Field not found: {name}"));
137        self.set_key_field_index(index)
138    }
139
140    /// Try to set the key field by name, returning an error if the field is not found
141    pub fn try_set_key_field(&mut self, name: &str) -> Result<&mut Self, String> {
142        let index = self
143            .fields
144            .iter()
145            .position(|f| f.name == name)
146            .ok_or_else(|| format!("Field not found: {name}"))?;
147        Ok(self.set_key_field_index(index))
148    }
149
150    /// Calculate the total size of a record in bytes
151    pub fn record_size(&self) -> usize {
152        self.fields.iter().map(|f| f.size()).sum()
153    }
154
155    /// Validate the schema against a DBC header
156    pub fn validate(&mut self, field_count: u32, record_size: u32) -> Result<(), String> {
157        let schema_field_count = if self.fields.iter().any(|f| f.is_array) {
158            // For arrays, we need to count each element as a separate field
159            self.fields
160                .iter()
161                .map(|f| {
162                    if f.is_array {
163                        f.array_size.unwrap_or(0)
164                    } else {
165                        1
166                    }
167                })
168                .sum::<usize>() as u32
169        } else {
170            self.fields.len() as u32
171        };
172
173        if schema_field_count != field_count {
174            return Err(format!(
175                "Field count mismatch: schema has {schema_field_count} fields, but DBC has {field_count} fields"
176            ));
177        }
178
179        let schema_record_size = self.record_size() as u32;
180        if schema_record_size != record_size {
181            return Err(format!(
182                "Record size mismatch: schema defines {schema_record_size} bytes, but DBC has {record_size} bytes per record"
183            ));
184        }
185
186        if let Some(index) = self.key_field_index {
187            if index >= self.fields.len() {
188                return Err(format!(
189                    "Key field index out of bounds: {} (max: {})",
190                    index,
191                    self.fields.len() - 1
192                ));
193            }
194
195            let key_field = &self.fields[index];
196            match key_field.field_type {
197                FieldType::UInt32 | FieldType::Int32 => {}
198                _ => {
199                    return Err(format!(
200                        "Key field must be a 32-bit integer, but is {:?}",
201                        key_field.field_type
202                    ));
203                }
204            }
205
206            if key_field.is_array {
207                return Err("Key field cannot be an array".to_string());
208            }
209        }
210
211        self.is_validated = true;
212        Ok(())
213    }
214}