use crate::error::{CaError, CaResult};
use crate::server::record::{FieldDesc, ProcessOutcome, Record};
use crate::types::{DbFieldType, EpicsValue};
const MAX_STRING_SIZE: usize = 40;
fn truncate_string(s: &str) -> String {
let max = MAX_STRING_SIZE - 1;
if s.len() <= max {
return s.to_string();
}
let trunc = (0..=max)
.rev()
.find(|&i| s.is_char_boundary(i))
.unwrap_or(0);
s[..trunc].to_string()
}
pub struct StringinRecord {
pub val: String,
pub oval: String,
pub simm: i16,
pub siml: String,
pub siol: String,
pub sims: i16,
}
impl Default for StringinRecord {
fn default() -> Self {
Self {
val: String::new(),
oval: String::new(),
simm: 0,
siml: String::new(),
siol: String::new(),
sims: 0,
}
}
}
impl StringinRecord {
pub fn new(val: &str) -> Self {
Self {
val: truncate_string(val),
..Default::default()
}
}
}
static STRINGIN_FIELDS: &[FieldDesc] = &[
FieldDesc {
name: "VAL",
dbf_type: DbFieldType::String,
read_only: false,
},
FieldDesc {
name: "OVAL",
dbf_type: DbFieldType::String,
read_only: true,
},
FieldDesc {
name: "SIMM",
dbf_type: DbFieldType::Short,
read_only: false,
},
FieldDesc {
name: "SIML",
dbf_type: DbFieldType::String,
read_only: false,
},
FieldDesc {
name: "SIOL",
dbf_type: DbFieldType::String,
read_only: false,
},
FieldDesc {
name: "SIMS",
dbf_type: DbFieldType::Short,
read_only: false,
},
];
impl Record for StringinRecord {
fn record_type(&self) -> &'static str {
"stringin"
}
fn field_list(&self) -> &'static [FieldDesc] {
STRINGIN_FIELDS
}
fn process(&mut self) -> CaResult<ProcessOutcome> {
self.oval = self.val.clone();
Ok(ProcessOutcome::complete())
}
fn get_field(&self, name: &str) -> Option<EpicsValue> {
match name {
"VAL" => Some(EpicsValue::String(self.val.clone())),
"OVAL" => Some(EpicsValue::String(self.oval.clone())),
"SIMM" => Some(EpicsValue::Short(self.simm)),
"SIML" => Some(EpicsValue::String(self.siml.clone())),
"SIOL" => Some(EpicsValue::String(self.siol.clone())),
"SIMS" => Some(EpicsValue::Short(self.sims)),
_ => None,
}
}
fn put_field(&mut self, name: &str, value: EpicsValue) -> CaResult<()> {
match name {
"VAL" => match value {
EpicsValue::String(s) => {
self.val = truncate_string(&s);
Ok(())
}
_ => Err(CaError::TypeMismatch("VAL".into())),
},
"OVAL" => match value {
EpicsValue::String(s) => {
self.oval = truncate_string(&s);
Ok(())
}
_ => Err(CaError::TypeMismatch("OVAL".into())),
},
"SIMM" => match value {
EpicsValue::Short(v) => {
self.simm = v;
Ok(())
}
_ => Err(CaError::TypeMismatch("SIMM".into())),
},
"SIML" => match value {
EpicsValue::String(s) => {
self.siml = s;
Ok(())
}
_ => Err(CaError::TypeMismatch("SIML".into())),
},
"SIOL" => match value {
EpicsValue::String(s) => {
self.siol = s;
Ok(())
}
_ => Err(CaError::TypeMismatch("SIOL".into())),
},
"SIMS" => match value {
EpicsValue::Short(v) => {
self.sims = v;
Ok(())
}
_ => Err(CaError::TypeMismatch("SIMS".into())),
},
_ => Err(CaError::FieldNotFound(name.to_string())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn val_truncated_to_max_string_size() {
let long = "x".repeat(100);
let mut rec = StringinRecord::default();
rec.put_field("VAL", EpicsValue::String(long)).unwrap();
assert_eq!(rec.val.len(), 39, "VAL capped at MAX_STRING_SIZE-1");
}
#[test]
fn val_at_limit_kept_whole() {
let s = "y".repeat(39);
let rec = StringinRecord::new(&s);
assert_eq!(rec.val.len(), 39);
}
}