Message Name Validation
FlatMessage provides a robust name validation system that allows you to control whether structure names are stored during serialization and validated during deserialization. This feature helps ensure type safety and prevents accidental deserialization of incompatible data structures.
Overview
The name validation system uses two key attributes that work together:
store_name
: Controls whether the structure name hash is stored in the serialized datavalidate_name
: Controls whether the structure name is validated during deserialization
Attributes
store_name
Attribute
#![allow(unused)] fn main() { #[derive(FlatMessage)] #[flat_message_options(store_name = true)] // Default: true struct MyStruct { value: u32, } }
When store_name = true
:
- A 32-bit hash of the structure name is stored in the serialized data
- Adds 4 bytes to the serialized size
- Enables name validation during deserialization
When store_name = false
:
- No name information is stored in the serialized data
- Saves 4 bytes of storage space
- Name validation is impossible during deserialization
validate_name
Attribute
#![allow(unused)] fn main() { #[derive(FlatMessage)] #[flat_message_options(validate_name = true)] // Default: false struct MyStruct { value: u32, } }
When validate_name = true
:
- During deserialization, the stored name hash is compared with the target structure's name
- Throws
Error::UnmatchedName
if names don't match - Throws
Error::NameNotStored
if no name was stored during serialization
When validate_name = false
(default):
- No name validation is performed during deserialization
- Allows deserialization between different structure types (if fields are compatible)
Attribute Combinations
1. Default Behavior: store_name = true
, validate_name = false
#![allow(unused)] fn main() { #[derive(Debug, PartialEq, Eq, FlatMessage)] struct StructA { value: u64, } #[derive(Debug, PartialEq, Eq, FlatMessage)] struct StructB { value: u64, } }
Behavior:
- Name is stored during serialization (4 extra bytes)
- No validation during deserialization
- Cross-structure deserialization is allowed if fields are compatible
#![allow(unused)] fn main() { let a = StructA { value: 42 }; let mut storage = Storage::default(); a.serialize_to(&mut storage, Config::default()).unwrap(); // This works - same structure let restored_a = StructA::deserialize_from(&storage).unwrap(); // This also works - different structure but compatible fields let restored_b = StructB::deserialize_from(&storage).unwrap(); }
2. Strict Validation: store_name = true
, validate_name = true
#![allow(unused)] fn main() { #[derive(Debug, PartialEq, Eq, FlatMessage)] #[flat_message_options(store_name = true, validate_name = true)] struct StrictStruct { value: u64, } #[derive(Debug, PartialEq, Eq, FlatMessage)] #[flat_message_options(validate_name = true)] struct OtherStruct { value: u64, } }
Behavior:
- Name is stored during serialization
- Name is validated during deserialization
- Cross-structure deserialization fails with
Error::UnmatchedName
#![allow(unused)] fn main() { let strict = StrictStruct { value: 42 }; let mut storage = Storage::default(); strict.serialize_to(&mut storage, Config::default()).unwrap(); // This works - correct structure let restored = StrictStruct::deserialize_from(&storage).unwrap(); // This fails - different structure name match OtherStruct::deserialize_from(&storage) { Err(Error::UnmatchedName) => { println!("Name validation prevented cross-type deserialization"); } _ => panic!("Expected name validation error"), } }
3. Space Optimized: store_name = false
, validate_name = false
#![allow(unused)] fn main() { #[derive(Debug, PartialEq, Eq, FlatMessage)] #[flat_message_options(store_name = false, validate_name = false)] struct CompactStruct { value: u64, } }
Behavior:
- No name is stored (saves 4 bytes)
- No validation is performed
- Maximum interoperability between compatible structures
#![allow(unused)] fn main() { let compact = CompactStruct { value: 42 }; let mut storage = Storage::default(); compact.serialize_to(&mut storage, Config::default()).unwrap(); // Works with any compatible structure let restored = AnyCompatibleStruct::deserialize_from(&storage).unwrap(); }
4. Invalid Combination: store_name = false
, validate_name = true
#![allow(unused)] fn main() { #[derive(Debug, PartialEq, Eq, FlatMessage)] #[flat_message_options(store_name = false, validate_name = true)] struct InvalidConfig { value: u64, } #[derive(Debug, PartialEq, Eq, FlatMessage)] #[flat_message_options(store_name = false)] struct DataSource { value: u64, } }
Behavior:
- No name is stored during serialization
- Validation is attempted during deserialization
- Always fails with
Error::NameNotStored
#![allow(unused)] fn main() { let source = DataSource { value: 42 }; let mut storage = Storage::default(); source.serialize_to(&mut storage, Config::default()).unwrap(); // This always fails because no name was stored match InvalidConfig::deserialize_from(&storage) { Err(Error::NameNotStored) => { println!("Cannot validate name when none was stored"); } _ => panic!("Expected NameNotStored error"), } }
Error Types
Error::UnmatchedName
Occurs when:
validate_name = true
on the target structure- A name hash was stored in the serialized data
- The stored name hash doesn't match the target structure's name hash
Error::NameNotStored
Occurs when:
validate_name = true
on the target structure- No name hash was stored in the serialized data (source had
store_name = false
)
Use Cases
Type Safety in APIs
Use strict validation when deserializing data from external sources:
#![allow(unused)] fn main() { #[derive(FlatMessage)] #[flat_message_options(store_name = true, validate_name = true)] struct ApiRequest { user_id: u64, action: String, } fn handle_request(data: &[u8]) -> Result<(), Error> { // This will fail if the data wasn't serialized as ApiRequest let request = ApiRequest::deserialize_from(data)?; // Process request safely... Ok(()) } }
Best Practices
-
Use strict validation (
store_name = true, validate_name = true
) for:- Network protocol messages
- API endpoints
- Critical data structures where type safety is paramount
-
Use flexible validation (
store_name = true, validate_name = false
) for:- Data persistence with potential schema evolution
- Internal application data structures
- When you need some metadata but want flexibility
-
Disable name storage (
store_name = false
) for:- High-frequency data (sensor readings, telemetry)
- Memory-constrained environments
- When every byte counts and type safety is managed elsewhere
-
Avoid the invalid combination (
store_name = false, validate_name = true
):- This configuration will always fail during deserialization
- The compiler doesn't prevent this, so be careful with your configuration
Runtime Introspection
You can inspect stored names using StructureInformation
:
#![allow(unused)] fn main() { use flat_message::*; #[derive(FlatMessage)] #[flat_message_options(store_name = true)] struct MyStruct { value: u32, } fn inspect_name(storage: &Storage) -> Result<(), Error> { let info = StructureInformation::try_from(storage)?; if let Some(name) = info.name() { match name { name!("MyStruct") => println!("Found MyStruct data"), name!("OtherStruct") => println!("Found OtherStruct data"), _ => println!("Found unknown structure: {:?}", name), } } else { println!("No name information stored"); } Ok(()) } }
The name validation system provides a flexible balance between type safety, storage efficiency, and interoperability, allowing you to choose the right trade-offs for your specific use case.