mod error;
#[cfg(feature = "rest-api")]
mod rest_api;
mod unified;
mod yaml;
use std::collections::HashMap;
use std::iter::ExactSizeIterator;
pub use error::{InvalidNodeError, RegistryError};
pub use unified::UnifiedRegistry;
pub use yaml::LocalYamlRegistry;
#[cfg(feature = "registry-remote")]
pub use yaml::{RemoteYamlRegistry, RemoteYamlShutdownHandle};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Node {
pub identity: String,
pub endpoints: Vec<String>,
pub display_name: String,
pub keys: Vec<String>,
pub metadata: HashMap<String, String>,
}
impl Node {
pub fn builder<S: Into<String>>(identity: S) -> NodeBuilder {
NodeBuilder::new(identity)
}
pub fn has_key(&self, key: &str) -> bool {
self.keys.iter().any(|node_key| node_key == key)
}
}
pub struct NodeBuilder {
identity: String,
endpoints: Vec<String>,
display_name: Option<String>,
keys: Vec<String>,
metadata: HashMap<String, String>,
}
impl NodeBuilder {
pub fn new<S: Into<String>>(identity: S) -> Self {
Self {
identity: identity.into(),
endpoints: vec![],
display_name: None,
keys: vec![],
metadata: HashMap::new(),
}
}
pub fn with_endpoint<S: Into<String>>(mut self, endpoint: S) -> Self {
self.endpoints.push(endpoint.into());
self
}
pub fn with_endpoints<V: Into<Vec<String>>>(mut self, endpoints: V) -> Self {
self.endpoints.append(&mut endpoints.into());
self
}
pub fn with_display_name<S: Into<String>>(mut self, display_name: S) -> Self {
self.display_name = Some(display_name.into());
self
}
pub fn with_key<S: Into<String>>(mut self, key: S) -> Self {
self.keys.push(key.into());
self
}
pub fn with_keys<V: Into<Vec<String>>>(mut self, keys: V) -> Self {
self.keys.append(&mut keys.into());
self
}
pub fn with_metadata<S: Into<String>>(mut self, key: S, value: S) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn build(self) -> Result<Node, InvalidNodeError> {
let identity = self.identity;
let display_name = self
.display_name
.unwrap_or_else(|| format!("Node {}", identity));
let node = Node {
identity,
endpoints: self.endpoints,
display_name,
keys: self.keys,
metadata: self.metadata,
};
check_node_required_fields_are_not_empty(&node)?;
Ok(node)
}
}
#[derive(Clone)]
pub enum MetadataPredicate {
Eq(String, String),
Ne(String, String),
Gt(String, String),
Ge(String, String),
Lt(String, String),
Le(String, String),
}
impl MetadataPredicate {
pub fn apply(&self, node: &Node) -> bool {
match self {
MetadataPredicate::Eq(key, val) => {
node.metadata.get(key).map(|v| v == val).unwrap_or(false)
}
MetadataPredicate::Ne(key, val) => {
node.metadata.get(key).map(|v| v != val).unwrap_or(true)
}
MetadataPredicate::Gt(key, val) => {
node.metadata.get(key).map(|v| v > val).unwrap_or(false)
}
MetadataPredicate::Ge(key, val) => {
node.metadata.get(key).map(|v| v >= val).unwrap_or(false)
}
MetadataPredicate::Lt(key, val) => {
node.metadata.get(key).map(|v| v < val).unwrap_or(false)
}
MetadataPredicate::Le(key, val) => {
node.metadata.get(key).map(|v| v <= val).unwrap_or(false)
}
}
}
pub fn eq<S: Into<String>>(key: S, value: S) -> MetadataPredicate {
MetadataPredicate::Eq(key.into(), value.into())
}
pub fn ne<S: Into<String>>(key: S, value: S) -> MetadataPredicate {
MetadataPredicate::Ne(key.into(), value.into())
}
}
pub type NodeIter<'a> = Box<dyn ExactSizeIterator<Item = Node> + Send + 'a>;
pub trait RegistryReader: Send + Sync {
fn list_nodes<'a, 'b: 'a>(
&'b self,
predicates: &'a [MetadataPredicate],
) -> Result<NodeIter<'a>, RegistryError>;
fn count_nodes(&self, predicates: &[MetadataPredicate]) -> Result<u32, RegistryError>;
fn fetch_node(&self, identity: &str) -> Result<Option<Node>, RegistryError>;
fn has_node(&self, identity: &str) -> Result<bool, RegistryError> {
self.fetch_node(identity).map(|opt| opt.is_some())
}
}
pub trait RegistryWriter: Send + Sync {
fn insert_node(&self, node: Node) -> Result<(), RegistryError>;
fn delete_node(&self, identity: &str) -> Result<Option<Node>, RegistryError>;
}
pub trait RwRegistry: RegistryWriter + RegistryReader {
fn clone_box(&self) -> Box<dyn RwRegistry>;
fn clone_box_as_reader(&self) -> Box<dyn RegistryReader>;
fn clone_box_as_writer(&self) -> Box<dyn RegistryWriter>;
}
impl Clone for Box<dyn RwRegistry> {
fn clone(&self) -> Box<dyn RwRegistry> {
self.clone_box()
}
}
impl<NR> RegistryReader for Box<NR>
where
NR: RegistryReader + ?Sized,
{
fn list_nodes<'a, 'b: 'a>(
&'b self,
predicates: &'a [MetadataPredicate],
) -> Result<NodeIter<'a>, RegistryError> {
(**self).list_nodes(predicates)
}
fn count_nodes(&self, predicates: &[MetadataPredicate]) -> Result<u32, RegistryError> {
(**self).count_nodes(predicates)
}
fn fetch_node(&self, identity: &str) -> Result<Option<Node>, RegistryError> {
(**self).fetch_node(identity)
}
fn has_node(&self, identity: &str) -> Result<bool, RegistryError> {
(**self).has_node(identity)
}
}
impl<NW> RegistryWriter for Box<NW>
where
NW: RegistryWriter + ?Sized,
{
fn insert_node(&self, node: Node) -> Result<(), RegistryError> {
(**self).insert_node(node)
}
fn delete_node(&self, identity: &str) -> Result<Option<Node>, RegistryError> {
(**self).delete_node(identity)
}
}
fn validate_nodes(nodes: &[Node]) -> Result<(), InvalidNodeError> {
for (idx, node) in nodes.iter().enumerate() {
check_node_required_fields_are_not_empty(node)?;
check_if_node_is_duplicate(node, &nodes[idx + 1..])?;
}
Ok(())
}
fn check_node_required_fields_are_not_empty(node: &Node) -> Result<(), InvalidNodeError> {
if node.identity.is_empty() {
Err(InvalidNodeError::EmptyIdentity)
} else if node.endpoints.is_empty() {
Err(InvalidNodeError::MissingEndpoints)
} else if node.endpoints.iter().any(|endpoint| endpoint.is_empty()) {
Err(InvalidNodeError::EmptyEndpoint)
} else if node.display_name.is_empty() {
Err(InvalidNodeError::EmptyDisplayName)
} else if node.keys.is_empty() {
Err(InvalidNodeError::MissingKeys)
} else if node.keys.iter().any(|key| key.is_empty()) {
Err(InvalidNodeError::EmptyKey)
} else {
Ok(())
}
}
fn check_if_node_is_duplicate(
node: &Node,
existing_nodes: &[Node],
) -> Result<(), InvalidNodeError> {
existing_nodes.iter().try_for_each(|existing_node| {
if existing_node.identity == node.identity {
Err(InvalidNodeError::DuplicateIdentity(node.identity.clone()))
} else if let Some(endpoint) = existing_node
.endpoints
.iter()
.find(|endpoint| node.endpoints.contains(endpoint))
{
Err(InvalidNodeError::DuplicateEndpoint(endpoint.clone()))
} else {
Ok(())
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn node_builder_minimum() {
let node = Node::builder("identity")
.with_endpoint("endpoint")
.with_key("key")
.build()
.expect("Failed to build node");
assert_eq!(&node.identity, "identity");
assert_eq!(node.endpoints, vec!["endpoint".to_string()]);
assert_eq!(node.display_name, format!("Node {}", node.identity));
assert_eq!(node.keys, vec!["key".to_string()]);
assert!(node.metadata.is_empty());
}
#[test]
fn node_builder_all_fields() {
let node = NodeBuilder::new("identity")
.with_endpoint("endpoint1")
.with_endpoints(vec!["endpoint2".into(), "endpoint3".into()])
.with_display_name("display name")
.with_key("key1")
.with_keys(vec!["key2".into(), "key3".into()])
.with_metadata("k1", "v1")
.with_metadata("k2", "v2")
.build()
.expect("Failed to build node");
assert_eq!(&node.identity, "identity");
assert_eq!(
node.endpoints,
vec![
"endpoint1".to_string(),
"endpoint2".to_string(),
"endpoint3".to_string()
]
);
assert_eq!(&node.display_name, "display name");
assert_eq!(
node.keys,
vec!["key1".to_string(), "key2".to_string(), "key3".to_string()]
);
assert_eq!(node.metadata.len(), 2);
assert_eq!(node.metadata.get("k1"), Some(&"v1".to_string()));
assert_eq!(node.metadata.get("k2"), Some(&"v2".to_string()));
}
#[test]
fn node_builder_required_fields_emptiness() {
match NodeBuilder::new("")
.with_endpoint("endpoint")
.with_key("key")
.build()
{
Err(InvalidNodeError::EmptyIdentity) => {}
res => panic!(
"Result should have been Err(InvalidNodeError::EmptyIdentity), got: {:?}",
res
),
}
match NodeBuilder::new("identity").with_key("key").build() {
Err(InvalidNodeError::MissingEndpoints) => {}
res => panic!(
"Result should have been Err(InvalidNodeError::MissingEndpoints), got: {:?}",
res
),
}
match NodeBuilder::new("identity")
.with_endpoint("")
.with_key("key")
.build()
{
Err(InvalidNodeError::EmptyEndpoint) => {}
res => panic!(
"Result should have been Err(InvalidNodeError::EmptyEndpoint), got: {:?}",
res
),
}
match NodeBuilder::new("identity")
.with_endpoint("endpoint")
.build()
{
Err(InvalidNodeError::MissingKeys) => {}
res => panic!(
"Result should have been Err(InvalidNodeError::MissingKeys), got: {:?}",
res
),
}
match NodeBuilder::new("identity")
.with_endpoint("endpoint")
.with_key("")
.build()
{
Err(InvalidNodeError::EmptyKey) => {}
res => panic!(
"Result should have been Err(InvalidNodeError::EmptyKey), got: {:?}",
res
),
}
}
#[test]
fn node_has_key() {
let node = Node::builder("identity")
.with_endpoint("endpoint")
.with_key("key")
.build()
.expect("Failed to build node");
assert!(node.has_key("key"));
assert!(!node.has_key("other"));
}
#[test]
fn metadata_predicates() {
let node = Node::builder("identity")
.with_endpoint("endpoint")
.with_key("key")
.with_metadata("key", "5".into())
.build()
.expect("Failed to build node");
assert!(MetadataPredicate::Eq("key".into(), "5".into()).apply(&node));
assert!(!MetadataPredicate::Eq("key".into(), "4".into()).apply(&node));
assert!(MetadataPredicate::Ne("key".into(), "4".into()).apply(&node));
assert!(!MetadataPredicate::Ne("key".into(), "5".into()).apply(&node));
assert!(MetadataPredicate::Gt("key".into(), "4".into()).apply(&node));
assert!(!MetadataPredicate::Gt("key".into(), "5".into()).apply(&node));
assert!(!MetadataPredicate::Gt("key".into(), "6".into()).apply(&node));
assert!(MetadataPredicate::Ge("key".into(), "4".into()).apply(&node));
assert!(MetadataPredicate::Ge("key".into(), "5".into()).apply(&node));
assert!(!MetadataPredicate::Ge("key".into(), "6".into()).apply(&node));
assert!(MetadataPredicate::Lt("key".into(), "6".into()).apply(&node));
assert!(!MetadataPredicate::Lt("key".into(), "5".into()).apply(&node));
assert!(!MetadataPredicate::Lt("key".into(), "4".into()).apply(&node));
assert!(MetadataPredicate::Le("key".into(), "6".into()).apply(&node));
assert!(MetadataPredicate::Le("key".into(), "5".into()).apply(&node));
assert!(!MetadataPredicate::Le("key".into(), "4".into()).apply(&node));
}
#[test]
fn node_validation() {
let node1 = Node::builder("identity1")
.with_endpoint("endpoint1")
.with_key("key1")
.build()
.expect("Failed to build node1");
let node2 = Node::builder("identity2")
.with_endpoint("endpoint2")
.with_key("key2")
.build()
.expect("Failed to build node2");
let empty_identity = Node {
identity: "".into(),
endpoints: vec!["endpoint3".into()],
display_name: "display name".into(),
keys: vec!["key3".into()],
metadata: HashMap::new(),
};
match validate_nodes(&[node1.clone(), node2.clone(), empty_identity]) {
Err(InvalidNodeError::EmptyIdentity) => {}
res => panic!(
"Result should have been Err(InvalidNodeError::EmptyIdentity), got: {:?}",
res
),
}
let missing_endpoints = Node {
identity: "identity3".into(),
endpoints: vec![],
display_name: "display name".into(),
keys: vec!["key3".into()],
metadata: HashMap::new(),
};
match validate_nodes(&[node1.clone(), node2.clone(), missing_endpoints]) {
Err(InvalidNodeError::MissingEndpoints) => {}
res => panic!(
"Result should have been Err(InvalidNodeError::MissingEndpoints), got: {:?}",
res
),
}
let empty_endpoint = Node {
identity: "identity3".into(),
endpoints: vec!["".into()],
display_name: "display name".into(),
keys: vec!["key3".into()],
metadata: HashMap::new(),
};
match validate_nodes(&[node1.clone(), node2.clone(), empty_endpoint]) {
Err(InvalidNodeError::EmptyEndpoint) => {}
res => panic!(
"Result should have been Err(InvalidNodeError::EmptyEndpoint), got: {:?}",
res
),
}
let empty_display_name = Node {
identity: "identity3".into(),
endpoints: vec!["endpoint3".into()],
display_name: "".into(),
keys: vec!["key3".into()],
metadata: HashMap::new(),
};
match validate_nodes(&[node1.clone(), node2.clone(), empty_display_name]) {
Err(InvalidNodeError::EmptyDisplayName) => {}
res => panic!(
"Result should have been Err(InvalidNodeError::EmptyDisplayName), got: {:?}",
res
),
}
let missing_keys = Node {
identity: "identity3".into(),
endpoints: vec!["endpoint3".into()],
display_name: "display name".into(),
keys: vec![],
metadata: HashMap::new(),
};
match validate_nodes(&[node1.clone(), node2.clone(), missing_keys]) {
Err(InvalidNodeError::MissingKeys) => {}
res => panic!(
"Result should have been Err(InvalidNodeError::MissingKeys), got: {:?}",
res
),
}
let empty_key = Node {
identity: "identity3".into(),
endpoints: vec!["endpoint3".into()],
display_name: "display name".into(),
keys: vec!["".into()],
metadata: HashMap::new(),
};
match validate_nodes(&[node1.clone(), node2.clone(), empty_key]) {
Err(InvalidNodeError::EmptyKey) => {}
res => panic!(
"Result should have been Err(InvalidNodeError::EmptyKey), got: {:?}",
res
),
}
let duplicate_identity = Node {
identity: "identity1".into(),
endpoints: vec!["endpoint3".into()],
display_name: "display name".into(),
keys: vec!["key3".into()],
metadata: HashMap::new(),
};
match validate_nodes(&[node1.clone(), node2.clone(), duplicate_identity]) {
Err(InvalidNodeError::DuplicateIdentity(id)) if &id == "identity1" => {}
res => panic!(
"Result should have been Err(InvalidNodeError::DuplicateIdentity), got: {:?}",
res
),
}
let duplicate_endpoint = Node {
identity: "identity3".into(),
endpoints: vec!["endpoint1".into()],
display_name: "display name".into(),
keys: vec!["key3".into()],
metadata: HashMap::new(),
};
match validate_nodes(&[node1.clone(), node2.clone(), duplicate_endpoint]) {
Err(InvalidNodeError::DuplicateEndpoint(endpoint)) if &endpoint == "endpoint1" => {}
res => panic!(
"Result should have been Err(InvalidNodeError::DuplicateEndpoint), got: {:?}",
res
),
}
let valid_node3 = Node {
identity: "identity3".into(),
endpoints: vec!["endpoint3".into()],
display_name: "display name".into(),
keys: vec!["key3".into()],
metadata: HashMap::new(),
};
assert!(validate_nodes(&[node1, node2, valid_node3]).is_ok());
}
}