use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use std::result::Result;
use serde;
use serde_yaml;
use opcua_types::{ApplicationDescription, ApplicationType, LocalizedText, UAString};
#[derive(Debug)]
pub enum ConfigError {
ConfigInvalid(Vec<String>),
IO(std::io::Error),
Yaml(serde_yaml::Error),
}
impl From<std::io::Error> for ConfigError {
fn from(value: std::io::Error) -> Self {
Self::IO(value)
}
}
impl From<serde_yaml::Error> for ConfigError {
fn from(value: serde_yaml::Error) -> Self {
Self::Yaml(value)
}
}
pub trait Config: serde::Serialize {
fn save(&self, path: &Path) -> Result<(), ConfigError> {
if let Err(e) = self.validate() {
return Err(ConfigError::ConfigInvalid(e));
}
let s = serde_yaml::to_string(&self)?;
let mut f = File::create(path)?;
f.write_all(s.as_bytes())?;
Ok(())
}
#[cfg(feature = "env_expansion")]
fn load<A>(path: &Path) -> Result<A, ConfigError>
where
for<'de> A: Config + serde::Deserialize<'de>,
{
let mut f = File::open(path)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
let mut value: serde_yaml::Value = serde_yaml::from_str(&s)?;
expand_env_in_value(&mut value);
Ok(serde_yaml::from_value(value)?)
}
#[cfg(not(feature = "env_expansion"))]
fn load<A>(path: &Path) -> Result<A, ConfigError>
where
for<'de> A: Config + serde::Deserialize<'de>,
{
let mut f = File::open(path)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(serde_yaml::from_str(&s)?)
}
fn validate(&self) -> Result<(), Vec<String>>;
fn application_name(&self) -> UAString;
fn application_uri(&self) -> UAString;
fn product_uri(&self) -> UAString;
fn application_type(&self) -> ApplicationType;
fn discovery_urls(&self) -> Option<Vec<UAString>> {
None
}
fn application_description(&self) -> ApplicationDescription {
ApplicationDescription {
application_uri: self.application_uri(),
application_name: LocalizedText::new("", self.application_name().as_ref()),
application_type: self.application_type(),
product_uri: self.product_uri(),
gateway_server_uri: UAString::null(),
discovery_profile_uri: UAString::null(),
discovery_urls: self.discovery_urls(),
}
}
}
#[cfg(feature = "env_expansion")]
fn expand_env_in_value(value: &mut serde_yaml::Value) {
use serde_yaml::Value;
match value {
Value::String(s) => {
*value = match shellexpand::env(s) {
Ok(expanded) => match expanded.as_ref() {
"null" | "~" => Value::Null,
expanded_str => expanded_str
.parse::<bool>()
.map(Value::Bool)
.or_else(|_| expanded_str.parse::<i64>().map(|i| Value::Number(i.into())))
.or_else(|_| expanded_str.parse::<u64>().map(|u| Value::Number(u.into())))
.or_else(|_| expanded_str.parse::<f64>().map(|f| Value::Number(f.into())))
.unwrap_or_else(|_| Value::String(expanded.to_string())),
},
Err(_) => Value::Null,
}
}
Value::Sequence(seq) => {
for v in seq {
expand_env_in_value(v);
}
}
Value::Mapping(map) => {
for (_k, v) in map.iter_mut() {
expand_env_in_value(v);
}
}
_ => (),
}
}