use crate::{Manifest, SecurityProfile};
use serde::Serialize;
use std::collections::BTreeMap;
#[derive(Debug, Clone, Serialize)]
pub struct Capability {
#[serde(rename = "$schema")]
pub schema: String,
pub identifier: String,
pub description: String,
pub windows: Vec<String>,
pub permissions: Vec<String>,
}
impl Capability {
pub fn from_manifest(manifest: &Manifest) -> Vec<Self> {
let mut by_profile: BTreeMap<SecurityProfile, Vec<String>> = BTreeMap::new();
for (window, security) in &manifest.security.windows {
by_profile
.entry(security.profile)
.or_default()
.push(window.clone());
}
by_profile
.into_iter()
.map(|(profile, mut windows)| {
windows.sort();
Self {
schema: "../gen/schemas/desktop-schema.json".to_owned(),
identifier: profile.identifier().to_owned(),
description: format!(
"{} Generated from app.toml — do not edit.",
profile.description()
),
windows,
permissions: profile
.permissions()
.iter()
.map(|permission| (*permission).to_owned())
.collect(),
}
})
.collect()
}
pub fn file_name(&self) -> String {
format!("{}.json", self.identifier)
}
}
impl PartialOrd for SecurityProfile {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SecurityProfile {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.identifier().cmp(other.identifier())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn manifest(windows: &str) -> Manifest {
let contents = format!(
r#"
[origin]
version = "0.1.0"
[product]
id = "dev.origin.demo"
name = "Demo"
version = "0.1.0"
{windows}
"#
);
toml::from_str(&contents).unwrap()
}
#[test]
fn windows_sharing_a_profile_share_one_capability_file() {
let manifest = manifest(
"\n[security.windows.main]\nprofile = \"standard-dashboard\"\n\
\n[security.windows.detail]\nprofile = \"standard-dashboard\"\n",
);
let capabilities = Capability::from_manifest(&manifest);
assert_eq!(capabilities.len(), 1);
assert_eq!(capabilities[0].windows, vec!["detail", "main"]);
}
#[test]
fn different_profiles_produce_separate_files() {
let manifest = manifest(
"\n[security.windows.main]\nprofile = \"standard-dashboard\"\n\
\n[security.windows.settings]\nprofile = \"account-settings\"\n",
);
let capabilities = Capability::from_manifest(&manifest);
assert_eq!(capabilities.len(), 2);
assert_eq!(capabilities[0].file_name(), "account-settings.json");
assert_eq!(capabilities[1].file_name(), "standard-dashboard.json");
}
#[test]
fn the_generated_file_says_it_is_generated() {
let manifest = manifest("\n[security.windows.main]\nprofile = \"readonly-dashboard\"\n");
let capability = &Capability::from_manifest(&manifest)[0];
assert!(
capability.description.contains("do not edit"),
"someone opening this file must see where it comes from"
);
}
#[test]
fn a_multi_window_product_gets_one_capability_per_profile() {
let manifest = manifest(
"\n[security.windows.main]\nprofile = \"standard-dashboard\"\n\
\n[security.windows.settings]\nprofile = \"account-settings\"\n\
\n[security.windows.quick_capture]\nprofile = \"readonly-dashboard\"\n\
\n[security.windows.workspace]\nprofile = \"local-workspace\"\n",
);
let capabilities = Capability::from_manifest(&manifest);
let files: Vec<String> = capabilities.iter().map(Capability::file_name).collect();
assert_eq!(
files,
vec![
"account-settings.json",
"local-workspace.json",
"readonly-dashboard.json",
"standard-dashboard.json",
]
);
let windows_of = |identifier: &str| {
capabilities
.iter()
.find(|capability| capability.identifier == identifier)
.map(|capability| capability.windows.clone())
.unwrap_or_default()
};
assert_eq!(windows_of("standard-dashboard"), vec!["main"]);
assert_eq!(windows_of("account-settings"), vec!["settings"]);
assert_eq!(windows_of("readonly-dashboard"), vec!["quick_capture"]);
assert_eq!(windows_of("local-workspace"), vec!["workspace"]);
}
}