origin_manifest/
security.rs1use crate::{Manifest, SecurityProfile};
2use serde::Serialize;
3use std::collections::BTreeMap;
4
5#[derive(Debug, Clone, Serialize)]
7pub struct Capability {
8 #[serde(rename = "$schema")]
9 pub schema: String,
10 pub identifier: String,
11 pub description: String,
12 pub windows: Vec<String>,
13 pub permissions: Vec<String>,
14}
15
16impl Capability {
17 pub fn from_manifest(manifest: &Manifest) -> Vec<Self> {
23 let mut by_profile: BTreeMap<SecurityProfile, Vec<String>> = BTreeMap::new();
24
25 for (window, security) in &manifest.security.windows {
26 by_profile
27 .entry(security.profile)
28 .or_default()
29 .push(window.clone());
30 }
31
32 by_profile
33 .into_iter()
34 .map(|(profile, mut windows)| {
35 windows.sort();
36 Self {
37 schema: "../gen/schemas/desktop-schema.json".to_owned(),
38 identifier: profile.identifier().to_owned(),
39 description: format!(
40 "{} Generated from app.toml — do not edit.",
41 profile.description()
42 ),
43 windows,
44 permissions: profile
45 .permissions()
46 .iter()
47 .map(|permission| (*permission).to_owned())
48 .collect(),
49 }
50 })
51 .collect()
52 }
53
54 pub fn file_name(&self) -> String {
56 format!("{}.json", self.identifier)
57 }
58}
59
60impl PartialOrd for SecurityProfile {
62 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
63 Some(self.cmp(other))
64 }
65}
66
67impl Ord for SecurityProfile {
68 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
69 self.identifier().cmp(other.identifier())
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 fn manifest(windows: &str) -> Manifest {
78 let contents = format!(
79 r#"
80[origin]
81version = "0.1.0"
82
83[product]
84id = "dev.origin.demo"
85name = "Demo"
86version = "0.1.0"
87{windows}
88"#
89 );
90 toml::from_str(&contents).unwrap()
91 }
92
93 #[test]
94 fn windows_sharing_a_profile_share_one_capability_file() {
95 let manifest = manifest(
96 "\n[security.windows.main]\nprofile = \"standard-dashboard\"\n\
97 \n[security.windows.detail]\nprofile = \"standard-dashboard\"\n",
98 );
99
100 let capabilities = Capability::from_manifest(&manifest);
101
102 assert_eq!(capabilities.len(), 1);
103 assert_eq!(capabilities[0].windows, vec!["detail", "main"]);
104 }
105
106 #[test]
107 fn different_profiles_produce_separate_files() {
108 let manifest = manifest(
109 "\n[security.windows.main]\nprofile = \"standard-dashboard\"\n\
110 \n[security.windows.settings]\nprofile = \"account-settings\"\n",
111 );
112
113 let capabilities = Capability::from_manifest(&manifest);
114
115 assert_eq!(capabilities.len(), 2);
116 assert_eq!(capabilities[0].file_name(), "account-settings.json");
117 assert_eq!(capabilities[1].file_name(), "standard-dashboard.json");
118 }
119
120 #[test]
121 fn the_generated_file_says_it_is_generated() {
122 let manifest = manifest("\n[security.windows.main]\nprofile = \"readonly-dashboard\"\n");
123
124 let capability = &Capability::from_manifest(&manifest)[0];
125
126 assert!(
127 capability.description.contains("do not edit"),
128 "someone opening this file must see where it comes from"
129 );
130 }
131
132 #[test]
133 fn a_multi_window_product_gets_one_capability_per_profile() {
134 let manifest = manifest(
137 "\n[security.windows.main]\nprofile = \"standard-dashboard\"\n\
138 \n[security.windows.settings]\nprofile = \"account-settings\"\n\
139 \n[security.windows.quick_capture]\nprofile = \"readonly-dashboard\"\n\
140 \n[security.windows.workspace]\nprofile = \"local-workspace\"\n",
141 );
142
143 let capabilities = Capability::from_manifest(&manifest);
144 let files: Vec<String> = capabilities.iter().map(Capability::file_name).collect();
145
146 assert_eq!(
147 files,
148 vec![
149 "account-settings.json",
150 "local-workspace.json",
151 "readonly-dashboard.json",
152 "standard-dashboard.json",
153 ]
154 );
155
156 let windows_of = |identifier: &str| {
157 capabilities
158 .iter()
159 .find(|capability| capability.identifier == identifier)
160 .map(|capability| capability.windows.clone())
161 .unwrap_or_default()
162 };
163
164 assert_eq!(windows_of("standard-dashboard"), vec!["main"]);
165 assert_eq!(windows_of("account-settings"), vec!["settings"]);
166 assert_eq!(windows_of("readonly-dashboard"), vec!["quick_capture"]);
167 assert_eq!(windows_of("local-workspace"), vec!["workspace"]);
168 }
169}