use crate::prelude::ApiError;
use serde_json::Value;
use std::any::Any;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
pub type HandlerArgs = HashMap<String, String>;
pub type HandlerState = Option<Arc<dyn Any + Send + Sync>>;
pub type HandlerOutput = Value;
pub type HandlerFuture =
Pin<Box<dyn Future<Output = Result<HandlerOutput, ApiError>> + Send + 'static>>;
pub type HandlerFn = fn(HandlerArgs, HandlerState) -> HandlerFuture;
#[must_use]
pub fn extract_value(v: &Value) -> String {
match v {
Value::String(s) => s.clone(),
other => other.to_string(),
}
}
pub fn downcast_state<T: Any + Send + Sync + 'static>(
state: HandlerState,
) -> Result<Arc<T>, ApiError> {
let arc = state.ok_or_else(|| {
ApiError::internal_error(
"handler requires application state but none was injected",
"forge.state_missing",
)
})?;
arc.downcast::<T>().map_err(|_| {
ApiError::internal_error(
"handler state type mismatch (downcast failed)",
"forge.state_type_mismatch",
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn extract_value_string_is_raw() {
assert_eq!(extract_value(&json!("hi")), "hi");
assert_eq!(extract_value(&json!("Hello, world!")), "Hello, world!");
}
#[test]
fn extract_value_object_is_json() {
assert_eq!(extract_value(&json!({"a": 1})), r#"{"a":1}"#);
}
#[test]
fn extract_value_number_bool_null_array() {
assert_eq!(extract_value(&json!(42)), "42");
assert_eq!(extract_value(&json!(true)), "true");
assert_eq!(extract_value(&json!(null)), "null");
assert_eq!(extract_value(&json!([1, 2, 3])), "[1,2,3]");
}
#[test]
fn handler_types_are_send_static() {
fn assert_send_static<T: Send + 'static>() {}
assert_send_static::<HandlerFn>();
assert_send_static::<HandlerFuture>();
}
#[test]
fn handler_fn_is_constructible() {
let _f: HandlerFn = |_args, _state| Box::pin(async { Ok(Value::Null) });
}
#[test]
fn downcast_state_none_returns_internal_error() {
let result = downcast_state::<i32>(None);
assert!(result.is_err());
match result.unwrap_err() {
ApiError::Internal { error_id, .. } => {
assert_eq!(error_id, "forge.state_missing");
}
other => panic!("expected ApiError::Internal, got {other:?}"),
}
}
#[test]
fn downcast_state_wrong_type_returns_internal_error() {
let state: HandlerState = Some(Arc::new(42_i32));
let result = downcast_state::<String>(state);
assert!(result.is_err());
match result.unwrap_err() {
ApiError::Internal { error_id, .. } => {
assert_eq!(error_id, "forge.state_type_mismatch");
}
other => panic!("expected ApiError::Internal, got {other:?}"),
}
}
#[test]
fn downcast_state_correct_type_returns_arc() {
let state: HandlerState = Some(Arc::new(42_i32));
let arc = downcast_state::<i32>(state).expect("downcast to i32 must succeed");
assert_eq!(*arc, 42);
}
}