use crate::{error::ProgramError, result::ProgramResult};
#[repr(C)]
#[derive(Copy, Clone)]
pub struct OptionByte<T: Copy> {
tag: u8,
value: T,
}
impl<T: Copy> OptionByte<T> {
#[inline(always)]
pub const fn none(default_value: T) -> Self {
Self { tag: 0, value: default_value }
}
#[inline(always)]
pub const fn some(value: T) -> Self {
Self { tag: 1, value }
}
#[inline(always)]
pub const fn raw_tag(&self) -> u8 {
self.tag
}
#[inline]
pub fn get(&self) -> Result<Option<&T>, ProgramError> {
match self.tag {
0 => Ok(None),
1 => Ok(Some(&self.value)),
_ => Err(ProgramError::InvalidInstructionData),
}
}
#[inline]
pub fn validate_tag(&self) -> ProgramResult {
match self.tag {
0 | 1 => Ok(()),
_ => Err(ProgramError::InvalidInstructionData),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[repr(C, align(8))]
struct AlignedNine([u8; 9]);
#[test]
fn none_reads_as_none() {
let o: OptionByte<u64> = OptionByte::none(0);
assert!(o.get().unwrap().is_none());
}
#[test]
fn some_reads_back() {
let o = OptionByte::some(42u64);
assert_eq!(*o.get().unwrap().unwrap(), 42);
}
#[test]
fn malformed_tag_rejects() {
let mut buf = AlignedNine([0u8; 9]);
buf.0[0] = 0xFF;
let o: &OptionByte<u64> = unsafe { &*(buf.0.as_ptr() as *const OptionByte<u64>) };
assert_eq!(o.get().unwrap_err(), ProgramError::InvalidInstructionData);
assert_eq!(o.validate_tag().unwrap_err(), ProgramError::InvalidInstructionData);
}
#[test]
fn zero_tag_ignores_value_payload() {
let mut buf = AlignedNine([0u8; 9]);
buf.0[1..9].copy_from_slice(&0x1234_5678_9ABC_DEF0u64.to_le_bytes());
let o: &OptionByte<u64> = unsafe { &*(buf.0.as_ptr() as *const OptionByte<u64>) };
assert!(o.get().unwrap().is_none());
}
}