use super::compile::{ProjectionError, RevisionProjection};
use crate::config::{Config, NamespacePolicy};
use crate::desired_state::policy::{PolicyScope, PolicySet};
use crate::desired_state::{DesiredState, RevisionId};
#[derive(Debug, Clone, Copy, Default)]
pub struct PolicyProjection<P> {
inner: P,
}
impl<P: RevisionProjection> PolicyProjection<P> {
pub const fn over(inner: P) -> Self {
Self { inner }
}
}
impl<P: RevisionProjection> RevisionProjection for PolicyProjection<P> {
fn name(&self) -> &'static str {
"policy"
}
fn projects_inbound_principals(&self) -> bool {
self.inner.projects_inbound_principals()
}
fn project(
&self,
bootstrap: &Config,
state: &DesiredState,
source: RevisionId,
) -> Result<Config, ProjectionError> {
let mut config = self.inner.project(bootstrap, state, source)?;
let policies = PolicySet::of(state).map_err(|error| ProjectionError::Body {
reference: error.reference(),
detail: error.to_string(),
})?;
if policies.documents().len() == 0 {
return Ok(config);
}
for namespace in &mut config.namespace {
let Some(identity) = namespace.project else {
continue;
};
let scope = PolicyScope::Project {
tenant: identity.tenant,
project: identity.project,
};
let Some(document) = policies.effective(scope) else {
continue;
};
namespace.policy = Some(NamespacePolicy {
body: document.body,
generation: document.body.generation(source),
});
}
Ok(config)
}
}
#[cfg(test)]
mod tests {
use super::super::compile::testing::{bootstrap, revision};
use super::super::tenancy::TenancyProjection;
use super::*;
use crate::desired_state::fixtures::{project_id, revision_id, state, tenant_id};
use crate::desired_state::policy::PolicyBody;
use crate::desired_state::{DesiredState, Slug};
use crate::policy::fixtures::body;
fn with_policy(body: PolicyBody) -> DesiredState {
let mut state = state();
state
.insert(body.version(Slug::parse("policy").expect("a valid slug")))
.expect("a policy resource");
state
}
fn projection() -> PolicyProjection<TenancyProjection> {
PolicyProjection::over(TenancyProjection)
}
fn policy_of<'a>(config: &'a Config, id: &str) -> Option<&'a NamespacePolicy> {
config
.namespace
.iter()
.find(|namespace| namespace.id == id)
.expect("the namespace is projected")
.policy
.as_ref()
}
#[test]
fn a_project_document_governs_the_namespace_that_project_projects_to() {
let scope = PolicyScope::Project {
tenant: tenant_id(1),
project: project_id(2),
};
let config = projection()
.project(
&bootstrap(),
&with_policy(body(scope, 1, 9_000)),
revision_id(4),
)
.expect("the revision projects");
let attached = policy_of(&config, "acme/core").expect("the project is governed");
assert_eq!(attached.body.budget().subject_limit_microdollars(), 9_000);
assert_eq!(attached.generation.epoch().get(), 1);
assert_eq!(attached.generation.source(), revision_id(4));
}
#[test]
fn a_tenant_document_governs_a_project_that_publishes_none_of_its_own() {
let config = projection()
.project(
&bootstrap(),
&with_policy(body(PolicyScope::Tenant(tenant_id(1)), 2, 4_000)),
revision_id(4),
)
.expect("the revision projects");
let attached = policy_of(&config, "acme/core").expect("the tenant's policy governs");
assert_eq!(attached.body.budget().subject_limit_microdollars(), 4_000);
assert_eq!(
attached.generation.scope(),
PolicyScope::Tenant(tenant_id(1))
);
}
#[test]
fn a_bootstrap_namespace_is_not_governed_by_any_published_document() {
let config = projection()
.project(
&bootstrap(),
&with_policy(body(PolicyScope::Tenant(tenant_id(1)), 1, 4_000)),
revision_id(4),
)
.expect("the revision projects");
assert!(policy_of(&config, "platform").is_none());
}
#[test]
fn a_revision_publishing_no_policy_leaves_every_namespace_on_the_bootstrap_limits() {
let config = projection()
.project(&bootstrap(), revision().state(), revision_id(4))
.expect("the revision projects");
assert!(
config
.namespace
.iter()
.all(|namespace| namespace.policy.is_none())
);
}
}