greentic_bundle/access/
mod.rs1pub mod edit;
2pub mod eval;
3pub mod gmap;
4pub mod parse;
5
6use std::path::Path;
7
8use anyhow::{Context, Result};
9use serde::Serialize;
10
11pub use edit::upsert_policy;
12pub use eval::{MatchDecision, eval_policy, eval_with_overlay};
13pub use gmap::{GmapMutation, GmapTarget};
14pub use parse::{GmapPath, GmapRule, Policy, parse_file, parse_path, parse_rule_line, parse_str};
15
16#[derive(Debug, Serialize)]
17pub struct AccessMutationPreview {
18 pub gmap_path: String,
19 pub rule_path: String,
20 pub policy: String,
21 pub writes: Vec<String>,
22}
23
24pub fn mutate_access(
25 root: &Path,
26 target: &GmapTarget,
27 mutation: &GmapMutation,
28 dry_run: bool,
29) -> Result<AccessMutationPreview> {
30 let gmap_path = crate::project::gmap_path(root, target);
31 let resolved_writes =
32 crate::project::resolved_output_paths(root, &target.tenant, target.team.as_deref());
33
34 let preview = AccessMutationPreview {
35 gmap_path: relative_display(root, &gmap_path),
36 rule_path: mutation.rule_path.clone(),
37 policy: mutation.policy.to_string(),
38 writes: std::iter::once(relative_display(root, &gmap_path))
39 .chain(
40 resolved_writes
41 .iter()
42 .map(|path| relative_display(root, path)),
43 )
44 .collect(),
45 };
46
47 if dry_run {
48 return Ok(preview);
49 }
50
51 crate::project::ensure_layout(root)?;
52 if let Some(team) = &target.team {
53 crate::project::ensure_team(root, &target.tenant, team)?;
54 } else {
55 crate::project::ensure_tenant(root, &target.tenant)?;
56 }
57 upsert_policy(&gmap_path, &mutation.rule_path, mutation.policy.clone())
58 .with_context(|| format!("update gmap {}", gmap_path.display()))?;
59 crate::project::sync_project(root)?;
60 Ok(preview)
61}
62
63fn relative_display(root: &Path, path: &Path) -> String {
64 path.strip_prefix(root)
65 .unwrap_or(path)
66 .display()
67 .to_string()
68}
69
70pub const ACCESS_LAYOUT_HINT: &str = "tenants/<tenant>/tenant.gmap";