Implemented reading/writing of json settings
This commit is contained in:
parent
ec70ed2c51
commit
260ecdc531
1119
Linux/Cargo.lock
generated
1119
Linux/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ edition = "2021"
|
|||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.86"
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
log = "0.4.21"
|
log = "0.4.21"
|
||||||
platform-dirs = "0.3.0"
|
platform-dirs = "0.3.0"
|
||||||
@ -12,11 +13,8 @@ regex = "1.10.5"
|
|||||||
relm4 = "0.8.1"
|
relm4 = "0.8.1"
|
||||||
relm4-icons = "0.8.3"
|
relm4-icons = "0.8.3"
|
||||||
rust-i18n = "3.0.1"
|
rust-i18n = "3.0.1"
|
||||||
serde = "1.0.203"
|
serde = { version = "1.0.203", features = ["derive"] }
|
||||||
|
serde_json = "1.0.117"
|
||||||
|
|
||||||
[dependencies.min-rs]
|
[dependencies.min-rs]
|
||||||
git = "https://github.com/MvRens/min-rs.git"
|
git = "https://github.com/MvRens/min-rs.git"
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
slint-build = "1.6"
|
|
||||||
walkdir = "2.5.0"
|
|
||||||
|
@ -3,6 +3,7 @@ _version: 2
|
|||||||
mainwindow:
|
mainwindow:
|
||||||
title:
|
title:
|
||||||
en: MassiveKnob
|
en: MassiveKnob
|
||||||
|
|
||||||
tab:
|
tab:
|
||||||
device:
|
device:
|
||||||
en: Device
|
en: Device
|
||||||
@ -14,3 +15,7 @@ mainwindow:
|
|||||||
en: Analog outputs
|
en: Analog outputs
|
||||||
digitaloutputs:
|
digitaloutputs:
|
||||||
en: Digital outputs
|
en: Digital outputs
|
||||||
|
|
||||||
|
deviceType:
|
||||||
|
label:
|
||||||
|
en: Type
|
@ -1,57 +0,0 @@
|
|||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::io::{Error, Read, Write};
|
|
||||||
use platform_dirs::AppDirs;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Config
|
|
||||||
{
|
|
||||||
root: PathBuf,
|
|
||||||
pub device_id: Option<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Config
|
|
||||||
{
|
|
||||||
pub fn new() -> Self
|
|
||||||
{
|
|
||||||
let appdirs = AppDirs::new(Some("massiveknob"), false).unwrap();
|
|
||||||
|
|
||||||
Self
|
|
||||||
{
|
|
||||||
root: appdirs.data_dir,
|
|
||||||
device_id: None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn get_reader(&self, name: &str) -> Option<impl Read>
|
|
||||||
{
|
|
||||||
let path = Path::join(&self.root, name);
|
|
||||||
if !path.exists()
|
|
||||||
{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
match std::fs::File::open(path)
|
|
||||||
{
|
|
||||||
Ok(v) => Some(v),
|
|
||||||
Err(_) => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn get_writer(&self, name: &str) -> Result<impl Write, Error>
|
|
||||||
{
|
|
||||||
let path = Path::join(&self.root, name);
|
|
||||||
if !path.exists()
|
|
||||||
{
|
|
||||||
match std::fs::create_dir_all(path.clone())
|
|
||||||
{
|
|
||||||
Ok(_v) => (),
|
|
||||||
Err(e) => return Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fs::File::create(path)
|
|
||||||
}
|
|
||||||
}
|
|
55
Linux/src/config/json.rs
Normal file
55
Linux/src/config/json.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use anyhow::Error;
|
||||||
|
|
||||||
|
use crate::util::option_result::OptionResult;
|
||||||
|
use super::{ConfigManager, ConfigName};
|
||||||
|
|
||||||
|
pub trait JsonConfigManager
|
||||||
|
{
|
||||||
|
fn read_json<T>(&self, name: &ConfigName) -> OptionResult<T, Error> where T : serde::de::DeserializeOwned;
|
||||||
|
fn write_json<T>(&self, name: &ConfigName, value: &T) -> Result<(), Error> where T : serde::ser::Serialize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl JsonConfigManager for ConfigManager
|
||||||
|
{
|
||||||
|
fn read_json<T>(&self, name: &ConfigName) -> OptionResult<T, Error> where T : serde::de::DeserializeOwned
|
||||||
|
{
|
||||||
|
match self.get_reader(&json_config_name(name))
|
||||||
|
{
|
||||||
|
OptionResult::None => OptionResult::None,
|
||||||
|
|
||||||
|
OptionResult::Some(reader) =>
|
||||||
|
{
|
||||||
|
match serde_json::from_reader(reader)
|
||||||
|
{
|
||||||
|
Ok(v) => OptionResult::Some(v),
|
||||||
|
Err(e) => OptionResult::Err(e.into())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
OptionResult::Err(e) => OptionResult::Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn write_json<T>(&self, name: &ConfigName, value: &T) -> Result<(), Error> where T : serde::ser::Serialize
|
||||||
|
{
|
||||||
|
match self.get_writer(&json_config_name(name))
|
||||||
|
{
|
||||||
|
Ok(writer) => match serde_json::to_writer(writer, value)
|
||||||
|
{
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(e.into())
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(e) => Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn json_config_name(name: &ConfigName) -> ConfigName
|
||||||
|
{
|
||||||
|
ConfigName::new(format!("{}.json", name.as_str()).as_str())
|
||||||
|
}
|
75
Linux/src/config/mod.rs
Normal file
75
Linux/src/config/mod.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::io::{Error, Read, Write};
|
||||||
|
use platform_dirs::AppDirs;
|
||||||
|
|
||||||
|
use crate::util::option_result::OptionResult;
|
||||||
|
use crate::util::validated_string::{ValidatedString, ValidatedStringPattern};
|
||||||
|
|
||||||
|
|
||||||
|
pub mod json;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ConfigManager
|
||||||
|
{
|
||||||
|
root: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ConfigManager
|
||||||
|
{
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
let appdirs = AppDirs::new(Some("massiveknob"), false).unwrap();
|
||||||
|
|
||||||
|
Self
|
||||||
|
{
|
||||||
|
root: appdirs.data_dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get_reader(&self, name: &ConfigName) -> OptionResult<impl Read, anyhow::Error>
|
||||||
|
{
|
||||||
|
let path = Path::join(&self.root, name.as_str());
|
||||||
|
if !path.exists()
|
||||||
|
{
|
||||||
|
return OptionResult::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match std::fs::File::open(path)
|
||||||
|
{
|
||||||
|
Ok(v) => OptionResult::Some(v),
|
||||||
|
Err(e) => OptionResult::Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get_writer(&self, name: &ConfigName) -> Result<impl Write, Error>
|
||||||
|
{
|
||||||
|
let path = Path::join(&self.root, name.as_str());
|
||||||
|
if !path.exists()
|
||||||
|
{
|
||||||
|
match std::fs::create_dir_all(path.clone())
|
||||||
|
{
|
||||||
|
Ok(_v) => (),
|
||||||
|
Err(e) => return Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::File::create(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pub type ConfigName = ValidatedString<ConfigNamePattern>;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct ConfigNamePattern;
|
||||||
|
|
||||||
|
impl ValidatedStringPattern for ConfigNamePattern
|
||||||
|
{
|
||||||
|
fn pattern() -> &'static str { r"^[a-zA-Z0-9\.\-_]+$" }
|
||||||
|
}
|
@ -34,6 +34,13 @@ pub enum MainWindowMsg
|
|||||||
|
|
||||||
pub struct MainWindowWidgets
|
pub struct MainWindowWidgets
|
||||||
{
|
{
|
||||||
|
device: MainWindowDeviceWidgets
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct MainWindowDeviceWidgets
|
||||||
|
{
|
||||||
|
devices_combobox: gtk::ComboBoxText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -60,18 +67,8 @@ impl SimpleComponent for MainWindow
|
|||||||
fn init(_data: Self::Init, window: Self::Root, _sender: ComponentSender<Self>, ) -> ComponentParts<Self>
|
fn init(_data: Self::Init, window: Self::Root, _sender: ComponentSender<Self>, ) -> ComponentParts<Self>
|
||||||
{
|
{
|
||||||
let model = MainWindow {};
|
let model = MainWindow {};
|
||||||
|
let widgets = Self::init_ui(&window);
|
||||||
|
|
||||||
let tabs = gtk::Notebook::builder().build();
|
|
||||||
window.set_child(Some(&tabs));
|
|
||||||
|
|
||||||
add_box_tab(&tabs, "mainwindow.tab.device");
|
|
||||||
add_box_tab(&tabs, "mainwindow.tab.analoginputs");
|
|
||||||
add_box_tab(&tabs, "mainwindow.tab.digitalinputs");
|
|
||||||
//add_box_tab(&tabs, "mainwindow.tab.analogoutputs");
|
|
||||||
//add_box_tab(&tabs, "mainwindow.tab.digitaloutputs");
|
|
||||||
|
|
||||||
|
|
||||||
let widgets = MainWindowWidgets {};
|
|
||||||
ComponentParts { model, widgets }
|
ComponentParts { model, widgets }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,10 +82,65 @@ impl SimpleComponent for MainWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn add_box_tab(notebook: >k::Notebook, title_key: &str) -> gtk::Box
|
|
||||||
|
impl MainWindow
|
||||||
|
{
|
||||||
|
fn init_ui(window: >k::Window) -> MainWindowWidgets
|
||||||
|
{
|
||||||
|
let tabs = gtk::Notebook::builder().build();
|
||||||
|
window.set_child(Some(&tabs));
|
||||||
|
|
||||||
|
MainWindowWidgets
|
||||||
|
{
|
||||||
|
device: Self::init_device_tab(&tabs)
|
||||||
|
//Self::new_box_tab(&tabs, "mainwindow.tab.analoginputs");
|
||||||
|
//Self::new_box_tab(&tabs, "mainwindow.tab.digitalinputs");
|
||||||
|
//Self::add_box_tab(&tabs, "mainwindow.tab.analogoutputs");
|
||||||
|
//Self::add_box_tab(&tabs, "mainwindow.tab.digitaloutputs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn init_device_tab(tabs: >k::Notebook) -> MainWindowDeviceWidgets
|
||||||
|
{
|
||||||
|
let tab = Self::new_box_tab(&tabs, "mainwindow.tab.device");
|
||||||
|
|
||||||
|
let label = gtk::Label::builder()
|
||||||
|
.label(t!("mainwindow.deviceType.label"))
|
||||||
|
.halign(gtk::Align::Start)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
tab.append(&label);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let devices_combobox = gtk::ComboBoxText::builder()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
tab.append(&devices_combobox);
|
||||||
|
|
||||||
|
|
||||||
|
// TEMP
|
||||||
|
devices_combobox.append_text("Test");
|
||||||
|
devices_combobox.append_text("Test 2");
|
||||||
|
|
||||||
|
|
||||||
|
MainWindowDeviceWidgets
|
||||||
|
{
|
||||||
|
devices_combobox
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn new_box_tab(notebook: >k::Notebook, title_key: &str) -> gtk::Box
|
||||||
{
|
{
|
||||||
let tab = gtk::Box::builder()
|
let tab = gtk::Box::builder()
|
||||||
.orientation(gtk::Orientation::Vertical)
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
.spacing(8)
|
||||||
|
.margin_start(8)
|
||||||
|
.margin_end(8)
|
||||||
|
.margin_top(8)
|
||||||
|
.margin_bottom(8)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let tab_label = gtk::Label::builder()
|
let tab_label = gtk::Label::builder()
|
||||||
@ -99,3 +151,4 @@ fn add_box_tab(notebook: >k::Notebook, title_key: &str) -> gtk::Box
|
|||||||
|
|
||||||
tab
|
tab
|
||||||
}
|
}
|
||||||
|
}
|
@ -1,18 +1,25 @@
|
|||||||
use crate::actions;
|
use crate::actions;
|
||||||
use crate::actions::MkAction;
|
use crate::actions::MkAction;
|
||||||
use crate::config::Config;
|
use crate::config::json::JsonConfigManager;
|
||||||
|
use crate::config::{ConfigManager, ConfigName};
|
||||||
use crate::devices;
|
use crate::devices;
|
||||||
use crate::devices::MkDevice;
|
use crate::devices::MkDevice;
|
||||||
use crate::registry::MkRegistry;
|
use crate::registry::MkRegistry;
|
||||||
use crate::util::unique_id::UniqueId;
|
use crate::util::unique_id::UniqueId;
|
||||||
|
|
||||||
|
|
||||||
|
mod settings;
|
||||||
|
|
||||||
|
|
||||||
pub struct Orchestrator
|
pub struct Orchestrator
|
||||||
{
|
{
|
||||||
config: Config,
|
config_manager: ConfigManager,
|
||||||
|
|
||||||
device_registry: MkRegistry<MkDevice>,
|
device_registry: MkRegistry<MkDevice>,
|
||||||
action_registry: MkRegistry<MkAction>
|
action_registry: MkRegistry<MkAction>,
|
||||||
|
|
||||||
|
settings_name: ConfigName,
|
||||||
|
settings: settings::Settings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -20,8 +27,14 @@ impl Orchestrator
|
|||||||
{
|
{
|
||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
let config = Config::new();
|
let config_manager = ConfigManager::new();
|
||||||
//config.get_reader(name)
|
|
||||||
|
let settings_name = ConfigName::new("settings");
|
||||||
|
let settings = match config_manager.read_json(&settings_name).expect("Error reading settings")
|
||||||
|
{
|
||||||
|
None => settings::Settings::new(),
|
||||||
|
Some(v) => v
|
||||||
|
};
|
||||||
|
|
||||||
let mut device_registry = MkRegistry::new();
|
let mut device_registry = MkRegistry::new();
|
||||||
let mut action_registry = MkRegistry::new();
|
let mut action_registry = MkRegistry::new();
|
||||||
@ -31,23 +44,41 @@ impl Orchestrator
|
|||||||
|
|
||||||
Self
|
Self
|
||||||
{
|
{
|
||||||
config,
|
config_manager,
|
||||||
|
|
||||||
device_registry,
|
device_registry,
|
||||||
action_registry
|
action_registry,
|
||||||
|
|
||||||
|
settings_name,
|
||||||
|
settings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn current_device(&self) -> Option<&MkDevice>
|
pub fn current_device(&self) -> Option<&MkDevice>
|
||||||
{
|
{
|
||||||
let Some(device_id) = &self.config.device_id else { return None };
|
let Some(device_id) = &self.settings.device_id else { return None };
|
||||||
self.device_registry.by_id(UniqueId::new(device_id.as_str()))
|
self.device_registry.by_id(UniqueId::new(device_id.as_str()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn set_current_device_id(&self, id: &str)
|
pub fn set_current_device_id(&mut self, id: &str)
|
||||||
{
|
{
|
||||||
// TODO if changed, unload old device, activate new
|
let new_id = Some(String::from(id));
|
||||||
todo!("Store in config");
|
if new_id == self.settings.device_id { return; }
|
||||||
|
|
||||||
|
self.settings.device_id = new_id;
|
||||||
|
self.store_settings();
|
||||||
|
|
||||||
|
// TODO unload old device, activate new
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn store_settings(&self)
|
||||||
|
{
|
||||||
|
if let Err(e) = self.config_manager.write_json(&self.settings_name, &self.settings)
|
||||||
|
{
|
||||||
|
log::error!("Error writing settings: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
19
Linux/src/orchestrator/settings.rs
Normal file
19
Linux/src/orchestrator/settings.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Settings
|
||||||
|
{
|
||||||
|
pub device_id: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Settings
|
||||||
|
{
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self
|
||||||
|
{
|
||||||
|
device_id: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use log::info;
|
use log;
|
||||||
|
|
||||||
use crate::util::unique_id::UniqueId;
|
use crate::util::unique_id::UniqueId;
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ impl<'a, T> MkRegistry<T> where T: RegistryItem
|
|||||||
{
|
{
|
||||||
let device_id = device.unique_id();
|
let device_id = device.unique_id();
|
||||||
|
|
||||||
info!("Registered device: [{}] {}", device_id.as_str(), device.name());
|
log::debug!("Registered device: [{}] {}", device_id.as_str(), device.name());
|
||||||
self.items.insert(String::from(device_id.as_str()), device);
|
self.items.insert(String::from(device_id.as_str()), device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1 +1,3 @@
|
|||||||
pub mod unique_id;
|
pub mod unique_id;
|
||||||
|
pub mod option_result;
|
||||||
|
pub mod validated_string;
|
||||||
|
33
Linux/src/util/option_result.rs
Normal file
33
Linux/src/util/option_result.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
|
||||||
|
pub enum OptionResult<T, E: Display>
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Some(T),
|
||||||
|
Err(E),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T, E: Display> OptionResult<T, E>
|
||||||
|
{
|
||||||
|
pub fn unwrap(self) -> Option<T>
|
||||||
|
{
|
||||||
|
match self
|
||||||
|
{
|
||||||
|
OptionResult::None => None,
|
||||||
|
OptionResult::Some(v) => Some(v),
|
||||||
|
OptionResult::Err(e) => panic!("called `OptionResult::unwrap()` on an `Err` value: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expect(self, msg: &str) -> Option<T>
|
||||||
|
{
|
||||||
|
match self
|
||||||
|
{
|
||||||
|
OptionResult::None => None,
|
||||||
|
OptionResult::Some(v) => Some(v),
|
||||||
|
OptionResult::Err(e) => panic!("{msg}: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +1,12 @@
|
|||||||
use regex::Regex;
|
use super::validated_string::{ValidatedString, ValidatedStringPattern};
|
||||||
|
|
||||||
|
|
||||||
pub struct UniqueId
|
pub type UniqueId = ValidatedString<UniqueIdPattern>;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct UniqueIdPattern;
|
||||||
|
|
||||||
|
impl ValidatedStringPattern for UniqueIdPattern
|
||||||
{
|
{
|
||||||
inner: String,
|
fn pattern() -> &'static str { r"^[a-zA-Z0-9\.\-_]+$" }
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl UniqueId
|
|
||||||
{
|
|
||||||
pub fn new(id: &str) -> Self
|
|
||||||
{
|
|
||||||
assert!(is_valid_unique_id(id), "Id '{id}' has invalid characters");
|
|
||||||
UniqueId { inner: id.to_string() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_str(&self) -> &str
|
|
||||||
{
|
|
||||||
self.inner.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn is_valid_unique_id(id: &str) -> bool
|
|
||||||
{
|
|
||||||
let re = Regex::new(r"^[a-zA-Z0-9\.\-_]+$").unwrap();
|
|
||||||
re.is_match(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Clone for UniqueId
|
|
||||||
{
|
|
||||||
fn clone(&self) -> Self
|
|
||||||
{
|
|
||||||
Self { inner: self.inner.clone() }
|
|
||||||
}
|
|
||||||
}
|
}
|
55
Linux/src/util/validated_string.rs
Normal file
55
Linux/src/util/validated_string.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
|
||||||
|
/// A string which must conform to the specified regex pattern,
|
||||||
|
/// otherwise it will panic by design. Intended for code validation,
|
||||||
|
/// not for runtime input validation.
|
||||||
|
pub struct ValidatedString<T: ValidatedStringPattern>
|
||||||
|
{
|
||||||
|
inner: String,
|
||||||
|
|
||||||
|
// Satisfy the compiler's demand to use T
|
||||||
|
_phantom: std::marker::PhantomData<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T: ValidatedStringPattern> ValidatedString<T>
|
||||||
|
{
|
||||||
|
pub fn new(value: &str) -> Self
|
||||||
|
{
|
||||||
|
let pattern = Regex::new(T::pattern()).unwrap();
|
||||||
|
assert!(pattern.is_match(value), "Value '{value}' has invalid characters");
|
||||||
|
|
||||||
|
Self
|
||||||
|
{
|
||||||
|
inner: value.to_string(),
|
||||||
|
_phantom: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str
|
||||||
|
{
|
||||||
|
self.inner.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T: ValidatedStringPattern> Clone for ValidatedString<T>
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self
|
||||||
|
{
|
||||||
|
Self
|
||||||
|
{
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
_phantom: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub trait ValidatedStringPattern
|
||||||
|
{
|
||||||
|
fn pattern() -> &'static str;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user