use crate::error::{CaError, CaResult};
pub const DBR_STRING: u16 = 0;
pub const DBR_SHORT: u16 = 1;
pub const DBR_FLOAT: u16 = 2;
pub const DBR_ENUM: u16 = 3;
pub const DBR_CHAR: u16 = 4;
pub const DBR_LONG: u16 = 5;
pub const DBR_DOUBLE: u16 = 6;
pub const DBR_INT: u16 = DBR_SHORT;
pub const DBR_STS_STRING: u16 = 7;
pub const DBR_STS_SHORT: u16 = 8;
pub const DBR_STS_FLOAT: u16 = 9;
pub const DBR_STS_ENUM: u16 = 10;
pub const DBR_STS_CHAR: u16 = 11;
pub const DBR_STS_LONG: u16 = 12;
pub const DBR_STS_DOUBLE: u16 = 13;
pub const DBR_STS_INT: u16 = DBR_STS_SHORT;
pub const DBR_TIME_STRING: u16 = 14;
pub const DBR_TIME_SHORT: u16 = 15;
pub const DBR_TIME_FLOAT: u16 = 16;
pub const DBR_TIME_ENUM: u16 = 17;
pub const DBR_TIME_CHAR: u16 = 18;
pub const DBR_TIME_LONG: u16 = 19;
pub const DBR_TIME_DOUBLE: u16 = 20;
pub const DBR_TIME_INT: u16 = DBR_TIME_SHORT;
pub const DBR_GR_STRING: u16 = 21;
pub const DBR_GR_SHORT: u16 = 22;
pub const DBR_GR_FLOAT: u16 = 23;
pub const DBR_GR_ENUM: u16 = 24;
pub const DBR_GR_CHAR: u16 = 25;
pub const DBR_GR_LONG: u16 = 26;
pub const DBR_GR_DOUBLE: u16 = 27;
pub const DBR_GR_INT: u16 = DBR_GR_SHORT;
pub const DBR_CTRL_STRING: u16 = 28;
pub const DBR_CTRL_SHORT: u16 = 29;
pub const DBR_CTRL_FLOAT: u16 = 30;
pub const DBR_CTRL_ENUM: u16 = 31;
pub const DBR_CTRL_CHAR: u16 = 32;
pub const DBR_CTRL_LONG: u16 = 33;
pub const DBR_CTRL_DOUBLE: u16 = 34;
pub const DBR_CTRL_INT: u16 = DBR_CTRL_SHORT;
pub const DBR_PUT_ACKT: u16 = 35;
pub const DBR_PUT_ACKS: u16 = 36;
pub const DBR_STSACK_STRING: u16 = 37;
pub const DBR_CLASS_NAME: u16 = 38;
pub const LAST_BUFFER_TYPE: u16 = DBR_CLASS_NAME;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum DbFieldType {
String = 0,
Short = 1, Float = 2,
Enum = 3,
Char = 4, Long = 5, Double = 6,
Int64 = 7,
UInt64 = 8,
UShort = 9,
ULong = 10,
UChar = 11,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(i16)]
pub enum DbfCode {
String = 0,
Char = 1,
UChar = 2,
Short = 3,
UShort = 4,
Long = 5,
ULong = 6,
Int64 = 7,
UInt64 = 8,
Float = 9,
Double = 10,
Enum = 11,
Menu = 12,
Device = 13,
Inlink = 14,
Outlink = 15,
Fwdlink = 16,
NoAccess = 17,
}
impl DbfCode {
pub const fn name(self) -> &'static str {
match self {
DbfCode::String => "STRING",
DbfCode::Char => "CHAR",
DbfCode::UChar => "UCHAR",
DbfCode::Short => "SHORT",
DbfCode::UShort => "USHORT",
DbfCode::Long => "LONG",
DbfCode::ULong => "ULONG",
DbfCode::Int64 => "INT64",
DbfCode::UInt64 => "UINT64",
DbfCode::Float => "FLOAT",
DbfCode::Double => "DOUBLE",
DbfCode::Enum => "ENUM",
DbfCode::Menu => "MENU",
DbfCode::Device => "DEVICE",
DbfCode::Inlink => "INLINK",
DbfCode::Outlink => "OUTLINK",
DbfCode::Fwdlink => "FWDLINK",
DbfCode::NoAccess => "NOACCESS",
}
}
pub const fn dbr_code(self) -> DbfCode {
match self {
DbfCode::Menu | DbfCode::Device => DbfCode::Enum,
DbfCode::Inlink | DbfCode::Outlink | DbfCode::Fwdlink => DbfCode::String,
other => other,
}
}
}
impl DbFieldType {
pub const fn dbf_code(self) -> DbfCode {
match self {
DbFieldType::String => DbfCode::String,
DbFieldType::Char => DbfCode::Char,
DbFieldType::UChar => DbfCode::UChar,
DbFieldType::Short => DbfCode::Short,
DbFieldType::UShort => DbfCode::UShort,
DbFieldType::Long => DbfCode::Long,
DbFieldType::ULong => DbfCode::ULong,
DbFieldType::Int64 => DbfCode::Int64,
DbFieldType::UInt64 => DbfCode::UInt64,
DbFieldType::Float => DbfCode::Float,
DbFieldType::Double => DbfCode::Double,
DbFieldType::Enum => DbfCode::Enum,
}
}
pub fn wire_carrier(self) -> Self {
match self {
Self::Char => Self::UChar,
other => other,
}
}
pub fn from_u16(v: u16) -> CaResult<Self> {
match v {
0 => Ok(Self::String),
1 => Ok(Self::Short),
2 => Ok(Self::Float),
3 => Ok(Self::Enum),
4 => Ok(Self::Char),
5 => Ok(Self::Long),
6 => Ok(Self::Double),
_ => Err(CaError::UnsupportedType(v)),
}
}
pub fn element_size(&self) -> usize {
match self {
Self::String => 40, Self::Short | Self::Enum | Self::UShort => 2,
Self::Float | Self::Long | Self::ULong => 4,
Self::Char | Self::UChar => 1,
Self::Double | Self::Int64 | Self::UInt64 => 8,
}
}
pub fn ca_wire_type(&self) -> u16 {
match self {
Self::Int64 | Self::UInt64 | Self::ULong => Self::Double as u16,
Self::UShort => Self::Long as u16,
Self::UChar => Self::Char as u16,
other => *other as u16,
}
}
pub fn sts_dbr_type(&self) -> u16 {
self.ca_wire_type() + 7
}
pub fn time_dbr_type(&self) -> u16 {
self.ca_wire_type() + 14
}
pub fn gr_dbr_type(&self) -> u16 {
self.ca_wire_type() + 21
}
pub fn ctrl_dbr_type(&self) -> u16 {
self.ca_wire_type() + 28
}
pub fn buffer_size(&self, count: usize) -> usize {
self.element_size() * count
}
pub fn to_dbr_type(&self) -> DbFieldType {
*self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum DbfLinkClass {
InLink = DBF_INLINK,
OutLink = DBF_OUTLINK,
FwdLink = DBF_FWDLINK,
}
pub const DBF_INLINK: u8 = 14;
pub const DBF_OUTLINK: u8 = 15;
pub const DBF_FWDLINK: u8 = 16;
impl DbfLinkClass {
pub fn dbf_type(self) -> u8 {
self as u8
}
}
pub fn is_link_dbf_type(dbf_type: u8) -> bool {
(DBF_INLINK..=DBF_FWDLINK).contains(&dbf_type)
}
pub fn dbf_link_class(record_type: &str, field: &str) -> Option<DbfLinkClass> {
let desc = crate::server::record::declared_field(record_type, field.trim())?;
match desc.declared_dbf {
DbfCode::Inlink => Some(DbfLinkClass::InLink),
DbfCode::Outlink => Some(DbfLinkClass::OutLink),
DbfCode::Fwdlink => Some(DbfLinkClass::FwdLink),
_ => None,
}
}
pub fn dbr_buffer_size(dbr_type: u16, native_type: DbFieldType, count: usize) -> usize {
if dbr_type == DBR_CLASS_NAME {
return 40;
}
let value_size = native_type.element_size() * count;
crate::types::codec::dbr_meta_size(dbr_type, native_type) + value_size
}
fn dbr_native_index(dbr_type: u16) -> Option<u16> {
match dbr_type {
0..=6 => Some(dbr_type),
7..=13 => Some(dbr_type - 7),
14..=20 => Some(dbr_type - 14),
21..=27 => Some(dbr_type - 21),
28..=34 => Some(dbr_type - 28),
35 | 36 => Some(1), 37 => Some(0), 38 => Some(0),
_ => None,
}
}
pub fn native_type_for_dbr(dbr_type: u16) -> CaResult<DbFieldType> {
match dbr_native_index(dbr_type) {
Some(idx) => DbFieldType::from_u16(idx),
None => Err(CaError::UnsupportedType(dbr_type)),
}
}
const DBR_TEXT: [&str; (LAST_BUFFER_TYPE + 1) as usize] = [
"DBR_STRING",
"DBR_SHORT",
"DBR_FLOAT",
"DBR_ENUM",
"DBR_CHAR",
"DBR_LONG",
"DBR_DOUBLE",
"DBR_STS_STRING",
"DBR_STS_SHORT",
"DBR_STS_FLOAT",
"DBR_STS_ENUM",
"DBR_STS_CHAR",
"DBR_STS_LONG",
"DBR_STS_DOUBLE",
"DBR_TIME_STRING",
"DBR_TIME_SHORT",
"DBR_TIME_FLOAT",
"DBR_TIME_ENUM",
"DBR_TIME_CHAR",
"DBR_TIME_LONG",
"DBR_TIME_DOUBLE",
"DBR_GR_STRING",
"DBR_GR_SHORT",
"DBR_GR_FLOAT",
"DBR_GR_ENUM",
"DBR_GR_CHAR",
"DBR_GR_LONG",
"DBR_GR_DOUBLE",
"DBR_CTRL_STRING",
"DBR_CTRL_SHORT",
"DBR_CTRL_FLOAT",
"DBR_CTRL_ENUM",
"DBR_CTRL_CHAR",
"DBR_CTRL_LONG",
"DBR_CTRL_DOUBLE",
"DBR_PUT_ACKT",
"DBR_PUT_ACKS",
"DBR_STSACK_STRING",
"DBR_CLASS_NAME",
];
pub fn dbr_text_to_type(text: &str) -> Option<u16> {
DBR_TEXT.iter().position(|&n| n == text).map(|i| i as u16)
}
pub fn dbr_type_to_text(code: u16) -> &'static str {
DBR_TEXT
.get(code as usize)
.copied()
.unwrap_or("DBR_invalid")
}
#[cfg(test)]
mod buffer_size_tests {
use super::*;
#[test]
fn sts_double_meta_is_8() {
assert_eq!(dbr_buffer_size(DBR_STS_DOUBLE, DbFieldType::Double, 1), 16);
assert_eq!(
dbr_buffer_size(DBR_STS_DOUBLE, DbFieldType::Double, 5),
8 + 8 * 5
);
}
#[test]
fn sts_char_meta_is_5() {
assert_eq!(dbr_buffer_size(DBR_STS_CHAR, DbFieldType::Char, 1), 6);
assert_eq!(dbr_buffer_size(DBR_STS_CHAR, DbFieldType::Char, 10), 5 + 10);
}
#[test]
fn sts_short_meta_is_4() {
assert_eq!(dbr_buffer_size(DBR_STS_SHORT, DbFieldType::Short, 1), 6);
assert_eq!(dbr_buffer_size(DBR_STS_LONG, DbFieldType::Long, 1), 8);
assert_eq!(dbr_buffer_size(DBR_STS_FLOAT, DbFieldType::Float, 1), 8);
}
#[test]
fn plain_value_size_only() {
assert_eq!(dbr_buffer_size(DBR_DOUBLE, DbFieldType::Double, 3), 24);
}
#[test]
fn time_meta_includes_risc_pad() {
assert_eq!(dbr_buffer_size(DBR_TIME_DOUBLE, DbFieldType::Double, 1), 24);
assert_eq!(dbr_buffer_size(DBR_TIME_SHORT, DbFieldType::Short, 1), 16);
assert_eq!(dbr_buffer_size(DBR_TIME_ENUM, DbFieldType::Enum, 1), 16);
assert_eq!(dbr_buffer_size(DBR_TIME_CHAR, DbFieldType::Char, 1), 16);
assert_eq!(dbr_buffer_size(DBR_TIME_FLOAT, DbFieldType::Float, 1), 16);
assert_eq!(dbr_buffer_size(DBR_TIME_LONG, DbFieldType::Long, 1), 16);
assert_eq!(
dbr_buffer_size(DBR_TIME_DOUBLE, DbFieldType::Double, 4),
16 + 8 * 4
);
}
#[test]
fn gr_ctrl_meta_is_per_type() {
assert_eq!(dbr_buffer_size(DBR_GR_SHORT, DbFieldType::Short, 1), 24 + 2);
assert_eq!(dbr_buffer_size(DBR_GR_FLOAT, DbFieldType::Float, 1), 40 + 4);
assert_eq!(
dbr_buffer_size(DBR_GR_DOUBLE, DbFieldType::Double, 1),
64 + 8
);
assert_eq!(dbr_buffer_size(DBR_GR_CHAR, DbFieldType::Char, 1), 19 + 1);
assert_eq!(dbr_buffer_size(DBR_GR_LONG, DbFieldType::Long, 1), 36 + 4);
assert_eq!(dbr_buffer_size(DBR_GR_ENUM, DbFieldType::Enum, 1), 422 + 2);
assert_eq!(
dbr_buffer_size(DBR_CTRL_DOUBLE, DbFieldType::Double, 1),
80 + 8
);
assert_eq!(
dbr_buffer_size(DBR_CTRL_SHORT, DbFieldType::Short, 1),
28 + 2
);
assert_eq!(dbr_buffer_size(DBR_CTRL_CHAR, DbFieldType::Char, 1), 21 + 1);
}
}
#[cfg(test)]
mod dbf_link_class_tests {
use super::*;
#[test]
fn dbcommon_links_classified_uniformly() {
assert_eq!(dbf_link_class("ai", "FLNK"), Some(DbfLinkClass::FwdLink));
assert_eq!(dbf_link_class("ao", "SDIS"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("calc", "TSEL"), Some(DbfLinkClass::InLink));
}
#[test]
fn record_specific_link_families_the_old_name_list_missed() {
assert_eq!(dbf_link_class("seq", "DOL0"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("seq", "LNK0"), Some(DbfLinkClass::OutLink));
assert_eq!(dbf_link_class("seq", "DOLA"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("seq", "DOLF"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("seq", "LNKF"), Some(DbfLinkClass::OutLink));
assert_eq!(dbf_link_class("sel", "NVL"), Some(DbfLinkClass::InLink));
assert_eq!(
dbf_link_class("histogram", "SVL"),
Some(DbfLinkClass::InLink)
);
assert_eq!(dbf_link_class("calc", "INPA"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("aSub", "INPU"), Some(DbfLinkClass::InLink));
assert_eq!(
dbf_link_class("dfanout", "OUTA"),
Some(DbfLinkClass::OutLink)
);
assert_eq!(dbf_link_class("fanout", "OUTA"), None);
assert_eq!(dbf_link_class("printf", "INP0"), Some(DbfLinkClass::InLink));
}
#[test]
fn every_declared_field_classifies_as_its_dbd_says() {
use crate::server::record::dbd_generated::{DB_COMMON_FIELDS, RECORD_TYPES, record_fields};
let mut wrong = Vec::new();
for rt in RECORD_TYPES {
let own = record_fields(rt).unwrap_or(&[]);
for d in DB_COMMON_FIELDS.iter().chain(own.iter()) {
let declared = match d.declared_dbf {
DbfCode::Inlink => Some(DbfLinkClass::InLink),
DbfCode::Outlink => Some(DbfLinkClass::OutLink),
DbfCode::Fwdlink => Some(DbfLinkClass::FwdLink),
_ => None,
};
let got = dbf_link_class(rt, d.name);
if got != declared {
wrong.push(format!("{rt}.{} declared={declared:?} got={got:?}", d.name));
}
}
}
assert!(
wrong.is_empty(),
"{} fields disagree with their declaration:\n{}",
wrong.len(),
wrong.join("\n")
);
}
#[test]
fn the_two_shapes_the_name_rule_could_not_express() {
for rt in ["acalcout", "scalcout"] {
for f in ["INAA", "INBB", "INLL"] {
assert_eq!(
dbf_link_class(rt, f),
Some(DbfLinkClass::InLink),
"{rt}.{f}"
);
}
}
for (rt, f) in [
("acalcout", "OUTV"),
("calcout", "OUTV"),
("scalcout", "OUTV"),
("calcout", "INPV"),
("compress", "INPN"),
("swait", "DOLN"),
("swait", "OUTN"),
("swait", "DOLV"),
("swait", "OUTV"),
("swait", "DOLD"),
] {
assert_eq!(dbf_link_class(rt, f), None, "{rt}.{f} is not a link field");
}
}
#[test]
fn a_registered_downstream_type_resolves_its_own_declarations() {
use crate::server::record::{FieldDesc, register_declared_fields};
static FIELDS: &[FieldDesc] = &[FieldDesc {
name: "RDBL",
dbf_type: DbFieldType::String,
declared_dbf: DbfCode::Inlink,
runtime_typed: false,
read_only: false,
special: crate::server::record::Special::None,
declared_special: crate::server::record::Special::None,
pp: false,
asl: crate::server::record::Asl::Asl1,
size: 0,
extra: None,
menu: None,
initial: None,
interest: 0,
prop: false,
prompt: None,
promptgroup: None,
base: crate::server::record::Base::Decimal,
}];
assert_eq!(dbf_link_class("zzTestRec", "RDBL"), None);
assert_eq!(
dbf_link_class("zzTestRec", "FLNK"),
Some(DbfLinkClass::FwdLink)
);
register_declared_fields("zzTestRec", FIELDS);
assert_eq!(
dbf_link_class("zzTestRec", "RDBL"),
Some(DbfLinkClass::InLink),
"the name rule this replaced answered None for motor's RDBL"
);
}
#[test]
fn siol_class_depends_on_record_direction() {
assert_eq!(dbf_link_class("bo", "SIOL"), Some(DbfLinkClass::OutLink));
assert_eq!(dbf_link_class("ao", "SIOL"), Some(DbfLinkClass::OutLink));
assert_eq!(dbf_link_class("ai", "SIOL"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("bi", "SIOL"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("bo", "SIML"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("ai", "SIML"), Some(DbfLinkClass::InLink));
}
#[test]
fn siol_direction_matches_every_c_dbd_this_workspace_ports() {
for rtype in [
"ao",
"bo",
"busy",
"longout",
"int64out",
"mbbo",
"mbboDirect",
"stringout",
"lso",
"aao",
] {
assert_eq!(
dbf_link_class(rtype, "SIOL"),
Some(DbfLinkClass::OutLink),
"{rtype} declares field(SIOL,DBF_OUTLINK)"
);
}
for rtype in [
"ai",
"bi",
"mbbi",
"mbbiDirect",
"longin",
"int64in",
"stringin",
"lsi",
"event",
"waveform",
"aai",
"histogram",
"swait",
] {
assert_eq!(
dbf_link_class(rtype, "SIOL"),
Some(DbfLinkClass::InLink),
"{rtype} declares field(SIOL,DBF_INLINK)"
);
}
}
#[test]
fn a_busy_siol_discards_cp_the_way_an_output_link_must() {
use crate::server::record::{
LinkFieldType, LinkProcessPolicy, ParsedLink, parse_link_field,
};
let ftype = match dbf_link_class("busy", "SIOL").unwrap() {
DbfLinkClass::InLink => LinkFieldType::In,
DbfLinkClass::OutLink => LinkFieldType::Out,
DbfLinkClass::FwdLink => LinkFieldType::Fwd,
};
match parse_link_field("TGT.VAL CPP", ftype) {
ParsedLink::Db(db) => assert_eq!(db.policy, LinkProcessPolicy::NoProcess),
other => panic!("expected a db link, got {other:?}"),
}
}
#[test]
fn lnk_class_depends_on_record_type() {
assert_eq!(
dbf_link_class("fanout", "LNK0"),
Some(DbfLinkClass::FwdLink),
"fanout LNK0 is DBF_FWDLINK (16), not OUTLINK"
);
assert_eq!(
dbf_link_class("fanout", "LNKF"),
Some(DbfLinkClass::FwdLink),
"fanout LNKF is DBF_FWDLINK (16), not OUTLINK"
);
assert_eq!(dbf_link_class("seq", "LNK0"), Some(DbfLinkClass::OutLink));
assert_eq!(dbf_link_class("seq", "LNKF"), Some(DbfLinkClass::OutLink));
assert_eq!(dbf_link_class("sseq", "LNK1"), Some(DbfLinkClass::OutLink));
assert_eq!(
dbf_link_class("fanout", "FLNK"),
Some(DbfLinkClass::FwdLink)
);
}
#[test]
fn plain_value_fields_are_not_links() {
for f in [
"VAL", "EGU", "PREC", "HOPR", "RVAL", "DESC", "A", "B", "OVAL",
] {
assert_eq!(dbf_link_class("ai", f), None, "{f} must not be a link");
}
}
#[test]
fn case_insensitive_and_trims() {
assert_eq!(dbf_link_class("ai", "inp"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("ao", " OUT "), Some(DbfLinkClass::OutLink));
}
#[test]
fn dbf_codes_and_range_match_pvxs() {
assert_eq!(DbfLinkClass::InLink.dbf_type(), 14);
assert_eq!(DbfLinkClass::OutLink.dbf_type(), 15);
assert_eq!(DbfLinkClass::FwdLink.dbf_type(), 16);
assert!(is_link_dbf_type(DBF_INLINK));
assert!(is_link_dbf_type(DBF_OUTLINK));
assert!(is_link_dbf_type(DBF_FWDLINK));
assert!(!is_link_dbf_type(13)); assert!(!is_link_dbf_type(17)); assert!(!is_link_dbf_type(0)); }
}
#[cfg(test)]
mod dbr_text_tests {
use super::*;
#[test]
fn resolves_every_type_name_to_its_code() {
for (code, name) in DBR_TEXT.iter().enumerate() {
assert_eq!(
dbr_text_to_type(name),
Some(code as u16),
"{name} should resolve to {code}"
);
}
assert_eq!(dbr_text_to_type("DBR_STRING"), Some(DBR_STRING));
assert_eq!(dbr_text_to_type("DBR_TIME_FLOAT"), Some(DBR_TIME_FLOAT));
assert_eq!(
dbr_text_to_type("DBR_STSACK_STRING"),
Some(DBR_STSACK_STRING)
);
assert_eq!(dbr_text_to_type("DBR_CLASS_NAME"), Some(DBR_CLASS_NAME));
}
#[test]
fn is_case_sensitive_like_c_strcmp() {
assert_eq!(dbr_text_to_type("dbr_time_float"), None);
assert_eq!(dbr_text_to_type("DBR_Time_Float"), None);
}
#[test]
fn unknown_and_bare_family_names_do_not_match() {
assert_eq!(dbr_text_to_type("TIME_FLOAT"), None);
assert_eq!(dbr_text_to_type("DOUBLE"), None);
assert_eq!(dbr_text_to_type("DBR_NONSENSE"), None);
assert_eq!(dbr_text_to_type(""), None);
}
}
#[cfg(test)]
mod dbf_code_tests {
use super::*;
const C_DBF_TYPE: [(DbfCode, i16, &str); 18] = [
(DbfCode::String, 0, "STRING"),
(DbfCode::Char, 1, "CHAR"),
(DbfCode::UChar, 2, "UCHAR"),
(DbfCode::Short, 3, "SHORT"),
(DbfCode::UShort, 4, "USHORT"),
(DbfCode::Long, 5, "LONG"),
(DbfCode::ULong, 6, "ULONG"),
(DbfCode::Int64, 7, "INT64"),
(DbfCode::UInt64, 8, "UINT64"),
(DbfCode::Float, 9, "FLOAT"),
(DbfCode::Double, 10, "DOUBLE"),
(DbfCode::Enum, 11, "ENUM"),
(DbfCode::Menu, 12, "MENU"),
(DbfCode::Device, 13, "DEVICE"),
(DbfCode::Inlink, 14, "INLINK"),
(DbfCode::Outlink, 15, "OUTLINK"),
(DbfCode::Fwdlink, 16, "FWDLINK"),
(DbfCode::NoAccess, 17, "NOACCESS"),
];
fn _the_enum_has_no_variant_outside_the_table(c: DbfCode) {
match c {
DbfCode::String
| DbfCode::Char
| DbfCode::UChar
| DbfCode::Short
| DbfCode::UShort
| DbfCode::Long
| DbfCode::ULong
| DbfCode::Int64
| DbfCode::UInt64
| DbfCode::Float
| DbfCode::Double
| DbfCode::Enum
| DbfCode::Menu
| DbfCode::Device
| DbfCode::Inlink
| DbfCode::Outlink
| DbfCode::Fwdlink
| DbfCode::NoAccess => (),
}
}
#[test]
fn every_dbf_code_carries_cs_number_and_name() {
for (i, (code, n, name)) in C_DBF_TYPE.iter().enumerate() {
assert_eq!(*code as i16, *n, "DBF_{name}: wrong dbfType code");
assert_eq!(code.name(), *name, "DBF_{name}: wrong name");
assert_eq!(i as i16, *n, "DBF_{name}: table position != code");
}
assert_eq!(C_DBF_TYPE.len(), 18);
for (a, (x, ..)) in C_DBF_TYPE.iter().enumerate() {
for (y, ..) in C_DBF_TYPE.iter().skip(a + 1) {
assert_ne!(x, y, "{x:?} listed twice");
}
}
}
#[test]
fn every_served_type_maps_to_its_dbstatic_code() {
let table = [
(DbFieldType::String, DbfCode::String),
(DbFieldType::Short, DbfCode::Short),
(DbFieldType::Float, DbfCode::Float),
(DbFieldType::Enum, DbfCode::Enum),
(DbFieldType::Char, DbfCode::Char),
(DbFieldType::Long, DbfCode::Long),
(DbFieldType::Double, DbfCode::Double),
(DbFieldType::Int64, DbfCode::Int64),
(DbFieldType::UInt64, DbfCode::UInt64),
(DbFieldType::UShort, DbfCode::UShort),
(DbFieldType::ULong, DbfCode::ULong),
(DbFieldType::UChar, DbfCode::UChar),
];
for (ft, code) in table {
assert_eq!(ft.dbf_code(), code, "{ft:?}");
}
assert_eq!(table.len(), 12);
for (i, (ft, _)) in table.iter().enumerate() {
assert_eq!(*ft as u16, i as u16, "{ft:?}: CA wire code moved");
}
}
#[test]
fn the_dbr_map_is_cs_whole_table() {
const C_MAP: [(DbfCode, DbfCode); 18] = [
(DbfCode::String, DbfCode::String),
(DbfCode::Char, DbfCode::Char),
(DbfCode::UChar, DbfCode::UChar),
(DbfCode::Short, DbfCode::Short),
(DbfCode::UShort, DbfCode::UShort),
(DbfCode::Long, DbfCode::Long),
(DbfCode::ULong, DbfCode::ULong),
(DbfCode::Int64, DbfCode::Int64),
(DbfCode::UInt64, DbfCode::UInt64),
(DbfCode::Float, DbfCode::Float),
(DbfCode::Double, DbfCode::Double),
(DbfCode::Enum, DbfCode::Enum),
(DbfCode::Menu, DbfCode::Enum),
(DbfCode::Device, DbfCode::Enum),
(DbfCode::Inlink, DbfCode::String),
(DbfCode::Outlink, DbfCode::String),
(DbfCode::Fwdlink, DbfCode::String),
(DbfCode::NoAccess, DbfCode::NoAccess),
];
for (dbf, dbr) in C_MAP {
assert_eq!(dbf.dbr_code(), dbr, "mapDBFToDBR[DBF_{}]", dbf.name());
}
assert_eq!(C_MAP.len(), 18);
for (i, (dbf, _)) in C_MAP.iter().enumerate() {
assert_eq!(
*dbf as i16,
i as i16,
"DBF_{}: row out of order",
dbf.name()
);
}
assert_eq!(DbfCode::NoAccess.dbr_code() as i16, 17);
assert_eq!(DbfCode::NoAccess.dbr_code().name(), "NOACCESS");
}
}