96 lines
2.7 KiB
Rust
96 lines
2.7 KiB
Rust
//! Policy decisions. `Decision` can only be constructed in this module.
|
|
//!
|
|
//! Code outside this module cannot build a `Decision` with a struct literal, because its fields
|
|
//! are private:
|
|
//!
|
|
//! ```compile_fail
|
|
//! let request = proto::ToolRequest {
|
|
//! session: proto::SessionId::new("s1").unwrap(),
|
|
//! call: proto::CallId(1),
|
|
//! tool: "read_file".to_string(),
|
|
//! arguments: "{}".to_string(),
|
|
//! };
|
|
//! let _ = brokerd::policy::Decision { request, grant: "g".to_string() };
|
|
//! ```
|
|
//!
|
|
//! Nor with the constructor, because it is private to this module:
|
|
//!
|
|
//! ```compile_fail
|
|
//! let request = proto::ToolRequest {
|
|
//! session: proto::SessionId::new("s1").unwrap(),
|
|
//! call: proto::CallId(1),
|
|
//! tool: "read_file".to_string(),
|
|
//! arguments: "{}".to_string(),
|
|
//! };
|
|
//! let _ = brokerd::policy::Decision::new(request, "g".to_string());
|
|
//! ```
|
|
//!
|
|
//! The same setup compiles when it goes through `decide`, which proves the two examples above
|
|
//! fail because of `Decision` and not because of a mistake in the setup:
|
|
//!
|
|
//! ```
|
|
//! let request = proto::ToolRequest {
|
|
//! session: proto::SessionId::new("s1").unwrap(),
|
|
//! call: proto::CallId(1),
|
|
//! tool: "read_file".to_string(),
|
|
//! arguments: "{}".to_string(),
|
|
//! };
|
|
//! assert_eq!(brokerd::policy::decide(request).unwrap_err(), proto::DenyReason::NoGrant);
|
|
//! ```
|
|
|
|
use proto::{DenyReason, ToolRequest};
|
|
|
|
#[derive(Debug)]
|
|
pub struct Decision {
|
|
request: ToolRequest,
|
|
grant: String,
|
|
}
|
|
|
|
impl Decision {
|
|
#[cfg_attr(not(test), expect(dead_code, reason = "grant matching arrives in M3"))]
|
|
fn new(request: ToolRequest, grant: String) -> Self {
|
|
Decision { request, grant }
|
|
}
|
|
|
|
pub fn request(&self) -> &ToolRequest {
|
|
&self.request
|
|
}
|
|
|
|
pub fn grant(&self) -> &str {
|
|
&self.grant
|
|
}
|
|
}
|
|
|
|
/// Until M3 there are no grants, so every request is denied with DenyReason::NoGrant.
|
|
pub fn decide(request: ToolRequest) -> Result<Decision, DenyReason> {
|
|
let _ = request;
|
|
Err(DenyReason::NoGrant)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use proto::{CallId, SessionId};
|
|
|
|
fn request() -> ToolRequest {
|
|
ToolRequest {
|
|
session: SessionId::new("s1").unwrap(),
|
|
call: CallId(1),
|
|
tool: "read_file".to_string(),
|
|
arguments: "{}".to_string(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn no_grants_means_deny() {
|
|
assert_eq!(decide(request()).unwrap_err(), DenyReason::NoGrant);
|
|
}
|
|
|
|
#[test]
|
|
fn decision_exposes_request_and_grant() {
|
|
let d = Decision::new(request(), "g1".to_string());
|
|
assert_eq!(d.request().tool, "read_file");
|
|
assert_eq!(d.grant(), "g1");
|
|
}
|
|
}
|