1use std::fs::File;
8use std::io::{Read, Write};
9use std::path::Path;
10use std::result::Result;
11
12use serde;
13use serde_yaml;
14
15use opcua_types::{ApplicationDescription, ApplicationType, LocalizedText, UAString};
16
17#[derive(Debug)]
19pub enum ConfigError {
20 ConfigInvalid(Vec<String>),
22 IO(std::io::Error),
24 Yaml(serde_yaml::Error),
26}
27
28impl From<std::io::Error> for ConfigError {
29 fn from(value: std::io::Error) -> Self {
30 Self::IO(value)
31 }
32}
33
34impl From<serde_yaml::Error> for ConfigError {
35 fn from(value: serde_yaml::Error) -> Self {
36 Self::Yaml(value)
37 }
38}
39
40pub trait Config: serde::Serialize {
43 fn save(&self, path: &Path) -> Result<(), ConfigError> {
45 if let Err(e) = self.validate() {
46 return Err(ConfigError::ConfigInvalid(e));
47 }
48 let s = serde_yaml::to_string(&self)?;
49 let mut f = File::create(path)?;
50 f.write_all(s.as_bytes())?;
51 Ok(())
52 }
53
54 #[cfg(feature = "env_expansion")]
56 fn load<A>(path: &Path) -> Result<A, ConfigError>
57 where
58 for<'de> A: Config + serde::Deserialize<'de>,
59 {
60 let mut f = File::open(path)?;
61 let mut s = String::new();
62 f.read_to_string(&mut s)?;
63 let mut value: serde_yaml::Value = serde_yaml::from_str(&s)?;
64 expand_env_in_value(&mut value);
65 return Ok(serde_yaml::from_value(value)?);
66 }
67
68 #[cfg(not(feature = "env_expansion"))]
70 fn load<A>(path: &Path) -> Result<A, ConfigError>
71 where
72 for<'de> A: Config + serde::Deserialize<'de>,
73 {
74 let mut f = File::open(path)?;
75 let mut s = String::new();
76 f.read_to_string(&mut s)?;
77 Ok(serde_yaml::from_str(&s)?)
78 }
79
80 fn validate(&self) -> Result<(), Vec<String>>;
82
83 fn application_name(&self) -> UAString;
85
86 fn application_uri(&self) -> UAString;
88
89 fn product_uri(&self) -> UAString;
91
92 fn application_type(&self) -> ApplicationType;
94
95 fn discovery_urls(&self) -> Option<Vec<UAString>> {
97 None
98 }
99
100 fn application_description(&self) -> ApplicationDescription {
102 ApplicationDescription {
103 application_uri: self.application_uri(),
104 application_name: LocalizedText::new("", self.application_name().as_ref()),
105 application_type: self.application_type(),
106 product_uri: self.product_uri(),
107 gateway_server_uri: UAString::null(),
108 discovery_profile_uri: UAString::null(),
109 discovery_urls: self.discovery_urls(),
110 }
111 }
112}
113
114#[cfg(feature = "env_expansion")]
115fn expand_env_in_value(value: &mut serde_yaml::Value) {
116 use serde_yaml::Value;
117 match value {
118 Value::String(s) => {
119 *value = match shellexpand::env(s) {
120 Ok(expanded) => match expanded.as_ref() {
121 "null" | "~" => Value::Null,
122 expanded_str => expanded_str
123 .parse::<bool>()
124 .map(Value::Bool)
125 .or_else(|_| expanded_str.parse::<i64>().map(|i| Value::Number(i.into())))
126 .or_else(|_| expanded_str.parse::<u64>().map(|u| Value::Number(u.into())))
127 .or_else(|_| expanded_str.parse::<f64>().map(|f| Value::Number(f.into())))
128 .unwrap_or_else(|_| Value::String(expanded.to_string())),
129 },
130 Err(_) => Value::Null,
131 }
132 }
133 Value::Sequence(seq) => {
134 for v in seq {
135 expand_env_in_value(v);
136 }
137 }
138 Value::Mapping(map) => {
139 for (_k, v) in map.iter_mut() {
140 expand_env_in_value(v);
141 }
142 }
143 _ => (),
144 }
145}