Skip to main content

cratestack_axum/rpc/
subscribe_error.rs

1//! Errors from `GET /rpc/subscribe/{op_id}` before its stream starts:
2//! always a JSON [`RpcErrorBody`] with the error's own status, whatever the
3//! RPC codec.
4//!
5//! The SSE binding is JSON throughout (`super::sse`'s module doc), and its
6//! clients send `Accept: text/event-stream`, which names no RPC codec: run
7//! through the unary encoder's content negotiation, every refusal came back
8//! as `406` instead of the `401`/`403` an `EventSource` client must act on.
9//! A refusal's `WWW-Authenticate` challenges are kept (cratestack#1208).
10
11use axum::http::{HeaderValue, header};
12use axum::response::Response;
13use cratestack_core::CratestackError;
14use cratestack_core::rpc::RpcErrorBody;
15
16/// The subscribe endpoint's error response.
17pub fn encode_subscribe_error(error: &CratestackError) -> Response {
18    let body = serde_json::to_vec(&RpcErrorBody::from_cratestack(error))
19        .unwrap_or_else(|_| br#"{"code":"internal","message":"encode failure"}"#.to_vec());
20    let mut response = Response::new(axum::body::Body::from(body));
21    *response.status_mut() = error.status_code();
22    response.headers_mut().insert(
23        header::CONTENT_TYPE,
24        HeaderValue::from_static("application/json"),
25    );
26    crate::auth_challenge::attach(response, error.auth_challenges())
27}
28
29#[cfg(test)]
30mod tests {
31    use axum::body::to_bytes;
32    use axum::http::{StatusCode, header};
33    use cratestack_core::CratestackError;
34
35    use super::encode_subscribe_error;
36
37    #[tokio::test]
38    async fn a_refusal_keeps_its_status_and_is_json() {
39        let response = encode_subscribe_error(&CratestackError::Unauthorized("sign in".to_owned()));
40        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
41        assert_eq!(response.headers()[header::CONTENT_TYPE], "application/json");
42        let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
43        let body: serde_json::Value = serde_json::from_slice(&body).unwrap();
44        assert_eq!(body["code"], "unauthenticated");
45    }
46}