1#[allow(clippy::doc_markdown, clippy::too_many_arguments)]
7pub mod action {
8 include!(concat!(env!("OUT_DIR"), "/action/mod.rs"));
9}
10
11#[allow(clippy::doc_markdown, clippy::too_many_arguments)]
12pub mod policy {
13 include!(concat!(env!("OUT_DIR"), "/policy/mod.rs"));
14}
15
16#[allow(clippy::doc_markdown, clippy::too_many_arguments)]
17pub mod verdict {
18 include!(concat!(env!("OUT_DIR"), "/verdict/mod.rs"));
19}
20
21#[allow(clippy::doc_markdown, clippy::too_many_arguments)]
22pub mod audit {
23 include!(concat!(env!("OUT_DIR"), "/audit/mod.rs"));
24}
25
26#[allow(clippy::doc_markdown, clippy::too_many_arguments)]
27pub mod control {
28 include!(concat!(env!("OUT_DIR"), "/control/mod.rs"));
29}
30
31#[allow(clippy::doc_markdown, clippy::too_many_arguments)]
32pub mod provision {
33 include!(concat!(env!("OUT_DIR"), "/provision/mod.rs"));
34}
35
36pub fn empty_fields() -> serde_json::Value {
39 serde_json::Value::Object(serde_json::Map::new())
40}
41
42impl action::Action {
43 pub fn of(target: impl Into<String>, verb: action::Verb, resource: action::Resource) -> Self {
47 Self {
48 target: target.into(),
49 verb,
50 resource,
51 fields: empty_fields(),
52 }
53 }
54
55 #[must_use]
57 pub fn with_fields(mut self, fields: serde_json::Value) -> Self {
58 self.fields = fields;
59 self
60 }
61}
62
63impl action::Verb {
64 pub fn crud(kind: action::CrudKind) -> Self {
66 action::Verb::Crud(action::CrudVerb { kind })
67 }
68
69 pub fn action(id: impl Into<String>) -> Self {
71 action::Verb::Action(action::NamedVerb { id: id.into() })
72 }
73
74 pub fn parse(s: &str) -> Self {
81 match s.to_ascii_lowercase().as_str() {
82 "read" => Self::crud(action::CrudKind::Read),
83 "create" => Self::crud(action::CrudKind::Create),
84 "update" => Self::crud(action::CrudKind::Update),
85 "delete" => Self::crud(action::CrudKind::Delete),
86 _ => Self::action(s),
87 }
88 }
89}
90
91impl action::Resource {
92 pub fn of(path: impl Into<String>, kind: impl Into<String>) -> Self {
95 Self {
96 path: path.into(),
97 kind: kind.into(),
98 }
99 }
100}
101
102impl verdict::Verdict {
103 pub fn is_allow(&self) -> bool {
105 matches!(self, verdict::Verdict::Allow(_))
106 }
107
108 pub fn allow(obligations: Vec<verdict::Obligation>) -> Self {
111 verdict::Verdict::Allow(verdict::AllowVerdict { obligations })
112 }
113
114 pub fn inject(id: impl Into<String>) -> verdict::Obligation {
116 verdict::Obligation::InjectCredential(verdict::InjectCredentialObligation {
117 credential: verdict::CredentialRef { id: id.into() },
118 })
119 }
120
121 pub fn passthrough() -> verdict::Obligation {
123 verdict::Obligation::Passthrough(verdict::PassthroughObligation {})
124 }
125
126 pub fn deny(reason: verdict::DenyReason) -> Self {
128 verdict::Verdict::Deny(verdict::DenyVerdict { reason })
129 }
130}
131
132#[cfg(test)]
133#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
134mod tests {
135 use super::action::{Action, CrudKind, Resource, Verb};
136 use super::verdict::{DenyReason, Verdict};
137
138 #[test]
139 fn action_round_trips_through_json() {
140 let action = Action::of(
141 "github",
142 Verb::crud(CrudKind::Create),
143 Resource::of("repos/octocat/hello/pulls", "pull_request"),
144 )
145 .with_fields(serde_json::json!({ "base": "main" }));
146 let json = serde_json::to_string(&action).unwrap();
147 let back: Action = serde_json::from_str(&json).unwrap();
148 assert_eq!(action, back);
149 }
150
151 #[test]
152 fn verb_parse_shorthand_maps_crud_words_and_named_actions() {
153 assert_eq!(Verb::parse("read"), Verb::crud(CrudKind::Read));
154 assert_eq!(Verb::parse("DELETE"), Verb::crud(CrudKind::Delete));
155 assert_eq!(
156 Verb::parse("ec2:DescribeInstances"),
157 Verb::action("ec2:DescribeInstances")
158 );
159 }
160
161 #[test]
162 fn verb_union_supports_crud_and_named_action() {
163 let read = Verb::crud(CrudKind::Read);
164 let terminate = Verb::action("ec2:TerminateInstances");
165 assert_ne!(read, terminate);
166 let json = serde_json::to_string(&terminate).unwrap();
167 let back: Verb = serde_json::from_str(&json).unwrap();
168 assert_eq!(terminate, back);
169 }
170
171 #[test]
172 fn verdict_helpers_build_expected_variants() {
173 let allow = Verdict::allow(vec![Verdict::inject("gh")]);
174 assert!(allow.is_allow());
175
176 let passthrough = Verdict::allow(vec![Verdict::passthrough()]);
177 assert!(passthrough.is_allow());
178
179 let deny = Verdict::deny(DenyReason::NotAllowed);
180 assert!(!deny.is_allow());
181 }
182}