openlogi_core/binding/
application_target.rs1use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)]
8pub enum ApplicationTargetError {
9 #[error("application path must not be empty")]
11 EmptyPath,
12}
13
14#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[serde(try_from = "ApplicationTargetWire", into = "ApplicationTargetWire")]
17pub struct ApplicationTarget {
18 path: String,
19 display_name: String,
20}
21
22impl ApplicationTarget {
23 pub fn new(
25 path: impl Into<String>,
26 display_name: impl Into<String>,
27 ) -> Result<Self, ApplicationTargetError> {
28 let path = path.into().trim().to_string();
29 if path.is_empty() {
30 return Err(ApplicationTargetError::EmptyPath);
31 }
32 let requested_name = display_name.into();
33 let display_name = if requested_name.trim().is_empty() {
34 std::path::Path::new(&path)
35 .file_stem()
36 .and_then(std::ffi::OsStr::to_str)
37 .filter(|name| !name.is_empty())
38 .unwrap_or(path.as_str())
39 .to_string()
40 } else {
41 requested_name.trim().to_string()
42 };
43 Ok(Self { path, display_name })
44 }
45
46 #[must_use]
49 pub fn path(&self) -> &str {
50 &self.path
51 }
52
53 #[must_use]
55 pub fn display_name(&self) -> &str {
56 &self.display_name
57 }
58}
59
60#[derive(Clone, Debug, Serialize, Deserialize)]
61struct ApplicationTargetWire {
62 path: String,
63 #[serde(default)]
64 display_name: String,
65}
66
67impl TryFrom<ApplicationTargetWire> for ApplicationTarget {
68 type Error = ApplicationTargetError;
69
70 fn try_from(target: ApplicationTargetWire) -> Result<Self, Self::Error> {
71 Self::new(target.path, target.display_name)
72 }
73}
74
75impl From<ApplicationTarget> for ApplicationTargetWire {
76 fn from(target: ApplicationTarget) -> Self {
77 Self {
78 path: target.path,
79 display_name: target.display_name,
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn validates_and_derives_display_name() {
90 let target = ApplicationTarget::new("/Applications/Safari.app", "")
91 .unwrap_or_else(|error| panic!("valid target failed: {error}"));
92 assert_eq!(target.path(), "/Applications/Safari.app");
93 assert_eq!(target.display_name(), "Safari");
94 assert_eq!(
95 ApplicationTarget::new(" ", "Safari"),
96 Err(ApplicationTargetError::EmptyPath)
97 );
98 }
99
100 #[test]
101 fn roundtrips_as_a_human_readable_target_table() {
102 let target = ApplicationTarget::new("https://example.test", "Example")
103 .unwrap_or_else(|error| panic!("valid target failed: {error}"));
104 let encoded = toml::to_string(&target)
105 .unwrap_or_else(|error| panic!("target serialization failed: {error}"));
106 assert!(encoded.contains("path = \"https://example.test\""));
107 assert_eq!(toml::from_str::<ApplicationTarget>(&encoded), Ok(target));
108 }
109}