blob: 3afae91ce88f55e936f3d22ea96611670fba7b55 [file] [log] [blame]
// Copyright 2020 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.
// Allow unused until library is used.
#![allow(unused)]
///! High level node abstraction on top of Fuchsia's Stash service.
///! Allows to insert structured, hierarchical data into a Stash backed
///! store.
use {
anyhow::Error,
fidl_fuchsia_stash as fidl_stash,
std::collections::{HashMap, HashSet},
};
pub const NODE_SEPARATOR: &'static str = "#/@";
pub struct StashNode {
// Always terminated with `NODE_SEPARATOR`
key: String,
stash: fidl_stash::StoreAccessorProxy,
}
/// A convience wrapper around a Stash-backed `HashMap`.
pub struct StashNodeFields(HashMap<String, fidl_stash::Value>);
impl StashNodeFields {
/// Returns `None` if the field is not present or its value is not of type `String`.
/// Returns `Some(String)` otherwise.
pub fn get_str(&self, field: &str) -> Option<&String> {
match self.0.get(field) {
Some(fidl_stash::Value::Stringval(x)) => Some(x),
_ => None,
}
}
/// Returns `None` if the field is not present or its value is not of type `i64`.
/// Returns `Some(i64)` otherwise.
pub fn get_int(&self, field: &str) -> Option<i64> {
match self.0.get(field) {
Some(fidl_stash::Value::Intval(x)) => Some(*x),
_ => None,
}
}
/// Returns `None` if the field is not present or its value is not of type `bool`.
/// Returns `Some(bool)` otherwise.
pub fn get_bool(&self, field: &str) -> Option<bool> {
match self.0.get(field) {
Some(fidl_stash::Value::Boolval(x)) => Some(*x),
_ => None,
}
}
/// Returns `None` if the field is not present or its value is not of type `f64`.
/// Returns `Some(f64)` otherwise.
pub fn get_float(&self, field: &str) -> Option<f64> {
match self.0.get(field) {
Some(fidl_stash::Value::Floatval(x)) => Some(*x),
_ => None,
}
}
}
impl StashNode {
pub fn root(stash: fidl_stash::StoreAccessorProxy) -> Self {
Self { key: NODE_SEPARATOR.to_string(), stash }
}
pub fn with_key(key: String, stash: fidl_stash::StoreAccessorProxy) -> Self {
Self { key, stash }
}
/// Returns the key's current node. The key shouldn't be accessed unless the key itself is of
/// a constant, never changing value.
pub fn key(&self) -> String {
self.key.clone()
}
/// Requests a child node with a given name.
/// The child may or may not exist already in Stash.
pub fn child(&self, key: &str) -> Self {
Self { key: format!("{}{}{}", self.key, key, NODE_SEPARATOR), stash: self.stash.clone() }
}
/// Deletes an entire node and all its fields and children.
/// No-op if the Node is not yet backed by Stash.
pub fn delete(&mut self) {
self.stash.delete_prefix(&self.key);
}
/// Deletes a specific field of the node. No-op if the field doesn't exist.
pub fn delete_field(&mut self, field: &str) {
self.stash.delete_value(&format!("{}{}", self.key, field));
}
fn write_val(&mut self, field: &str, mut value: fidl_stash::Value) -> Result<(), Error> {
self.stash.set_value(&format!("{}{}", self.key, field), &mut value)?;
Ok(())
}
pub fn write_str(&mut self, field: &str, s: String) -> Result<(), Error> {
self.write_val(field, fidl_stash::Value::Stringval(s))
}
pub fn write_int(&mut self, field: &str, i: i64) -> Result<(), Error> {
self.write_val(field, fidl_stash::Value::Intval(i))
}
pub fn write_bool(&mut self, field: &str, b: bool) -> Result<(), Error> {
self.write_val(field, fidl_stash::Value::Boolval(b))
}
pub fn write_float(&mut self, field: &str, f: f64) -> Result<(), Error> {
self.write_val(field, fidl_stash::Value::Floatval(f))
}
/// Commits all changes made to Stash.
/// Note that this applies to the entire tree, not just changes made under this node.
pub fn commit(&mut self) -> Result<(), Error> {
self.stash.commit()?;
Ok(())
}
/// Returns an unordered set of all of the node's direct child nodes.
pub async fn children(&self) -> Result<HashSet<StashNode>, Error> {
let (local, remote) = fidl::endpoints::create_proxy::<_>()?;
let () = self.stash.list_prefix(&self.key, remote)?;
let parent_key_len = self.key.len();
let mut children = HashSet::new();
loop {
let key_list = local.get_next().await?;
if key_list.is_empty() {
break;
}
for list_item in key_list {
let key = &list_item.key[parent_key_len..];
if let Some(idx) = key.find(NODE_SEPARATOR) {
children.insert(self.child(&key[..idx]));
}
}
}
Ok(children)
}
/// Returns an `StashNodeFields` granting access to all direct fields of the node.
pub async fn fields(&self) -> Result<StashNodeFields, Error> {
let (local, remote) = fidl::endpoints::create_proxy::<_>()?;
let () = self.stash.get_prefix(&self.key, remote)?;
let parent_key_len = self.key.len();
let mut fields = HashMap::new();
loop {
let key_value_list = local.get_next().await?;
if key_value_list.is_empty() {
break;
}
for key_value in key_value_list {
let key = &key_value.key[parent_key_len..];
if let None = key.find(NODE_SEPARATOR) {
fields.insert(key.to_string(), key_value.val);
}
}
}
Ok(StashNodeFields(fields))
}
async fn read_val(&self, field: &str) -> Option<fidl_stash::Value> {
match self.stash.get_value(&format!("{}{}", self.key, field)).await.ok() {
Some(Some(boxed_value)) => Some(*boxed_value),
_ => None,
}
}
pub async fn read_int(&self, field: &str) -> Option<i64> {
match self.read_val(field).await {
Some(fidl_stash::Value::Intval(x)) => Some(x),
_ => None,
}
}
pub async fn read_str(&self, field: &str) -> Option<String> {
match self.read_val(field).await {
Some(fidl_stash::Value::Stringval(x)) => Some(x),
_ => None,
}
}
pub async fn read_bool(&self, field: &str) -> Option<bool> {
match self.read_val(field).await {
Some(fidl_stash::Value::Boolval(x)) => Some(x),
_ => None,
}
}
pub async fn read_float(&self, field: &str) -> Option<f64> {
match self.read_val(field).await {
Some(fidl_stash::Value::Floatval(x)) => Some(x),
_ => None,
}
}
}
impl std::hash::Hash for StashNode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.key.hash(state);
}
}
impl PartialEq for StashNode {
fn eq(&self, other: &Self) -> bool {
self.key == other.key
}
}
impl Eq for StashNode {}
#[cfg(test)]
mod tests {
use {super::*, fidl::endpoints::create_proxy, fuchsia_component::client::connect_to_service};
fn new_stash_store(id: &str) -> fidl_stash::StoreAccessorProxy {
let store_client = connect_to_service::<fidl_stash::StoreMarker>()
.expect("failed connecting to Stash service");
store_client.identify(id).expect("failed identifying client to store");
let (proxy, remote) = create_proxy().expect("failed creating accessor proxy");
store_client.create_accessor(false, remote).expect("failed creating Stash accessor");
proxy
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_root() {
let proxy = new_stash_store("test_root");
let mut root = StashNode::root(proxy);
// Nothing written yet.
let fields = root.fields().await.expect("error reading fields");
assert!(fields.0.is_empty());
// Write a field.
root.write_str("test", "Foobar".to_string()).expect("error writing field");
root.commit().expect("error commiting");
// Read a single fields and all fields.
assert_eq!(Some("Foobar".to_string()), root.read_str("test").await);
let fields = root.fields().await.expect("error reading fields");
assert!(!fields.0.is_empty());
assert_eq!(Some(&"Foobar".to_string()), fields.get_str("test"));
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_overwrite() {
let proxy = new_stash_store("test_overwrite");
let mut root = StashNode::root(proxy);
// Write a field.
root.write_str("test", "FoobarOne".to_string()).expect("error writing field");
root.commit().expect("error commiting");
// Overwrite the same field.
root.write_str("test", "FoobarTwo".to_string()).expect("error writing field");
root.commit().expect("error commiting");
assert_eq!(Some("FoobarTwo".to_string()), root.read_str("test").await);
// Overwrite the same field with a different type.
root.write_int("test", 1337).expect("error writing field");
root.commit().expect("error commiting");
assert_eq!(None, root.read_str("test").await);
assert_eq!(Some(1337), root.read_int("test").await);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_child() {
let proxy = new_stash_store("test_child");
let root = StashNode::root(proxy);
let mut child = root.child("child");
child.write_str("test", "Foobar".to_string()).expect("error writing field");
child.commit().expect("error commiting");
// Root should have no fields.
let fields = root.fields().await.expect("error reading fields");
assert!(fields.0.is_empty());
// Child should hold entry.
assert_eq!(Some("Foobar".to_string()), child.read_str("test").await);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_multi_level_value() {
let proxy = new_stash_store("test_multi_level_value");
let mut root = StashNode::root(proxy);
root.write_str("same_key", "Value Root".to_string()).expect("error writing field");
root.write_str("root", "Foobar Root".to_string()).expect("error writing field");
root.commit().expect("error commiting");
let mut child = root.child("child");
child.write_str("same_key", "Value Child".to_string()).expect("error writing field");
child.write_str("child", "Foobar Child".to_string()).expect("error writing field");
child.commit().expect("error commiting");
assert_eq!(Some("Value Root".to_string()), root.read_str("same_key").await);
assert_eq!(Some("Foobar Root".to_string()), root.read_str("root").await);
assert_eq!(Some("Value Child".to_string()), child.read_str("same_key").await);
assert_eq!(Some("Foobar Child".to_string()), child.read_str("child").await);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_delete() {
let proxy = new_stash_store("test_delete");
let mut root = StashNode::root(proxy);
root.write_str("same_key", "Value Root".to_string()).expect("error writing field");
root.write_str("root", "Foobar Root".to_string()).expect("error writing field");
root.commit().expect("error commiting");
let mut child = root.child("child");
child.write_str("same_key", "Value Child".to_string()).expect("error writing field");
child.write_str("child", "Foobar Child".to_string()).expect("error writing field");
child.commit().expect("error commiting");
child.delete();
child.commit().expect("error commiting");
assert_eq!(Some("Value Root".to_string()), root.read_str("same_key").await);
assert_eq!(Some("Foobar Root".to_string()), root.read_str("root").await);
let fields = child.fields().await.expect("error reading fields");
assert!(fields.0.is_empty());
assert!(root.children().await.expect("error fetching children").is_empty());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_delete_field() {
let proxy = new_stash_store("test_delete_field");
let mut root = StashNode::root(proxy);
root.write_str("same_key", "Value Root".to_string()).expect("error writing field");
root.write_str("root", "Foobar Root".to_string()).expect("error writing field");
root.commit().expect("error commiting");
root.delete_field("same_key");
root.commit().expect("error commiting");
assert_eq!(None, root.read_str("same_key").await);
assert_eq!(Some("Foobar Root".to_string()), root.read_str("root").await);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_children() {
let proxy = new_stash_store("test_children");
let root = StashNode::root(proxy);
let mut child_a = root.child("child a");
let expected_key = child_a.key.clone();
// Accessing the child will not yet create it.
let children = root.children().await.expect("error fetching children");
assert!(children.is_empty());
child_a.write_int("an int", 42);
child_a.commit().expect("error committing");
// Child should now be available to query.
let children = root.children().await.expect("error fetching children");
assert_eq!(children.len(), 1);
let added_child = children.iter().next().unwrap();
// Read from child.
assert_eq!(Some(42), added_child.read_int("an int").await);
assert_eq!(expected_key, added_child.key);
}
}