use crate::error::{CaError, CaResult};
use crate::server::device_support::{DeviceReadOutcome, DeviceSupport};
use crate::server::recgbl::alarm_status;
use crate::server::record::{AlarmSeverity, ProcessContext, Record};
use crate::types::EpicsValue;
pub struct GetenvDeviceSupport {
var: String,
udfs: AlarmSeverity,
unset_alarm: Option<(u16, u16)>,
}
impl GetenvDeviceSupport {
pub fn new(inp: &str) -> Self {
Self {
var: Self::resolve_var_name(inp).to_string(),
udfs: AlarmSeverity::Invalid,
unset_alarm: None,
}
}
fn resolve_var_name(inp: &str) -> &str {
let trimmed = inp.trim();
trimmed.strip_prefix('@').unwrap_or(trimmed).trim()
}
}
impl DeviceSupport for GetenvDeviceSupport {
fn dtyp(&self) -> &str {
"getenv"
}
fn init(&mut self, record: &mut dyn Record) -> CaResult<()> {
let rtype = record.record_type();
if !matches!(rtype, "stringin" | "lsi") {
return Err(CaError::InvalidValue(format!(
"DTYP=getenv: unsupported record type '{rtype}' (use stringin or lsi)"
)));
}
Ok(())
}
fn read(&mut self, record: &mut dyn Record) -> CaResult<DeviceReadOutcome> {
let val = if self.var.is_empty() {
None
} else {
std::env::var(&self.var).ok()
};
match val {
Some(s) => {
self.unset_alarm = None;
record.put_field("VAL", EpicsValue::String(s.into()))?;
Ok(DeviceReadOutcome::ok())
}
None => {
self.unset_alarm = Some((alarm_status::UDF_ALARM, self.udfs as u16));
record.put_field("VAL", EpicsValue::String(String::new().into()))?;
Ok(DeviceReadOutcome::ok())
}
}
}
fn set_process_context(&mut self, ctx: &ProcessContext) {
self.udfs = ctx.udfs;
}
fn last_alarm(&self) -> Option<(u16, u16)> {
self.unset_alarm
}
fn write(&mut self, _record: &mut dyn Record) -> CaResult<()> {
Err(CaError::InvalidValue(
"getenv device support is read-only (stringin / lsi only)".into(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::server::records::ai::AiRecord;
#[test]
fn resolve_var_name_strips_at_prefix() {
assert_eq!(
GetenvDeviceSupport::resolve_var_name("@HOSTNAME"),
"HOSTNAME"
);
assert_eq!(
GetenvDeviceSupport::resolve_var_name(" HOSTNAME "),
"HOSTNAME"
);
assert_eq!(
GetenvDeviceSupport::resolve_var_name("@ HOSTNAME"),
"HOSTNAME"
);
assert_eq!(GetenvDeviceSupport::resolve_var_name(""), "");
assert_eq!(GetenvDeviceSupport::resolve_var_name("@"), "");
}
#[test]
fn new_resolves_var_from_inp() {
assert_eq!(GetenvDeviceSupport::new("@HOSTNAME").var, "HOSTNAME");
assert_eq!(GetenvDeviceSupport::new(" PATH ").var, "PATH");
assert!(GetenvDeviceSupport::new("@").var.is_empty());
assert_eq!(GetenvDeviceSupport::new("@PATH").dtyp(), "getenv");
}
#[test]
fn init_rejects_unsupported_record_type() {
let mut dev = GetenvDeviceSupport::new("@PATH");
let mut rec = AiRecord::new(0.0);
assert!(dev.init(&mut rec).is_err());
}
}