use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct DevelopConfig {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub watch: Vec<WatchRule>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct WatchRule {
pub path: String,
pub action: WatchAction,
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ignore: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub include: Vec<String>,
#[serde(default)]
pub initial_sync: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub exec: Option<WatchExec>,
#[serde(flatten, default, skip_serializing_if = "indexmap::IndexMap::is_empty")]
pub unknown: indexmap::IndexMap<String, serde_yaml::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum WatchAction {
#[default]
Sync,
Rebuild,
Restart,
SyncAndRestart,
SyncAndExec,
}
impl WatchAction {
pub fn as_token(&self) -> &'static str {
match self {
WatchAction::Sync => "sync",
WatchAction::Rebuild => "rebuild",
WatchAction::Restart => "restart",
WatchAction::SyncAndRestart => "sync+restart",
WatchAction::SyncAndExec => "sync+exec",
}
}
pub fn requires_target(&self) -> bool {
matches!(
self,
WatchAction::Sync | WatchAction::SyncAndRestart | WatchAction::SyncAndExec
)
}
}
impl Serialize for WatchAction {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.as_token())
}
}
impl<'de> Deserialize<'de> for WatchAction {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
match s.as_str() {
"sync" => Ok(WatchAction::Sync),
"rebuild" => Ok(WatchAction::Rebuild),
"restart" => Ok(WatchAction::Restart),
"sync+restart" => Ok(WatchAction::SyncAndRestart),
"sync+exec" => Ok(WatchAction::SyncAndExec),
other => Err(serde::de::Error::custom(format!(
"unknown watch action: {other}"
))),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WatchExec {
pub command: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn watch_action_sync() {
let a: WatchAction = serde_yaml::from_str("\"sync\"").unwrap();
assert_eq!(a, WatchAction::Sync);
}
#[test]
fn watch_action_rebuild() {
let a: WatchAction = serde_yaml::from_str("\"rebuild\"").unwrap();
assert_eq!(a, WatchAction::Rebuild);
}
#[test]
fn watch_action_restart() {
let a: WatchAction = serde_yaml::from_str("\"restart\"").unwrap();
assert_eq!(a, WatchAction::Restart);
}
#[test]
fn watch_action_sync_and_restart() {
let a: WatchAction = serde_yaml::from_str("\"sync+restart\"").unwrap();
assert_eq!(a, WatchAction::SyncAndRestart);
}
#[test]
fn watch_action_sync_and_exec() {
let a: WatchAction = serde_yaml::from_str("\"sync+exec\"").unwrap();
assert_eq!(a, WatchAction::SyncAndExec);
}
#[test]
fn watch_action_unknown_is_error() {
assert!(serde_yaml::from_str::<WatchAction>("\"deploy\"").is_err());
}
#[test]
fn watch_action_serializes_lowercase_token() {
assert_eq!(
serde_yaml::to_string(&WatchAction::Sync).unwrap().trim(),
"sync"
);
assert_eq!(
serde_yaml::to_string(&WatchAction::SyncAndRestart)
.unwrap()
.trim(),
"sync+restart"
);
assert_eq!(
serde_yaml::to_string(&WatchAction::SyncAndExec)
.unwrap()
.trim(),
"sync+exec"
);
}
#[test]
fn watch_action_round_trips_through_config() {
for action in [
WatchAction::Sync,
WatchAction::Rebuild,
WatchAction::Restart,
WatchAction::SyncAndRestart,
WatchAction::SyncAndExec,
] {
let rendered = serde_yaml::to_string(&action).unwrap();
let parsed: WatchAction = serde_yaml::from_str(&rendered).unwrap();
assert_eq!(parsed, action);
}
}
#[test]
fn watch_action_requires_target_matches_sync_family() {
assert!(WatchAction::Sync.requires_target());
assert!(WatchAction::SyncAndRestart.requires_target());
assert!(WatchAction::SyncAndExec.requires_target());
assert!(!WatchAction::Rebuild.requires_target());
assert!(!WatchAction::Restart.requires_target());
}
}