Skip to main content

cratestack_client_rust/subscription/
frames.rs

1//! A server frame's item or error, the same on every binding.
2
3use cratestack_core::CratestackErrorResponse;
4use cratestack_core::rpc::{RpcErrorBody, rpc_code_to_cratestack_error_code};
5use reqwest::StatusCode;
6use serde::de::DeserializeOwned;
7
8use crate::error::ClientError;
9use crate::remote_refusal::Challenges;
10
11/// An `err` frame as a `ClientError::Remote`, with the HTTP status its code
12/// stands for, so it reads like a refusal of a unary call.
13pub fn rpc_error_to_client(body: RpcErrorBody) -> ClientError {
14    let status = match body.code.as_str() {
15        "invalid_argument" => StatusCode::BAD_REQUEST,
16        "unauthenticated" => StatusCode::UNAUTHORIZED,
17        "permission_denied" => StatusCode::FORBIDDEN,
18        "not_found" => StatusCode::NOT_FOUND,
19        "conflict" | "aborted" => StatusCode::CONFLICT,
20        "failed_precondition" => StatusCode::PRECONDITION_FAILED,
21        "resource_exhausted" => StatusCode::TOO_MANY_REQUESTS,
22        "unavailable" => StatusCode::SERVICE_UNAVAILABLE,
23        _ => StatusCode::INTERNAL_SERVER_ERROR,
24    };
25    let message = body.message.clone();
26    // The same members as the REST envelope (`code`, `message`, `details`,
27    // the extras), so the round trip loses nothing.
28    // The envelope speaks the REST code names, so the RPC code is translated
29    // on the way in; `RpcErrorBody::from_cratestack_response` translates it
30    // back, and the code a caller reads is the one the server sent.
31    let error = serde_json::to_value(&body)
32        .map(|mut value| {
33            value["code"] = rpc_code_to_cratestack_error_code(&body.code).into();
34            value
35        })
36        .and_then(serde_json::from_value::<CratestackErrorResponse>)
37        .ok()
38        .map(Box::new);
39    ClientError::Remote {
40        status,
41        error,
42        message,
43        challenges: Challenges::default(),
44    }
45}
46
47/// An item frame's value as `T`.
48pub(super) fn item<T: DeserializeOwned>(value: serde_json::Value) -> Result<T, ClientError> {
49    serde_json::from_value(value)
50        .map_err(|error| ClientError::InvalidResponse(format!("subscription item: {error}")))
51}
52
53#[cfg(test)]
54mod tests;