use crate::errors::{OpCodeFromStrError, UnknownOpCodeName};
use core::{
cmp::Ordering,
fmt::{self, Display, Formatter, Write},
str::FromStr,
};
const UNKNOWN_OPCODE: &str = "__UNKNOWN_OPCODE__";
const RFC3597_PFX: &str = "OPCODE";
static NAMES: [&str; 7] = ["QUERY", "IQUERY", "STATUS", "", "", "", ""];
static KNOWN: [u8; 7] = [1, 1, 1, 0, 0, 0, 0];
#[derive(Copy, Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct OpCode(u8);
impl OpCode {
pub const QUERY: OpCode = OpCode::new(0);
pub const IQUERY: OpCode = OpCode::new(1);
pub const STATUS: OpCode = OpCode::new(2);
#[cfg(test)]
#[allow(dead_code)]
pub(crate) const VALUES: [OpCode; 3] = [Self::QUERY, Self::IQUERY, Self::STATUS];
#[inline]
const fn new(v: u8) -> Self {
Self(v)
}
#[inline]
pub fn name(self) -> &'static str {
let val = self.value() as usize;
let name_ = if val < NAMES.len() { NAMES[val] } else { "" };
match name_ {
"" => UNKNOWN_OPCODE,
_ => name_,
}
}
#[inline]
pub const fn value(self) -> u8 {
self.0
}
pub fn from_name(name: &str) -> core::result::Result<Self, UnknownOpCodeName> {
match name.len() {
5 => match name {
"QUERY" => Ok(OpCode::QUERY),
_ => Err(UnknownOpCodeName),
},
6 => match name {
"IQUERY" => Ok(OpCode::IQUERY),
"STATUS" => Ok(OpCode::STATUS),
_ => Err(UnknownOpCodeName),
},
_ => Err(UnknownOpCodeName),
}
}
#[inline]
pub fn is_defined(self) -> bool {
let val = self.value() as usize;
if val < KNOWN.len() {
KNOWN[val] != 0
} else {
false
}
}
}
impl From<u8> for OpCode {
#[inline]
fn from(value: u8) -> Self {
OpCode(value)
}
}
impl Display for OpCode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let name = self.name();
match name {
UNKNOWN_OPCODE => {
let mut buf = arrayvec::ArrayString::<32>::new();
write!(&mut buf, "{}{}", RFC3597_PFX, self.0)?;
f.pad(buf.as_str())
}
_ => f.pad(name),
}
}
}
impl PartialEq<u8> for OpCode {
#[inline]
fn eq(&self, other: &u8) -> bool {
self.0 == *other
}
}
impl PartialOrd<u8> for OpCode {
#[inline]
fn partial_cmp(&self, other: &u8) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}
impl FromStr for OpCode {
type Err = OpCodeFromStrError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(oc) = Self::from_name(s) {
return Ok(oc);
}
if let Some(sfx) = s.strip_prefix(RFC3597_PFX) {
match sfx.parse::<u8>() {
Ok(v) => Ok(OpCode::from(v)),
_ => Err(OpCodeFromStrError),
}
} else {
Err(OpCodeFromStrError)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_name() {
assert_eq!(OpCode::QUERY.name(), "QUERY");
assert_eq!(OpCode::IQUERY.name(), "IQUERY");
assert_eq!(OpCode::STATUS.name(), "STATUS");
for (i, name) in NAMES.iter().enumerate() {
if !name.is_empty() {
assert_eq!(OpCode::from(i as u8).name(), *name);
} else {
assert_eq!(OpCode::from(i as u8).name(), "__UNKNOWN_OPCODE__");
}
}
}
#[test]
fn test_from_name() {
assert_eq!(OpCode::from_name("QUERY").unwrap(), OpCode::QUERY);
assert_eq!(OpCode::from_name("IQUERY").unwrap(), OpCode::IQUERY);
assert_eq!(OpCode::from_name("STATUS").unwrap(), OpCode::STATUS);
for (i, name) in NAMES.iter().enumerate() {
let opcode = OpCode::from(i as u8);
match opcode {
OpCode::QUERY => assert_eq!(OpCode::from_name(name).unwrap(), OpCode::QUERY),
OpCode::IQUERY => assert_eq!(OpCode::from_name(name).unwrap(), OpCode::IQUERY),
OpCode::STATUS => assert_eq!(OpCode::from_name(name).unwrap(), OpCode::STATUS),
_ => assert!(OpCode::from_name(name).is_err()),
}
assert!(OpCode::from_name(&name.to_lowercase()).is_err());
}
}
#[test]
fn test_from_str() {
assert_eq!(OpCode::from_str("QUERY").unwrap(), OpCode::QUERY);
assert_eq!(OpCode::from_str("IQUERY").unwrap(), OpCode::IQUERY);
assert_eq!(OpCode::from_str("STATUS").unwrap(), OpCode::STATUS);
for (i, name) in NAMES.iter().enumerate() {
if !name.is_empty() {
assert_eq!(OpCode::from_str(name).unwrap(), OpCode::from(i as u8));
assert!(OpCode::from_str(&name.to_lowercase()).is_err());
}
}
for i in 0..=u8::MAX {
let s = format!("OPCODE{i}");
assert_eq!(OpCode::from_str(&s).unwrap(), OpCode::from(i));
assert!(OpCode::from_str(&s.to_lowercase()).is_err());
}
}
#[test]
fn test_is_defined() {
assert!(OpCode::QUERY.is_defined());
assert!(OpCode::IQUERY.is_defined());
assert!(OpCode::STATUS.is_defined());
for (i, name) in NAMES.iter().enumerate() {
assert_eq!(OpCode::from(i as u8).is_defined(), !name.is_empty());
}
for i in 0..=255 {
assert_eq!(
OpCode::from(i).is_defined(),
OpCode::VALUES.iter().any(|v| *v == i)
);
}
}
}