use bytes::Bytes;
use helix_core::effect::{DomainEventBytes, HttpResponse};
use helix_core::PortError;
use serde_json::{json, Value};
pub const EVENT_HTTP_UNAUTHORIZED: &str = "im:http:unauthorized";
pub const EVENT_NET_OFFLINE: &str = "im:net:offline";
pub fn http_outcome_event(
url: &str,
outcome: &Result<HttpResponse, PortError>,
) -> Option<DomainEventBytes> {
match outcome {
Ok(response) if response.status == 401 => encode_event(
EVENT_HTTP_UNAUTHORIZED,
json!({ "status": response.status, "url": url }),
),
Err(PortError::Transport(_)) => encode_event(EVENT_NET_OFFLINE, json!({ "url": url })),
_ => None,
}
}
fn encode_event(event: &'static str, data: Value) -> Option<DomainEventBytes> {
serde_json::to_vec(&json!({ "event": event, "data": data }))
.ok()
.map(|bytes| DomainEventBytes(Bytes::from(bytes)))
}
#[cfg(test)]
mod tests {
use super::*;
fn response(status: u16) -> Result<HttpResponse, PortError> {
Ok(HttpResponse {
status,
headers: Vec::new(),
body: Bytes::new(),
})
}
fn event_json(event: DomainEventBytes) -> Value {
match serde_json::from_slice(event.0.as_ref()) {
Ok(value) => value,
Err(error) => panic!("policy event must be valid JSON: {error}"),
}
}
#[test]
fn unauthorized_maps_to_im_event() {
let outcome = response(401);
let Some(event) = http_outcome_event("/api/cses/posts/create", &outcome) else {
panic!("401 must produce an event");
};
assert_eq!(
event_json(event),
json!({
"event": EVENT_HTTP_UNAUTHORIZED,
"data": { "status": 401, "url": "/api/cses/posts/create" }
})
);
}
#[test]
fn transport_error_maps_to_offline_event() {
let outcome = Err(PortError::Transport("connection refused".to_string()));
let Some(event) = http_outcome_event("/api/cses/sync", &outcome) else {
panic!("transport must produce an event");
};
assert_eq!(
event_json(event),
json!({
"event": EVENT_NET_OFFLINE,
"data": { "url": "/api/cses/sync" }
})
);
}
#[test]
fn unrelated_outcomes_do_not_emit() {
assert!(http_outcome_event("/forbidden", &response(403)).is_none());
assert!(http_outcome_event(
"/invalid",
&Err(PortError::Http("invalid method".to_string()))
)
.is_none());
}
}