Hone logo
Hone
Problems

Custom Data Serialization in Rust

You've encountered a scenario where the default serialization mechanisms in Rust, like those provided by serde, don't quite fit your needs. Perhaps you have a very specific binary format to adhere to, or you need to optimize for space or speed with a custom encoding. This challenge will test your ability to design and implement a custom serialization and deserialization process for a given data structure in Rust.

Problem Description

Your task is to create a custom serialization and deserialization system for a User struct in Rust. You will be defining your own binary format for representing this struct.

What needs to be achieved: Implement two functions:

  1. serialize_user(user: &User) -> Vec<u8>: This function should take a User struct by reference and return a Vec<u8> representing its serialized form according to your custom format.
  2. deserialize_user(data: &[u8]) -> Result<User, DeserializationError>: This function should take a byte slice and attempt to deserialize it back into a User struct. It should return a Result indicating success with the User struct or failure with a custom DeserializationError enum.

Key requirements:

  • Custom Binary Format: Design and implement your own binary encoding for the User struct. This format is entirely up to you, but it must be consistent between serialization and deserialization.
  • Error Handling: Implement robust error handling for deserialization. Your deserialize_user function must return a Result type that can represent various deserialization failures (e.g., insufficient data, invalid data).
  • Struct Definition: You will be working with the following User struct:
#[derive(Debug, PartialEq)]
pub struct User {
    pub id: u32,
    pub username: String,
    pub active: bool,
    pub preferences: Option<u16>,
}

Expected behavior:

  • serialize_user should produce a byte vector that accurately represents the User struct.
  • deserialize_user should correctly reconstruct the User struct from a valid serialized byte vector.
  • deserialize_user should return an appropriate DeserializationError for invalid or incomplete input.

Important edge cases to consider:

  • Empty input for deserialization.
  • Input data that is shorter than expected for a complete User struct.
  • Potentially malformed data that doesn't conform to your chosen encoding.
  • The Option<u16> field can be either present or absent.

Examples

Example 1:

Input User: User { id: 123, username: "alice".to_string(), active: true, preferences: Some(42) }

Expected Output (Serialization): A Vec<u8> representing the above User.
(The exact bytes depend on your chosen format, but a possible format might look like:
[bytes for 123] [length of "alice"] [bytes for "alice"] [1 for true] [1 for Some] [bytes for 42]
)

Expected Output (Deserialization from above bytes):
Ok(User { id: 123, username: "alice".to_string(), active: true, preferences: Some(42) })

Explanation: This demonstrates a typical successful serialization and deserialization of a User with all fields populated.

Example 2:

Input User: User { id: 456, username: "bob".to_string(), active: false, preferences: None }

Expected Output (Serialization): A Vec<u8> representing the above User.
(A possible format: [bytes for 456] [length of "bob"] [bytes for "bob"] [0 for false] [0 for None])

Expected Output (Deserialization from above bytes):
Ok(User { id: 456, username: "bob".to_string(), active: false, preferences: None })

Explanation: This shows serialization and deserialization of a User where preferences is None.

Example 3:

Input Data: &[0x01, 0x02] // Incomplete data for deserialization

Expected Output:
Err(DeserializationError::UnexpectedEndOfData)

Explanation: This represents an input byte slice that is too short to contain even a minimal User struct, triggering an UnexpectedEndOfData error.

Constraints

  • The User struct and its fields are defined as provided.
  • You must implement your own serialization and deserialization logic; do not use external serialization crates like serde directly for the core serialization/deserialization of the User struct.
  • Performance is a consideration, but correctness and clarity of your custom format are paramount. Aim for a reasonably efficient binary format.
  • The maximum length of a username is not specified, but assume it fits within memory.
  • The id will fit within a u32, and preferences within a u16.

Notes

  • Choosing your format: Consider how you will represent each field:
    • u32: Fixed-size (4 bytes). Consider endianness (e.g., little-endian or big-endian).
    • String: You'll need to store its length and then its byte representation.
    • bool: Typically represented by a single byte (0 for false, 1 for true).
    • Option<u16>: You'll need a way to indicate whether the value is present or absent, and if present, then the bytes for the u16.
  • Error Types: Define a clear DeserializationError enum that covers potential issues like UnexpectedEndOfData, InvalidBooleanValue, InvalidOptionalIndicator, etc.
  • Helper functions: Consider creating helper functions for reading specific types (like u32, u16, bool, String) from the byte slice to keep your deserialize_user function clean.
  • Testing: Thoroughly test your implementation with various User structs, including edge cases like empty strings, minimum/maximum values for numeric types, and different combinations of Some and None for preferences.
Loading editor...
rust