use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RecallMode {
Off,
Writable,
Visible,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CaptureMode {
Off,
Writable,
}
impl RecallMode {
pub fn parse(value: Option<&str>) -> Self {
match value {
Some("off") => Self::Off,
Some("writable") => Self::Writable,
Some("visible") => Self::Visible,
_ => MemoryPolicy::DEFAULT.recall,
}
}
}
impl CaptureMode {
pub fn parse(value: Option<&str>) -> Self {
match value {
Some("off") => Self::Off,
_ => MemoryPolicy::DEFAULT.capture,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LayerMode {
Rw,
Ro,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkspaceLayer {
pub scope_id: String,
pub mode: LayerMode,
}
impl WorkspaceLayer {
pub fn new(scope_id: impl Into<String>, mode: LayerMode) -> Self {
Self {
scope_id: scope_id.into(),
mode,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemoryPolicy {
pub recall: RecallMode,
pub capture: CaptureMode,
}
impl MemoryPolicy {
pub const DEFAULT: Self = Self {
recall: RecallMode::Visible,
capture: CaptureMode::Writable,
};
pub fn captures(&self) -> bool {
self.capture == CaptureMode::Writable
}
pub fn capture_scope(&self, layers: &[WorkspaceLayer], fallback: &str) -> Option<String> {
self.captures()
.then(|| writable_memory_scope(layers, fallback))
}
pub fn recall_scopes(&self, layers: &[WorkspaceLayer], writable_scope_id: &str) -> Vec<String> {
recall_memory_scopes(*self, layers, writable_scope_id)
}
}
impl Default for MemoryPolicy {
fn default() -> Self {
Self::DEFAULT
}
}
pub fn writable_memory_scope(layers: &[WorkspaceLayer], fallback: &str) -> String {
layers
.iter()
.find(|l| l.mode == LayerMode::Rw)
.map(|l| l.scope_id.clone())
.unwrap_or_else(|| fallback.to_string())
}
pub fn recall_memory_scopes(
policy: MemoryPolicy,
layers: &[WorkspaceLayer],
writable_scope_id: &str,
) -> Vec<String> {
match policy.recall {
RecallMode::Off => Vec::new(),
RecallMode::Writable => vec![writable_scope_id.to_string()],
RecallMode::Visible => {
let mut out = vec![writable_scope_id.to_string()];
for layer in layers {
if !out.contains(&layer.scope_id) {
out.push(layer.scope_id.clone());
}
}
out
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn layers() -> Vec<WorkspaceLayer> {
vec![
WorkspaceLayer::new("org", LayerMode::Ro),
WorkspaceLayer::new("team", LayerMode::Rw),
WorkspaceLayer::new("shared", LayerMode::Ro),
]
}
#[test]
fn default_policy_is_visible_and_writable() {
let p = MemoryPolicy::default();
assert_eq!(p.recall, RecallMode::Visible);
assert_eq!(p.capture, CaptureMode::Writable);
assert!(p.captures());
}
#[test]
fn parse_falls_back_to_default() {
assert_eq!(RecallMode::parse(Some("off")), RecallMode::Off);
assert_eq!(RecallMode::parse(Some("writable")), RecallMode::Writable);
assert_eq!(RecallMode::parse(Some("visible")), RecallMode::Visible);
assert_eq!(RecallMode::parse(Some("nonsense")), RecallMode::Visible);
assert_eq!(RecallMode::parse(None), RecallMode::Visible);
assert_eq!(CaptureMode::parse(Some("off")), CaptureMode::Off);
assert_eq!(CaptureMode::parse(Some("writable")), CaptureMode::Writable);
assert_eq!(CaptureMode::parse(Some("nonsense")), CaptureMode::Writable);
assert_eq!(CaptureMode::parse(None), CaptureMode::Writable);
}
#[test]
fn writable_scope_prefers_first_rw_layer() {
assert_eq!(writable_memory_scope(&layers(), "self"), "team");
let read_only = vec![WorkspaceLayer::new("org", LayerMode::Ro)];
assert_eq!(writable_memory_scope(&read_only, "self"), "self");
assert_eq!(writable_memory_scope(&[], "self"), "self");
}
#[test]
fn recall_scopes_follow_the_mode() {
let ls = layers();
let off = MemoryPolicy {
recall: RecallMode::Off,
capture: CaptureMode::Off,
};
assert!(off.recall_scopes(&ls, "team").is_empty());
let writable = MemoryPolicy {
recall: RecallMode::Writable,
capture: CaptureMode::Writable,
};
assert_eq!(
writable.recall_scopes(&ls, "team"),
vec!["team".to_string()]
);
assert_eq!(
MemoryPolicy::DEFAULT.recall_scopes(&ls, "team"),
vec!["team".to_string(), "org".to_string(), "shared".to_string()]
);
}
#[test]
fn recall_scopes_deduplicate_and_lead_with_writable() {
let ls = vec![
WorkspaceLayer::new("team", LayerMode::Rw),
WorkspaceLayer::new("team", LayerMode::Ro),
];
assert_eq!(
MemoryPolicy::DEFAULT.recall_scopes(&ls, "team"),
vec!["team".to_string()]
);
}
#[test]
fn capture_scope_is_none_when_capture_off() {
let ls = layers();
let off = MemoryPolicy {
recall: RecallMode::Visible,
capture: CaptureMode::Off,
};
assert_eq!(off.capture_scope(&ls, "self"), None);
assert_eq!(
MemoryPolicy::DEFAULT.capture_scope(&ls, "self"),
Some("team".to_string())
);
}
}