use std::cell::{Cell, RefCell};
use std::collections::HashSet;
use std::sync::Arc;
use template_model::SemanticModel;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ReferenceRendering {
Marker,
TargetId,
}
pub(crate) struct EvaluationContext {
model: Arc<SemanticModel>,
region: Option<String>,
suppressed_builtin_rules: Arc<HashSet<String>>,
}
impl EvaluationContext {
pub(crate) fn new(
model: Arc<SemanticModel>,
region: Option<String>,
suppressed_builtin_rules: Arc<HashSet<String>>,
) -> Self {
Self { model, region, suppressed_builtin_rules }
}
}
thread_local! {
static CONTEXT_STACK: RefCell<Vec<EvaluationContext>> = const { RefCell::new(Vec::new()) };
static REFERENCE_RENDERING: Cell<ReferenceRendering> = const { Cell::new(ReferenceRendering::Marker) };
}
#[must_use = "the context is only installed while the scope is held"]
pub(crate) struct EvaluationScope {
_guard: (),
}
impl EvaluationScope {
pub(crate) fn enter(context: EvaluationContext) -> Self {
CONTEXT_STACK.with(|stack| stack.borrow_mut().push(context));
Self { _guard: () }
}
}
impl Drop for EvaluationScope {
fn drop(&mut self) {
CONTEXT_STACK.with(|stack| {
stack.borrow_mut().pop();
});
}
}
#[must_use = "the rendering is only selected while the scope is held"]
pub(crate) struct ReferenceRenderingScope {
previous: ReferenceRendering,
}
impl ReferenceRenderingScope {
pub(crate) fn enter(rendering: ReferenceRendering) -> Self {
let previous = REFERENCE_RENDERING.with(|slot| slot.replace(rendering));
Self { previous }
}
}
impl Drop for ReferenceRenderingScope {
fn drop(&mut self) {
REFERENCE_RENDERING.with(|slot| slot.set(self.previous));
}
}
fn with_current_context<R>(read: impl FnOnce(&EvaluationContext) -> R) -> Option<R> {
CONTEXT_STACK.with(|stack| stack.borrow().last().map(read))
}
pub(crate) fn current_model() -> Option<Arc<SemanticModel>> {
with_current_context(|context| context.model.clone())
}
pub(crate) fn current_region() -> Option<String> {
with_current_context(|context| context.region.clone()).flatten()
}
pub(crate) fn is_builtin_rule_suppressed(rule_id: &str) -> bool {
with_current_context(|context| context.suppressed_builtin_rules.contains(rule_id)).unwrap_or(false)
}
pub(crate) fn current_reference_rendering() -> ReferenceRendering {
REFERENCE_RENDERING.with(Cell::get)
}
#[cfg(test)]
mod tests {
use super::*;
fn empty_model() -> Arc<SemanticModel> {
Arc::new(SemanticModel::from_bytes(b"AWSTemplateFormatVersion: '2010-09-09'\nResources: {}").unwrap())
}
#[test]
fn no_scope_exposes_no_model_or_region_and_suppresses_nothing() {
assert!(current_model().is_none());
assert!(current_region().is_none());
assert!(!is_builtin_rule_suppressed("W9010"));
}
#[test]
fn scope_exposes_its_model_region_and_suppressed_rules() {
let suppressed = Arc::new(HashSet::from(["W9010".to_string()]));
let _scope =
EvaluationScope::enter(EvaluationContext::new(empty_model(), Some("eu-west-1".to_string()), suppressed));
assert!(current_model().is_some());
assert_eq!(current_region(), Some("eu-west-1".to_string()));
assert!(is_builtin_rule_suppressed("W9010"));
assert!(!is_builtin_rule_suppressed("W9011"));
}
#[test]
fn dropping_the_scope_clears_the_context() {
{
let _scope = EvaluationScope::enter(EvaluationContext::new(empty_model(), None, Arc::default()));
assert!(current_model().is_some());
}
assert!(current_model().is_none());
}
#[test]
fn nested_scope_restores_the_enclosing_context_on_exit() {
let outer = Arc::new(HashSet::from(["OUTER".to_string()]));
let inner = Arc::new(HashSet::from(["INNER".to_string()]));
let _outer_scope =
EvaluationScope::enter(EvaluationContext::new(empty_model(), Some("us-east-1".to_string()), outer));
{
let _inner_scope =
EvaluationScope::enter(EvaluationContext::new(empty_model(), Some("ap-south-1".to_string()), inner));
assert_eq!(current_region(), Some("ap-south-1".to_string()));
assert!(is_builtin_rule_suppressed("INNER"));
assert!(!is_builtin_rule_suppressed("OUTER"));
}
assert_eq!(current_region(), Some("us-east-1".to_string()));
assert!(is_builtin_rule_suppressed("OUTER"));
assert!(!is_builtin_rule_suppressed("INNER"));
}
#[test]
fn no_rendering_scope_uses_the_marker_rendering_of_the_builtin_policies() {
assert_eq!(current_reference_rendering(), ReferenceRendering::Marker);
}
#[test]
fn rendering_scope_selects_its_rendering_and_restores_the_previous_one_on_exit() {
{
let _custom = ReferenceRenderingScope::enter(ReferenceRendering::TargetId);
assert_eq!(current_reference_rendering(), ReferenceRendering::TargetId);
}
assert_eq!(current_reference_rendering(), ReferenceRendering::Marker);
}
#[test]
fn nested_rendering_scope_restores_the_enclosing_rendering_not_the_default() {
let _custom = ReferenceRenderingScope::enter(ReferenceRendering::TargetId);
{
let _builtin = ReferenceRenderingScope::enter(ReferenceRendering::Marker);
assert_eq!(current_reference_rendering(), ReferenceRendering::Marker);
}
assert_eq!(
current_reference_rendering(),
ReferenceRendering::TargetId,
"leaving the inner scope must hand back the outer package's rendering"
);
}
}