#![allow(dead_code)]
use crate::mqtt::ProtocolExitCode;
use rhai::{Dynamic, EvalAltResult, Map};
use std::cell::Cell;
thread_local! {
static LAST_PROTOCOL_EXIT_CODE: Cell<Option<i32>> = const { Cell::new(None) };
}
pub fn anyhow_to_rhai(e: anyhow::Error) -> Box<EvalAltResult> {
let mut code: Option<i32> = None;
if let Some(c) = e.downcast_ref::<ProtocolExitCode>() {
code = Some(*c as i32);
}
if code.is_none() {
for cause in e.chain() {
if let Some(c) = cause.downcast_ref::<ProtocolExitCode>() {
code = Some(*c as i32);
break;
}
if let Some(rq) = cause.downcast_ref::<reqwest::Error>() {
if rq.is_timeout() {
code = Some(28);
break;
}
if rq.is_connect() {
code = Some(7);
break;
}
}
}
}
if let Some(c) = code {
LAST_PROTOCOL_EXIT_CODE.with(|cell| cell.set(Some(c)));
}
format!("{e:#}").into()
}
pub fn take_protocol_exit_code() -> Option<i32> {
LAST_PROTOCOL_EXIT_CODE.with(|cell| cell.take())
}
pub fn clear_protocol_exit_code() {
LAST_PROTOCOL_EXIT_CODE.with(|cell| cell.set(None));
}
pub fn opts_get_str(opts: &Map, key: &str) -> Option<String> {
opts.get(key).and_then(|v| {
if v.is_string() {
Some(v.clone().into_string().unwrap_or_default())
} else {
None
}
})
}
pub fn opts_get_u64(opts: &Map, key: &str) -> Option<u64> {
opts.get(key).and_then(|v| v.as_int().ok()).and_then(|n| {
if n < 0 {
None
} else {
Some(n as u64)
}
})
}
pub fn opts_get_i64(opts: &Map, key: &str) -> Option<i64> {
opts.get(key).and_then(|v| v.as_int().ok())
}
pub fn opts_get_bool(opts: &Map, key: &str) -> Option<bool> {
opts.get(key).and_then(|v| v.as_bool().ok())
}
pub fn opts_clone_map(opts: &Map, key: &str) -> Option<Map> {
opts.get(key).and_then(|v| {
if v.is_map() {
v.clone().try_cast::<Map>()
} else {
None
}
})
}
pub fn opts_clone_array(opts: &Map, key: &str) -> Option<rhai::Array> {
opts.get(key).and_then(|v| {
if v.is_array() {
v.clone().try_cast::<rhai::Array>()
} else {
None
}
})
}
pub fn err(msg: impl Into<String>) -> Box<EvalAltResult> {
msg.into().into()
}
pub fn to_string(v: &Dynamic) -> String {
if v.is_string() {
v.clone().into_string().unwrap_or_default()
} else {
v.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::anyhow;
#[test]
fn anyhow_to_rhai_preserves_message() {
let e = anyhow!("boom").context("context");
let rhai_err = anyhow_to_rhai(e);
assert!(rhai_err.to_string().contains("boom"));
assert!(rhai_err.to_string().contains("context"));
}
#[test]
fn anyhow_to_rhai_stashes_protocol_exit_code() {
clear_protocol_exit_code();
let e = anyhow!("connection refused").context(ProtocolExitCode::CouldntConnect);
let _ = anyhow_to_rhai(e);
assert_eq!(take_protocol_exit_code(), Some(7));
}
#[test]
fn anyhow_to_rhai_without_tag_leaves_none() {
clear_protocol_exit_code();
let e = anyhow!("generic");
let _ = anyhow_to_rhai(e);
assert_eq!(take_protocol_exit_code(), None);
}
#[test]
fn opts_get_str_reads_value() {
let mut m = Map::new();
m.insert("method".into(), "POST".into());
assert_eq!(opts_get_str(&m, "method"), Some("POST".to_string()));
assert_eq!(opts_get_str(&m, "absent"), None);
}
#[test]
fn opts_get_u64_reads_non_negative_int() {
let mut m = Map::new();
m.insert("timeout_ms".into(), (500_i64).into());
m.insert("neg".into(), (-1_i64).into());
assert_eq!(opts_get_u64(&m, "timeout_ms"), Some(500));
assert_eq!(opts_get_u64(&m, "neg"), None);
assert_eq!(opts_get_u64(&m, "missing"), None);
}
#[test]
fn opts_get_bool_reads_value() {
let mut m = Map::new();
m.insert("insecure".into(), true.into());
assert_eq!(opts_get_bool(&m, "insecure"), Some(true));
assert_eq!(opts_get_bool(&m, "absent"), None);
}
#[test]
fn opts_clone_map_returns_nested() {
let mut inner = Map::new();
inner.insert("X-Foo".into(), "bar".into());
let mut m = Map::new();
m.insert("headers".into(), inner.into());
let cloned = opts_clone_map(&m, "headers").expect("headers is a map");
assert_eq!(opts_get_str(&cloned, "X-Foo"), Some("bar".to_string()));
}
}