1use std::{
2 collections::BTreeMap,
3 convert::{TryFrom, TryInto},
4};
5
6use serde_derive::{Deserialize, Serialize};
7use strum_macros::{EnumDiscriminants, EnumString};
8use thiserror::Error;
9use validator::Validate;
10
11use crate::db::models as db_models;
12
13pub mod backend;
14
15mod devices;
16pub use devices::*;
17
18mod global;
19pub use global::*;
20
21mod instance;
22pub use instance::*;
23
24mod layouts;
25pub use layouts::*;
26
27mod meta;
28pub use meta::*;
29
30mod users;
31pub use users::*;
32
33pub type Color = palette::rgb::LinSrgb<u8>;
34pub type Color16 = palette::rgb::LinSrgb<u16>;
35
36pub trait ServerConfig {
37 fn port(&self) -> u16;
38}
39
40fn default_true() -> bool {
41 true
42}
43
44fn default_false() -> bool {
45 false
46}
47
48#[derive(Debug, Clone, PartialEq, Deserialize)]
49pub struct Setting {
50 pub hyperion_inst: Option<i32>,
51 pub config: SettingData,
52 pub updated_at: chrono::DateTime<chrono::Utc>,
53}
54
55#[derive(Debug, Clone, PartialEq, EnumDiscriminants, Deserialize)]
56#[strum_discriminants(name(SettingKind), derive(EnumString))]
57pub enum SettingData {
58 BackgroundEffect(BackgroundEffect),
60 BlackBorderDetector(BlackBorderDetector),
61 BoblightServer(BoblightServer),
62 ColorAdjustment(ColorAdjustment),
63 Device(Device),
64 Effects(Effects),
65 FlatbuffersServer(FlatbuffersServer),
66 ForegroundEffect(ForegroundEffect),
67 Forwarder(Forwarder),
68 Framegrabber(Framegrabber),
69 General(General),
70 GrabberV4L2(GrabberV4L2),
71 InstanceCapture(InstanceCapture),
72 JsonServer(JsonServer),
73 LedConfig(LedConfig),
74 Leds(Leds),
75 Logger(Logger),
76 Network(Network),
77 ProtoServer(ProtoServer),
78 Smoothing(Smoothing),
79 WebConfig(WebConfig),
80 Hooks(Hooks),
82}
83
84impl Validate for SettingData {
85 fn validate(&self) -> Result<(), validator::ValidationErrors> {
86 match self {
87 SettingData::BackgroundEffect(setting) => setting.validate(),
88 SettingData::BlackBorderDetector(setting) => setting.validate(),
89 SettingData::BoblightServer(setting) => setting.validate(),
90 SettingData::ColorAdjustment(setting) => setting.validate(),
91 SettingData::Device(setting) => setting.validate(),
92 SettingData::Effects(setting) => setting.validate(),
93 SettingData::FlatbuffersServer(setting) => setting.validate(),
94 SettingData::ForegroundEffect(setting) => setting.validate(),
95 SettingData::Forwarder(setting) => setting.validate(),
96 SettingData::Framegrabber(setting) => setting.validate(),
97 SettingData::General(setting) => setting.validate(),
98 SettingData::GrabberV4L2(setting) => setting.validate(),
99 SettingData::InstanceCapture(setting) => setting.validate(),
100 SettingData::JsonServer(setting) => setting.validate(),
101 SettingData::LedConfig(setting) => setting.validate(),
102 SettingData::Leds(setting) => setting.validate(),
103 SettingData::Logger(setting) => setting.validate(),
104 SettingData::Network(setting) => setting.validate(),
105 SettingData::ProtoServer(setting) => setting.validate(),
106 SettingData::Smoothing(setting) => setting.validate(),
107 SettingData::WebConfig(setting) => setting.validate(),
108 SettingData::Hooks(setting) => setting.validate(),
109 }
110 }
111}
112
113#[derive(Debug, Error)]
114pub enum SettingErrorKind {
115 #[error(transparent)]
116 Serde(#[from] serde_json::Error),
117 #[error("error parsing date")]
118 Chrono(#[from] chrono::ParseError),
119 #[error(transparent)]
120 Validation(#[from] validator::ValidationErrors),
121 #[error("unknown setting type")]
122 UnknownType,
123}
124
125#[derive(Debug)]
126pub struct SettingError {
127 pub kind: SettingErrorKind,
128 pub setting: &'static str,
129 unknown: Option<String>,
130}
131
132impl SettingError {
133 pub fn new(kind: impl Into<SettingErrorKind>, setting: &'static str) -> Self {
134 Self {
135 kind: kind.into(),
136 setting,
137 unknown: None,
138 }
139 }
140
141 pub fn unknown(name: &str) -> Self {
142 Self {
143 kind: SettingErrorKind::UnknownType,
144 setting: "",
145 unknown: Some(name.to_owned()),
146 }
147 }
148}
149
150impl std::error::Error for SettingError {
151 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
152 self.kind.source()
153 }
154}
155
156impl std::fmt::Display for SettingError {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 match &self.unknown {
159 Some(setting) => write!(f, "`{}`: {}", setting, self.kind),
160 None => write!(f, "`{}`: {}", self.setting, self.kind),
161 }
162 }
163}
164
165macro_rules! convert_settings {
166 ($db:ident, $($name:literal => $variant:ident),*) => {
167 match $db.ty.as_str() {
168 $($name => {
169 let config = SettingData::$variant(serde_json::from_str(&$db.config).map_err(|err| {
170 SettingError::new(err, $name)
171 })?);
172
173 (
174 match config.validate() {
175 Ok(_) => Ok(config),
176 Err(err) => Err(SettingError::new(err, $name)),
177 }?,
178 chrono::DateTime::parse_from_rfc3339(&$db.updated_at).map_err(|err| {
179 SettingError::new(err, $name)
180 })?.with_timezone(&chrono::Utc)
181 )
182 },)*
183 other => {
184 return Err(SettingError::unknown(other));
185 }
186 }
187 };
188}
189
190impl TryFrom<db_models::DbSetting> for Setting {
191 type Error = SettingError;
192
193 fn try_from(db: db_models::DbSetting) -> Result<Self, Self::Error> {
194 let (config, updated_at) = convert_settings!(db,
195 "backgroundEffect" => BackgroundEffect,
196 "blackborderdetector" => BlackBorderDetector,
197 "boblightServer" => BoblightServer,
198 "color" => ColorAdjustment,
199 "device" => Device,
200 "effects" => Effects,
201 "flatbufServer" => FlatbuffersServer,
202 "foregroundEffect" => ForegroundEffect,
203 "forwarder" => Forwarder,
204 "framegrabber" => Framegrabber,
205 "general" => General,
206 "grabberV4L2" => GrabberV4L2,
207 "instCapture" => InstanceCapture,
208 "jsonServer" => JsonServer,
209 "ledConfig" => LedConfig,
210 "leds" => Leds,
211 "logger" => Logger,
212 "network" => Network,
213 "protoServer" => ProtoServer,
214 "smoothing" => Smoothing,
215 "webConfig" => WebConfig,
216 "hooks" => Hooks
217 );
218
219 Ok(Self {
220 hyperion_inst: db.hyperion_inst,
221 config,
222 updated_at,
223 })
224 }
225}
226
227fn default_none<T>() -> Option<T> {
228 None
229}
230
231#[derive(Debug, Error)]
232pub enum ConfigError {
233 #[error("i/o error")]
234 Io(#[from] std::io::Error),
235 #[error("error querying the database")]
236 Sqlx(#[from] sqlx::Error),
237 #[error("error loading instance")]
238 Instance(#[from] InstanceError),
239 #[error("error loading setting")]
240 Setting(#[from] SettingError),
241 #[error("error loading meta")]
242 Meta(#[from] MetaError),
243 #[error("error loading user")]
244 User(#[from] UserError),
245 #[error("missing hyperion_inst field on instance setting {0}")]
246 MissingHyperionInst(&'static str),
247 #[error("invalid TOML")]
248 Toml(#[from] toml::de::Error),
249 #[error("instance id must be an integer, got {0}")]
250 InvalidId(String),
251}
252
253#[derive(Debug, Clone, PartialEq)]
254pub struct Config {
255 pub instances: BTreeMap<i32, InstanceConfig>,
256 pub global: GlobalConfig,
257 meta: Vec<Meta>,
258 users: Vec<User>,
259}
260
261impl Config {
262 pub fn uuid(&self) -> uuid::Uuid {
263 self.meta.first().map(|meta| meta.uuid).unwrap_or_default()
265 }
266}