blob: 735208d597ea9a9d70dabf1a3042b1f4f8401b6c [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::{HashMap, HashSet};
type EntityReference = String;
pub struct StoryContextStore {
context_entities: HashMap<EntityReference, ContextEntity>,
contributor_to_refs: HashMap<Contributor, HashSet<EntityReference>>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContextEntity {
reference: EntityReference,
contributors: HashSet<Contributor>,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum Contributor {
ModuleContributor { story_id: String, module_id: String, parameter_name: String },
// TODO(miguelfrde): Add group scoped contributors
}
impl Contributor {
pub fn module_new(story_id: &str, module_id: &str, parameter_name: &str) -> Self {
Contributor::ModuleContributor {
story_id: story_id.to_string(),
module_id: module_id.to_string(),
parameter_name: parameter_name.to_string(),
}
}
}
impl ContextEntity {
pub fn new(reference: &str) -> Self {
ContextEntity { reference: reference.to_string(), contributors: HashSet::new() }
}
pub fn add_contributor(&mut self, contributor: Contributor) {
self.contributors.insert(contributor);
}
fn remove_contributor(&mut self, contributor: &Contributor) {
self.contributors.remove(contributor);
}
}
impl StoryContextStore {
pub fn new() -> Self {
StoryContextStore { context_entities: HashMap::new(), contributor_to_refs: HashMap::new() }
}
pub fn contribute(
&mut self,
story_id: &str,
module_id: &str,
parameter_name: &str,
reference: &str,
) {
let contributor = Contributor::module_new(story_id, module_id, parameter_name);
self.clear_contributor(&contributor);
self.context_entities
.entry(reference.to_string())
.or_insert(ContextEntity::new(reference))
.add_contributor(contributor.clone());
// Keep track of contributor => references removal.
self.contributor_to_refs
.entry(contributor)
.or_insert(HashSet::new())
.insert(reference.to_string());
}
pub fn withdraw(&mut self, story_id: &str, module_id: &str, parameter_names: Vec<&str>) {
parameter_names
.iter()
.map(|param| Contributor::ModuleContributor {
story_id: story_id.to_string(),
module_id: module_id.to_string(),
parameter_name: param.to_string(),
})
.for_each(|c| self.clear_contributor(&c));
}
#[allow(dead_code)] // Will be used through the ContextManager
pub fn withdraw_all(&mut self, story_id: &str, module_id: &str) {
let contributors: Vec<Contributor> = self
.contributor_to_refs
.keys()
.filter(|c| match c {
Contributor::ModuleContributor { story_id: s, module_id: m, .. } => {
s == story_id && m == module_id
}
})
.cloned()
.collect();
for contributor in contributors {
self.clear_contributor(&contributor);
}
}
/// Iterate over the context entities.
#[allow(dead_code)] // Will be used through the SuggestionsManager. Currently in tests.
pub fn current<'a>(&'a self) -> impl Iterator<Item = &'a ContextEntity> {
self.context_entities.values().into_iter()
}
fn clear_contributor(&mut self, contributor: &Contributor) {
self.contributor_to_refs.remove_entry(contributor).map(|(c, references)| {
for reference in references {
match self.context_entities.get_mut(&reference) {
None => continue,
Some(context_entity) => {
context_entity.remove_contributor(&c);
if context_entity.contributors.is_empty() {
self.context_entities.remove(&reference);
}
}
}
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn contribute() {
let mut story_context_store = StoryContextStore::new();
// Add a few references
story_context_store.contribute("story1", "mod-a", "param-foo", "foo");
story_context_store.contribute("story2", "mod-b", "param-baz", "foo");
story_context_store.contribute("story1", "mod-a", "param-bar", "bar");
// Verify context store state
let mut context_entity_foo = ContextEntity::new("foo");
context_entity_foo.add_contributor(Contributor::module_new("story1", "mod-a", "param-foo"));
context_entity_foo.add_contributor(Contributor::module_new("story2", "mod-b", "param-baz"));
let mut context_entity_bar = ContextEntity::new("bar");
context_entity_bar.add_contributor(Contributor::module_new("story1", "mod-a", "param-bar"));
let expected_context_entities: HashMap<String, ContextEntity> =
[("foo".to_string(), context_entity_foo), ("bar".to_string(), context_entity_bar)]
.iter()
.cloned()
.collect();
assert_eq!(story_context_store.context_entities, expected_context_entities);
// Contributing the same entity shouldn't have an effect.
story_context_store.contribute("story1", "mod-a", "param-foo", "foo");
assert_eq!(story_context_store.context_entities, expected_context_entities);
}
#[test]
fn withdraw() {
let mut story_context_store = StoryContextStore::new();
// Add a few references
story_context_store.contribute("story1", "mod-a", "param-foo", "foo");
story_context_store.contribute("story2", "mod-b", "param-baz", "foo");
story_context_store.contribute("story1", "mod-a", "param-bar", "bar");
// Remove a few of them
story_context_store.withdraw("story2", "mod-b", vec!["param-baz"]);
story_context_store.withdraw("story1", "mod-a", vec!["param-bar"]);
// Verify context store state
let mut context_entity = ContextEntity::new("foo");
context_entity.add_contributor(Contributor::module_new("story1", "mod-a", "param-foo"));
let mut expected_context_entities = HashMap::<String, ContextEntity>::new();
expected_context_entities.insert("foo".to_string(), context_entity);
assert_eq!(story_context_store.context_entities, expected_context_entities);
}
#[test]
fn withdraw_all() {
let mut story_context_store = StoryContextStore::new();
// Add a few references
story_context_store.contribute("story1", "mod-a", "param-foo", "foo");
story_context_store.contribute("story2", "mod-b", "param-baz", "foo");
story_context_store.contribute("story1", "mod-a", "param-bar", "bar");
// Withdraw all context for one of the mods
story_context_store.withdraw_all("story1", "mod-a");
// Verify context store state
let mut context_entity = ContextEntity::new("foo");
context_entity.add_contributor(Contributor::module_new("story2", "mod-b", "param-baz"));
let mut expected_context_entities = HashMap::<String, ContextEntity>::new();
expected_context_entities.insert("foo".to_string(), context_entity);
assert_eq!(story_context_store.context_entities, expected_context_entities);
}
}