Skip to main content

opcua_core/
config.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2024 Adam Lock
4
5//! Common utilities for configuration files in both the server and client.
6
7use 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/// Error returned from saving or loading config objects.
18#[derive(Debug)]
19pub enum ConfigError {
20    /// Configuration is invalid, with a list of validation errors.
21    ConfigInvalid(Vec<String>),
22    /// Reading or writing file failed.
23    IO(std::io::Error),
24    /// Failed to serialize or deserialize config object.
25    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
40/// A trait that handles the loading / saving and validity of configuration information for a
41/// client and/or server.
42pub trait Config: serde::Serialize {
43    /// Save the configuration object to a file.
44    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    /// Load the configuration object from the given path.
55    #[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    /// Load the configuration object from the given path.
69    #[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    /// Validate the config struct, returning a list of validation errors if it fails.
81    fn validate(&self) -> Result<(), Vec<String>>;
82
83    /// Get the application name.
84    fn application_name(&self) -> UAString;
85
86    /// Get the application URI.
87    fn application_uri(&self) -> UAString;
88
89    /// Get the configured product URI.
90    fn product_uri(&self) -> UAString;
91
92    /// Get the application type.
93    fn application_type(&self) -> ApplicationType;
94
95    /// Get the registered discovery URLs for this application.
96    fn discovery_urls(&self) -> Option<Vec<UAString>> {
97        None
98    }
99
100    /// Create an application description for the configured application.
101    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}