use crate::discovery::ServiceInstance;
use crate::discovery_etcd::{EtcdDiscoveryConfig, EtcdDiscoveryError, EtcdDiscoveryResult};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EtcdInstanceKey {
pub service: String,
pub id: String,
}
pub fn instance_key(config: &EtcdDiscoveryConfig, service: &str, id: &str) -> String {
format!(
"{}/{}/{}",
config.prefix.trim_end_matches('/'),
service.trim_matches('/'),
id.trim_matches('/')
)
}
pub fn service_prefix(config: &EtcdDiscoveryConfig, service: &str) -> String {
format!(
"{}/{}/",
config.prefix.trim_end_matches('/'),
service.trim_matches('/')
)
}
pub fn split_instance_key(
config: &EtcdDiscoveryConfig,
key: &str,
) -> EtcdDiscoveryResult<Option<EtcdInstanceKey>> {
let root = format!("{}/", config.prefix.trim_end_matches('/'));
let Some(suffix) = key.strip_prefix(&root) else {
return Ok(None);
};
let Some((service, id)) = suffix.split_once('/') else {
return Err(EtcdDiscoveryError::InvalidKey(key.to_string()));
};
if service.trim().is_empty() || id.trim().is_empty() {
return Err(EtcdDiscoveryError::InvalidKey(key.to_string()));
}
Ok(Some(EtcdInstanceKey {
service: service.to_string(),
id: id.to_string(),
}))
}
pub fn encode_instance(instance: &ServiceInstance) -> EtcdDiscoveryResult<Vec<u8>> {
Ok(serde_json::to_vec(instance)?)
}
pub fn decode_instance(value: &[u8]) -> EtcdDiscoveryResult<ServiceInstance> {
Ok(serde_json::from_slice(value)?)
}
pub(crate) fn decode_healthy_instances(
kvs: Vec<etcd_client::KeyValue>,
) -> EtcdDiscoveryResult<Vec<ServiceInstance>> {
let mut instances = Vec::new();
for kv in kvs {
let instance = decode_instance(kv.value())?;
if instance.healthy {
instances.push(instance);
}
}
Ok(instances)
}
#[cfg(test)]
mod tests {
use crate::discovery::{InstanceEndpoint, ServiceInstance};
use crate::discovery_etcd::{
EtcdDiscoveryConfig, decode_instance, encode_instance, instance_key, service_prefix,
split_instance_key,
};
#[test]
fn codec_round_trips_service_instance() {
let instance = ServiceInstance::new(
"api",
"api-1",
InstanceEndpoint::new("127.0.0.1", 8080).expect("endpoint"),
)
.with_metadata("zone", "local");
let value = encode_instance(&instance).expect("encode");
assert_eq!(decode_instance(&value).expect("decode"), instance);
assert_eq!(
instance_key(&EtcdDiscoveryConfig::default(), "api", "api-1"),
"/rs-zero/api/api-1"
);
assert_eq!(
service_prefix(&EtcdDiscoveryConfig::default(), "api"),
"/rs-zero/api/"
);
let parsed = split_instance_key(&EtcdDiscoveryConfig::default(), "/rs-zero/api/api-1")
.expect("split")
.expect("parts");
assert_eq!(parsed.service, "api");
assert_eq!(parsed.id, "api-1");
}
}