hyperion/
models.rs

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    // hyperion.ng settings
59    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    // hyperion.rs settings
81    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        // There should always be a meta uuid
264        self.meta.first().map(|meta| meta.uuid).unwrap_or_default()
265    }
266}