use axum::http::{StatusCode, header};
use axum::response::{IntoResponse, Response};
use serde::Serialize;
pub const ACTIVITY_PUB_CONTENT_TYPE: &str = "application/activity+json";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FederationJson<T>(pub T);
impl<T: Serialize> IntoResponse for FederationJson<T> {
fn into_response(self) -> Response {
match serde_json::to_vec(&self.0) {
Ok(body) => (
StatusCode::OK,
[(header::CONTENT_TYPE, ACTIVITY_PUB_CONTENT_TYPE)],
body,
)
.into_response(),
Err(err) => {
tracing::error!(target: "actpub::axum", %err, "FederationJson serialisation failed");
(
StatusCode::INTERNAL_SERVER_ERROR,
"JSON serialisation failed",
)
.into_response()
}
}
}
}
#[cfg(test)]
mod tests {
use axum::http::{StatusCode, header};
use axum::response::IntoResponse;
use http_body_util::BodyExt;
use serde_json::json;
use super::*;
#[tokio::test]
async fn responder_sets_activity_pub_content_type() {
let resp = FederationJson(json!({ "id": "https://example.com/u/1", "type": "Person" }))
.into_response();
assert_eq!(resp.status(), StatusCode::OK);
let ct = resp
.headers()
.get(header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.unwrap_or("");
assert_eq!(ct, ACTIVITY_PUB_CONTENT_TYPE);
}
#[tokio::test]
async fn responder_serialises_payload_as_canonical_json() {
let payload = json!({ "name": "Alice", "age": 30 });
let resp = FederationJson(payload.clone()).into_response();
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let back: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
assert_eq!(back, payload);
}
}