Lines
98.27 %
Functions
3.61 %
Branches
100 %
//! Program configurations.
use std::{collections::HashMap, env};
use clap::{
builder::{BoolValueParser, RangedU64ValueParser},
Arg, ArgMatches, Command,
};
use serde::Deserialize;
use sylvia_iot_corelib::constants::{CacheEngine, DbEngine};
/// Configuration file object.
#[derive(Default, Deserialize)]
pub struct Config {
/// **sylvia-iot-auth** API base path with host. For example: `http://localhost:1080/auth`.
pub auth: Option<String>,
pub db: Option<Db>,
pub cache: Option<Cache>,
pub mq: Option<Mq>,
#[serde(rename = "mqChannels")]
pub mq_channels: Option<MqChannels>,
#[serde(rename = "apiScopes")]
pub api_scopes: Option<HashMap<String, Vec<String>>>,
}
/// Database configuration object.
pub struct Db {
/// Select the model implementation.
/// - `mongodb`: pure MongoDB.
/// - `sqlite`: pure SQLite.
pub engine: Option<String>,
pub mongodb: Option<MongoDb>,
pub sqlite: Option<Sqlite>,
/// MongoDB configuration object.
pub struct MongoDb {
/// Use `mongodb://username:password@host:port` format.
pub url: Option<String>,
pub database: Option<String>,
#[serde(rename = "poolSize")]
pub pool_size: Option<u32>,
/// SQLite configuration object.
pub struct Sqlite {
/// Use absolute/relative path.
pub path: Option<String>,
/// Cache configuration object.
pub struct Cache {
/// Select the cache implementation.
/// - `none`: disable cache.
/// - `memory`: pure memory.
pub memory: Option<MemoryCache>,
/// Memory cache configuration object.
pub struct MemoryCache {
/// Maximum number of device cache count.
pub device: Option<usize>,
/// Maximum number of device route cache count.
#[serde(rename = "deviceRoute")]
pub device_route: Option<usize>,
/// Maximum number of network route cache count.
#[serde(rename = "networkRoute")]
pub network_route: Option<usize>,
/// Message queue configuration object.
pub struct Mq {
/// AMQP QoS prefetch from **1** to **65535**. None or zero use default value **100**.
pub prefetch: Option<u16>,
/// AMQP persistent.
pub persistent: Option<bool>,
/// MQTT shared subscription topic prefix.
#[serde(rename = "sharedPrefix")]
pub shared_prefix: Option<String>,
/// Message channels configuration object.
pub struct MqChannels {
pub unit: Option<BrokerCtrl>,
pub application: Option<BrokerCtrl>,
pub network: Option<BrokerCtrl>,
pub device: Option<BrokerCtrl>,
pub device_route: Option<BrokerCtrl>,
pub network_route: Option<BrokerCtrl>,
pub data: Option<BrokerData>,
/// Channel `broker.ctrl` configuration object.
pub struct BrokerCtrl {
/// Queue connection URL of the control channel.
/// Channel `broker.data` configuration object.
pub struct BrokerData {
/// Queue connection URL of the data channel.
pub const DEF_AUTH: &'static str = "http://localhost:1080/auth";
pub const DEF_ENGINE: &'static str = DbEngine::SQLITE;
pub const DEF_MONGODB_URL: &'static str = "mongodb://localhost:27017";
pub const DEF_MONGODB_DB: &'static str = "broker";
pub const DEF_SQLITE_PATH: &'static str = "broker.db";
pub const DEF_CACHE_ENGINE: &'static str = CacheEngine::NONE;
pub const DEF_MEMORY_DEVICE: usize = 1_000_000;
pub const DEF_MEMORY_DEVICE_ROUTE: usize = 1_000_000;
pub const DEF_MEMORY_NETWORK_ROUTE: usize = 1_000_000;
pub const DEF_MQ_PREFETCH: u16 = 100;
pub const DEF_MQ_PERSISTENT: bool = false;
pub const DEF_MQ_SHAREDPREFIX: &'static str = "$share/sylvia-iot-broker/";
pub const DEF_MQ_CHANNEL_URL: &'static str = "amqp://localhost";
/// To register Clap arguments.
pub fn reg_args(cmd: Command) -> Command {
cmd.arg(
Arg::new("broker.auth")
.long("broker.auth")
.help("sylvia-iot-auth host (ex: http://localhost:1080/auth)")
.num_args(1),
)
.arg(
Arg::new("broker.db.engine")
.long("broker.db.engine")
.help("database engine")
.num_args(1)
.value_parser([DbEngine::MONGODB, DbEngine::SQLITE]),
Arg::new("broker.db.mongodb.url")
.long("broker.db.mongodb.url")
.help("MongoDB URL (scheme://[username][:password][@][host][:port]")
Arg::new("broker.db.mongodb.database")
.long("broker.db.mongodb.database")
.help("database nane")
Arg::new("broker.db.mongodb.poolsize")
.long("broker.db.mongodb.poolsize")
.help("connection pool size")
.value_parser(RangedU64ValueParser::<u64>::new().range(1..=u32::MAX as u64)),
Arg::new("broker.db.sqlite.path")
.long("broker.db.sqlite.path")
.help("SQLite path")
Arg::new("broker.cache.engine")
.long("broker.cache.engine")
.help("cache engine")
.value_parser([CacheEngine::MEMORY, CacheEngine::NONE]),
Arg::new("broker.cache.memory.device")
.long("broker.cache.memory.device")
.help("Device cache size")
.value_parser(RangedU64ValueParser::<u64>::new().range(1..=usize::MAX as u64)),
Arg::new("broker.cache.memory.device-route")
.long("broker.cache.memory.device-route")
.help("Device route cache size")
Arg::new("broker.cache.memory.network-route")
.long("broker.cache.memory.network-route")
.help("Network route cache size")
Arg::new("broker.mq.prefetch")
.long("broker.mq.prefetch")
.help("AMQP prefetch")
.value_parser(RangedU64ValueParser::<u64>::new().range(1..=u16::MAX as u64)),
Arg::new("broker.mq.persistent")
.long("broker.mq.persistent")
.help("AMQP persistent")
.value_parser(BoolValueParser::new()),
Arg::new("broker.mq.sharedprefix")
.long("broker.mq.sharedprefix")
.help("MQTT shared subscription prefix")
Arg::new("broker.mq-channels.unit.url")
.long("broker.mq-channels.unit.url")
.help("URL of `broker.ctrl.unit` channel")
Arg::new("broker.mq-channels.unit.prefetch")
.long("broker.mq-channels.unit.prefetch")
.help("AMQP prefetch for `broker.ctrl.unit` channel")
Arg::new("broker.mq-channels.application.url")
.long("broker.mq-channels.application.url")
.help("URL of `broker.ctrl.application` channel")
Arg::new("broker.mq-channels.application.prefetch")
.long("broker.mq-channels.application.prefetch")
.help("AMQP prefetch for `broker.ctrl.application` channel")
Arg::new("broker.mq-channels.network.url")
.long("broker.mq-channels.network.url")
.help("URL of `broker.ctrl.network` channel")
Arg::new("broker.mq-channels.network.prefetch")
.long("broker.mq-channels.network.prefetch")
.help("AMQP prefetch for `broker.ctrl.network` channel")
Arg::new("broker.mq-channels.device.url")
.long("broker.mq-channels.device.url")
.help("URL of `broker.ctrl.device` channel")
Arg::new("broker.mq-channels.device.prefetch")
.long("broker.mq-channels.device.prefetch")
.help("AMQP prefetch for `broker.ctrl.device` channel")
Arg::new("broker.mq-channels.device-route.url")
.long("broker.mq-channels.device-route.url")
.help("URL of `broker.ctrl.device-route` channel")
Arg::new("broker.mq-channels.device-route.prefetch")
.long("broker.mq-channels.device-route.prefetch")
.help("AMQP prefetch for `broker.ctrl.device-route` channel")
Arg::new("broker.mq-channels.network-route.url")
.long("broker.mq-channels.network-route.url")
.help("URL of `broker.ctrl.network-route` channel")
Arg::new("broker.mq-channels.network-route.prefetch")
.long("broker.mq-channels.network-route.prefetch")
.help("AMQP prefetch for `broker.ctrl.network-route` channel")
Arg::new("broker.mq-channels.data.url")
.long("broker.mq-channels.data.url")
.help("URL of `broker.data` channel")
Arg::new("broker.mq-channels.data.persistent")
.long("broker.mq-channels.data.persistent")
.help("AMQP persistent for `broker.data` channel")
Arg::new("broker.api-scopes")
.long("broker.api-scopes")
.help("API scopes")
.num_args(0..),
/// To read input arguments from command-line arguments and environment variables.
///
/// This function will call [`apply_default()`] to fill missing values so you do not need call it
/// again.
pub fn read_args(args: &ArgMatches) -> Config {
apply_default(&Config {
auth: match args.get_one::<String>("broker.auth") {
None => match env::var("BROKER_AUTH") {
Err(_) => None,
Ok(v) => Some(v),
},
Some(v) => Some(v.clone()),
db: Some(Db {
engine: match args.get_one::<String>("broker.db.engine") {
None => match env::var("BROKER_DB_ENGINE") {
mongodb: Some(MongoDb {
url: match args.get_one::<String>("broker.db.mongodb.url") {
None => match env::var("BROKER_DB_MONGODB_URL") {
database: match args.get_one::<String>("broker.db.mongodb.database") {
None => match env::var("BROKER_DB_MONGODB_DATABASE") {
pool_size: match args.get_one::<u64>("broker.db.mongodb.poolsize") {
None => match env::var("BROKER_DB_MONGODB_POOLSIZE") {
Ok(v) => match v.parse::<u32>() {
Some(v) => Some(*v as u32),
}),
sqlite: Some(Sqlite {
path: match args.get_one::<String>("broker.db.sqlite.path") {
None => match env::var("BROKER_DB_SQLITE_PATH") {
cache: Some(Cache {
engine: match args.get_one::<String>("broker.cache.engine") {
None => match env::var("BROKER_CACHE_ENGINE") {
memory: Some(MemoryCache {
device: match args.get_one::<u64>("broker.cache.memory.device") {
None => match env::var("BROKER_CACHE_MEMORY_DEVICE") {
Ok(v) => match v.parse::<usize>() {
Some(v) => Some(*v as usize),
device_route: match args.get_one::<u64>("broker.cache.memory.device-route") {
None => match env::var("BROKER_CACHE_MEMORY_DEVICE_ROUTE") {
network_route: match args.get_one::<u64>("broker.cache.memory.network-route") {
None => match env::var("BROKER_CACHE_MEMORY_NETWORK_ROUTE") {
mq: Some(Mq {
prefetch: match args.get_one::<u64>("broker.mq.prefetch") {
None => match env::var("BROKER_MQ_PREFETCH") {
Ok(v) => match v.parse::<u16>() {
Some(v) => Some(*v as u16),
persistent: match args.get_one::<bool>("broker.mq.persistent") {
None => match env::var("BROKER_MQ_PERSISTENT") {
Ok(v) => match v.parse::<bool>() {
Some(v) => Some(*v as bool),
shared_prefix: match args.get_one::<String>("broker.mq.sharedprefix") {
None => match env::var("BROKER_MQ_SHAREDPREFIX") {
mq_channels: Some(MqChannels {
unit: Some(BrokerCtrl {
url: match args.get_one::<String>("broker.mq-channels.unit.url") {
None => match env::var("BROKER_MQCHANNELS_UNIT_URL") {
prefetch: match args.get_one::<u64>("broker.mq-channels.unit.prefetch") {
None => match env::var("BROKER_MQCHANNELS_UNIT_PREFETCH") {
application: Some(BrokerCtrl {
url: match args.get_one::<String>("broker.mq-channels.application.url") {
None => match env::var("BROKER_MQCHANNELS_APPLICATION_URL") {
prefetch: match args.get_one::<u64>("broker.mq-channels.application.prefetch") {
None => match env::var("BROKER_MQCHANNELS_APPLICATION_PREFETCH") {
network: Some(BrokerCtrl {
url: match args.get_one::<String>("broker.mq-channels.network.url") {
None => match env::var("BROKER_MQCHANNELS_NETWORK_URL") {
prefetch: match args.get_one::<u64>("broker.mq-channels.network.prefetch") {
None => match env::var("BROKER_MQCHANNELS_NETWORK_PREFETCH") {
device: Some(BrokerCtrl {
url: match args.get_one::<String>("broker.mq-channels.device.url") {
None => match env::var("BROKER_MQCHANNELS_DEVICE_URL") {
prefetch: match args.get_one::<u64>("broker.mq-channels.device.prefetch") {
None => match env::var("BROKER_MQCHANNELS_DEVICE_PREFETCH") {
device_route: Some(BrokerCtrl {
url: match args.get_one::<String>("broker.mq-channels.device-route.url") {
None => match env::var("BROKER_MQCHANNELS_DEVICE_ROUTE_URL") {
prefetch: match args.get_one::<u64>("broker.mq-channels.device-route.prefetch") {
None => match env::var("BROKER_MQCHANNELS_DEVICE_ROUTE_PREFETCH") {
network_route: Some(BrokerCtrl {
url: match args.get_one::<String>("broker.mq-channels.network-route.url") {
None => match env::var("BROKER_MQCHANNELS_NETWORK_ROUTE_URL") {
prefetch: match args.get_one::<u64>("broker.mq-channels.network-route.prefetch") {
None => match env::var("BROKER_MQCHANNELS_NETWORK_ROUTE_PREFETCH") {
data: Some(BrokerData {
url: match args.get_one::<String>("broker.mq-channels.data.url") {
None => match env::var("BROKER_MQCHANNELS_DATA_URL") {
persistent: match args.get_one::<bool>("broker.mq-channels.data.persistent") {
None => match env::var("BROKER_MQCHANNELS_DATA_PERSISTENT") {
api_scopes: match args.get_one::<String>("broker.api-scopes") {
None => match env::var("BROKER_API_SCOPES") {
Ok(v) => match v.len() {
0 => None,
_ => match serde_json::from_str::<HashMap<String, Vec<String>>>(v.as_str()) {
Some(v) => match v.len() {
})
/// Fill missing configuration with default values.
pub fn apply_default(config: &Config) -> Config {
Config {
auth: match config.auth.as_ref() {
None => Some(DEF_AUTH.to_string()),
Some(auth) => Some(auth.clone()),
db: match config.db.as_ref() {
None => Some(Db {
engine: Some(DEF_ENGINE.to_string()),
url: Some(DEF_MONGODB_URL.to_string()),
database: Some(DEF_MONGODB_DB.to_string()),
pool_size: None,
path: Some(DEF_SQLITE_PATH.to_string()),
Some(db) => Some(Db {
engine: match db.engine.as_ref() {
None => Some(DEF_ENGINE.to_string()),
Some(engine) => match engine.as_str() {
DbEngine::MONGODB => Some(DbEngine::MONGODB.to_string()),
DbEngine::SQLITE => Some(DbEngine::SQLITE.to_string()),
_ => Some(DEF_ENGINE.to_string()),
mongodb: match db.mongodb.as_ref() {
None => Some(MongoDb {
Some(mongodb) => Some(MongoDb {
url: match mongodb.url.as_ref() {
None => Some(DEF_MONGODB_URL.to_string()),
Some(url) => Some(url.to_string()),
database: match mongodb.database.as_ref() {
None => Some(DEF_MONGODB_DB.to_string()),
Some(database) => Some(database.to_string()),
pool_size: mongodb.pool_size,
sqlite: match db.sqlite.as_ref() {
None => Some(Sqlite {
Some(sqlite) => Some(Sqlite {
path: match sqlite.path.as_ref() {
None => Some(DEF_SQLITE_PATH.to_string()),
Some(path) => Some(path.to_string()),
cache: match config.cache.as_ref() {
None => Some(Cache {
engine: Some(DEF_CACHE_ENGINE.to_string()),
device: Some(DEF_MEMORY_DEVICE),
device_route: Some(DEF_MEMORY_DEVICE_ROUTE),
network_route: Some(DEF_MEMORY_NETWORK_ROUTE),
Some(cache) => Some(Cache {
engine: match cache.engine.as_ref() {
None => Some(DEF_CACHE_ENGINE.to_string()),
CacheEngine::MEMORY => Some(CacheEngine::MEMORY.to_string()),
_ => Some(DEF_CACHE_ENGINE.to_string()),
memory: match cache.memory.as_ref() {
None => Some(MemoryCache {
Some(memory) => Some(MemoryCache {
device: match memory.device {
None | Some(0) => Some(DEF_MEMORY_DEVICE),
Some(v) => Some(v),
device_route: match memory.device_route {
None | Some(0) => Some(DEF_MEMORY_DEVICE_ROUTE),
network_route: match memory.network_route {
None | Some(0) => Some(DEF_MEMORY_NETWORK_ROUTE),
mq: match config.mq.as_ref() {
None => Some(Mq {
prefetch: Some(DEF_MQ_PREFETCH),
persistent: Some(DEF_MQ_PERSISTENT),
shared_prefix: Some(DEF_MQ_SHAREDPREFIX.to_string()),
Some(mq) => Some(Mq {
prefetch: match mq.prefetch {
None | Some(0) => Some(DEF_MQ_PREFETCH),
Some(prefetch) => Some(prefetch),
persistent: match mq.persistent {
None => Some(DEF_MQ_PERSISTENT),
Some(persistent) => Some(persistent),
shared_prefix: match mq.shared_prefix.as_ref() {
None => Some(DEF_MQ_SHAREDPREFIX.to_string()),
Some(shared_prefix) => Some(shared_prefix.to_string()),
mq_channels: match config.mq_channels.as_ref() {
None => Some(MqChannels {
url: Some(DEF_MQ_CHANNEL_URL.to_string()),
data: None,
Some(mq_channels) => Some(MqChannels {
unit: match mq_channels.unit.as_ref() {
None => Some(BrokerCtrl {
Some(channel) => Some(BrokerCtrl {
url: match channel.url.as_ref() {
None => Some(DEF_MQ_CHANNEL_URL.to_string()),
prefetch: match channel.prefetch {
None => Some(DEF_MQ_PREFETCH),
application: match mq_channels.application.as_ref() {
network: match mq_channels.network.as_ref() {
device: match mq_channels.device.as_ref() {
device_route: match mq_channels.device_route.as_ref() {
network_route: match mq_channels.network_route.as_ref() {
data: match mq_channels.data.as_ref() {
None => None,
Some(channel) => Some(BrokerData {
persistent: match channel.persistent {
api_scopes: match config.api_scopes.as_ref() {
None => Some(HashMap::new()),
Some(scopes) => Some(scopes.clone()),