#![allow(missing_docs)]
use crate::extension::{DelegationError, Extension, ThroughRing, ViewRing};
use crate::quad::Tree;
pub struct StubThrough {
stub_name: String,
prefixes: Vec<String>,
#[allow(clippy::type_complexity)]
handler: Box<dyn Fn(&str, &Tree) -> Result<Tree, DelegationError> + Send + Sync>,
}
impl StubThrough {
pub fn new<F>(name: &str, prefixes: &[&str], handler: F) -> Self
where
F: Fn(&str, &Tree) -> Result<Tree, DelegationError> + Send + Sync + 'static,
{
Self {
stub_name: name.to_string(),
prefixes: prefixes.iter().map(|s| s.to_string()).collect(),
handler: Box::new(handler),
}
}
pub fn echo(name: &str, prefixes: &[&str]) -> Self {
Self::new(name, prefixes, |cmd_type, input| {
let mut result = Tree::new();
result.insert("result.status".into(), b"success".to_vec());
result.insert("result.command_type".into(), cmd_type.as_bytes().to_vec());
if let Some(payload) = input.get("command.payload") {
result.insert("result.payload".into(), payload.clone());
}
if let Some(req_id) = input.get("command.request_id") {
result.insert("result.request_id".into(), req_id.clone());
}
Ok(result)
})
}
pub fn failing(name: &str, prefixes: &[&str], error_msg: &str) -> Self {
let msg = error_msg.to_string();
Self::new(name, prefixes, move |cmd_type, input| {
let mut result = Tree::new();
result.insert("result.status".into(), b"error".to_vec());
result.insert("result.command_type".into(), cmd_type.as_bytes().to_vec());
result.insert("result.error".into(), msg.as_bytes().to_vec());
if let Some(req_id) = input.get("command.request_id") {
result.insert("result.request_id".into(), req_id.clone());
}
Ok(result)
})
}
}
impl Extension for StubThrough {
fn name(&self) -> &str {
&self.stub_name
}
}
impl ThroughRing for StubThrough {
fn handles(&self, command_type: &str) -> bool {
self.prefixes.iter().any(|p| command_type.starts_with(p))
}
fn claimed_prefixes(&self) -> Vec<String> {
self.prefixes.iter().map(|s| s.to_string()).collect()
}
fn dispatch(&self, input: &Tree) -> Result<Tree, DelegationError> {
let cmd_type = input
.get("command.type")
.map(|v| String::from_utf8_lossy(v).into_owned())
.unwrap_or_default();
(self.handler)(&cmd_type, input)
}
}
pub struct StubView {
stub_name: String,
prefixes: Vec<String>,
#[allow(clippy::type_complexity)]
handler: Box<dyn Fn(&str, &Tree) -> Result<Tree, DelegationError> + Send + Sync>,
}
impl StubView {
pub fn new<F>(name: &str, prefixes: &[&str], handler: F) -> Self
where
F: Fn(&str, &Tree) -> Result<Tree, DelegationError> + Send + Sync + 'static,
{
Self {
stub_name: name.to_string(),
prefixes: prefixes.iter().map(|s| s.to_string()).collect(),
handler: Box::new(handler),
}
}
pub fn echo(name: &str, prefixes: &[&str]) -> Self {
Self::new(name, prefixes, |view_id, input| {
let mut result = Tree::new();
result.insert("view.result.status".into(), b"success".to_vec());
result.insert("view.result.view_id".into(), view_id.as_bytes().to_vec());
result.insert("view.result.payload".into(), b"{}".to_vec());
if let Some(req_id) = input.get("view.request_id") {
result.insert("view.result.request_id".into(), req_id.clone());
}
Ok(result)
})
}
}
impl Extension for StubView {
fn name(&self) -> &str {
&self.stub_name
}
}
impl ViewRing for StubView {
fn handles_view(&self, view_id: &str) -> bool {
self.prefixes.iter().any(|p| view_id == p.as_str() || view_id.starts_with(p))
}
fn claimed_view_prefixes(&self) -> Vec<String> {
self.prefixes.clone()
}
fn project(&self, input: &Tree) -> Result<Tree, DelegationError> {
let view_id = input
.get("view.id")
.map(|v| String::from_utf8_lossy(v).into_owned())
.unwrap_or_default();
(self.handler)(&view_id, input)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn echo_returns_success() {
let stub = StubThrough::echo("test", &["user."]);
let mut input = Tree::new();
input.insert("command.type".into(), b"user.list".to_vec());
input.insert("command.request_id".into(), b"req-1".to_vec());
let result = stub.dispatch(&input).unwrap();
assert_eq!(result.get("result.status"), Some(&b"success".to_vec()));
assert_eq!(
result.get("result.command_type"),
Some(&b"user.list".to_vec())
);
assert_eq!(result.get("result.request_id"), Some(&b"req-1".to_vec()));
}
#[test]
fn handles_matches_prefix() {
let stub = StubThrough::echo("test", &["user.", "config."]);
assert!(stub.handles("user.create"));
assert!(stub.handles("config.set"));
assert!(!stub.handles("widget.explode"));
}
#[test]
fn stub_view_echo_returns_success() {
let stub = StubView::echo("test-view", &["organ.mirror"]);
let mut input = Tree::new();
input.insert("view.id".into(), b"organ.mirror".to_vec());
input.insert("view.request_id".into(), b"req-v1".to_vec());
let result = stub.project(&input).unwrap();
assert_eq!(result.get("view.result.status"), Some(&b"success".to_vec()));
assert_eq!(
result.get("view.result.view_id"),
Some(&b"organ.mirror".to_vec())
);
assert_eq!(result.get("view.result.request_id"), Some(&b"req-v1".to_vec()));
}
#[test]
fn stub_view_handles_exact_and_prefix() {
let stub = StubView::echo("test", &["organ.mirror", "term.mu."]);
assert!(stub.handles_view("organ.mirror"));
assert!(stub.handles_view("term.mu.health"));
assert!(!stub.handles_view("organ.guard"));
assert!(!stub.handles_view("term.fu.data"));
}
#[test]
fn failing_returns_error() {
let stub = StubThrough::failing("test", &["user."], "not found");
let mut input = Tree::new();
input.insert("command.type".into(), b"user.get".to_vec());
input.insert("command.request_id".into(), b"req-2".to_vec());
let result = stub.dispatch(&input).unwrap();
assert_eq!(result.get("result.status"), Some(&b"error".to_vec()));
assert_eq!(result.get("result.error"), Some(&b"not found".to_vec()));
}
#[test]
fn custom_handler_receives_input() {
let stub = StubThrough::new("test", &["user."], |cmd, input| {
let mut t = Tree::new();
t.insert("result.status".into(), b"success".to_vec());
t.insert("result.command_type".into(), cmd.as_bytes().to_vec());
if let Some(v) = input.get("command.payload") {
t.insert("result.payload".into(), v.clone());
}
Ok(t)
});
let mut input = Tree::new();
input.insert("command.type".into(), b"user.create".to_vec());
input.insert("command.payload".into(), b"test-data".to_vec());
let result = stub.dispatch(&input).unwrap();
assert_eq!(result.get("result.payload"), Some(&b"test-data".to_vec()));
}
}