use plana::jsonrpc::error_codes;
use serde::{Deserialize, Serialize};
use crate::classify::{ClassifiedError, ErrorKind};
use crate::envelope::TargetScope;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Endpoint {
Ipc(String),
Tcp {
host: String,
port: u16,
},
}
#[derive(Debug, Clone)]
pub struct TargetScopeGuard {
designated_alias: String,
}
impl TargetScopeGuard {
pub fn new(designated_alias: impl Into<String>) -> Self {
Self {
designated_alias: designated_alias.into(),
}
}
pub fn designated_alias(&self) -> &str {
&self.designated_alias
}
pub fn validate(&self, scope: &TargetScope) -> Result<(), ClassifiedError> {
let matches = match scope {
TargetScope::DesignatedSingleNode { alias } => *alias == self.designated_alias,
};
if matches {
return Ok(());
}
Err(ClassifiedError::new(
ErrorKind::TargetOutOfScope,
error_codes::AUTH_ERROR,
format!(
"target scope rejected: requested node is not the designated single node {:?}",
self.designated_alias
),
)
.with_reason("target_out_of_scope"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn designated_scope() -> TargetScope {
TargetScope::DesignatedSingleNode {
alias: "hydro-lab".to_string(),
}
}
#[test]
fn designated_alias_is_accepted() {
let guard = TargetScopeGuard::new("hydro-lab");
assert_eq!(guard.designated_alias(), "hydro-lab");
assert!(guard.validate(&designated_scope()).is_ok());
}
#[test]
fn foreign_alias_is_rejected_with_target_out_of_scope() {
let guard = TargetScopeGuard::new("hydro-lab");
let foreign = TargetScope::DesignatedSingleNode {
alias: "other-node".to_string(),
};
let error = guard.validate(&foreign).expect_err("must be rejected");
assert_eq!(error.kind, ErrorKind::TargetOutOfScope);
assert_eq!(error.code, error_codes::AUTH_ERROR);
assert_eq!(error.code, -32005);
assert_eq!(error.data, Some(json!({"reason": "target_out_of_scope"})));
assert!(error.message.contains("hydro-lab"));
}
#[test]
fn alias_comparison_is_exact_equality() {
let guard = TargetScopeGuard::new("hydro-lab");
let scope = TargetScope::DesignatedSingleNode {
alias: "hydro-lab-ext".to_string(),
};
assert!(guard.validate(&scope).is_err());
let case_variant = TargetScope::DesignatedSingleNode {
alias: "Hydro-Lab".to_string(),
};
assert!(guard.validate(&case_variant).is_err());
let empty_config = TargetScopeGuard::new("");
let scope = TargetScope::DesignatedSingleNode {
alias: String::new(),
};
assert!(empty_config.validate(&scope).is_ok());
let scope = TargetScope::DesignatedSingleNode {
alias: "x".to_string(),
};
assert!(empty_config.validate(&scope).is_err());
}
#[test]
fn endpoint_serializes_ipc_and_tcp() {
let ipc = Endpoint::Ipc("/var/run/evernight/broker.sock".to_string());
assert_eq!(
serde_json::to_value(&ipc).unwrap(),
json!({"Ipc": "/var/run/evernight/broker.sock"})
);
let tcp = Endpoint::Tcp {
host: "192.0.2.10".to_string(),
port: 7777,
};
let value = serde_json::to_value(&tcp).unwrap();
assert_eq!(value["Tcp"]["host"], "192.0.2.10");
assert_eq!(value["Tcp"]["port"], 7777);
let back: Endpoint = serde_json::from_value(value).unwrap();
assert_eq!(back, tcp);
}
}