use serde::{Deserialize, Serialize};
use crate::error::{CoolError, CoolErrorResponse};
pub const RPC_UNARY_PATH: &str = "/rpc/{op_id}";
pub const RPC_BATCH_PATH: &str = "/rpc/batch";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcErrorBody {
pub code: String,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
}
impl RpcErrorBody {
pub fn from_cool(error: &CoolError) -> Self {
Self {
code: rpc_code(error).to_owned(),
message: error.public_message().into_owned(),
details: None,
}
}
pub fn from_cool_response(response: CoolErrorResponse) -> Self {
let CoolErrorResponse {
code,
message,
details,
} = response;
Self {
code: cool_error_code_to_rpc_code(&code).to_owned(),
message,
details: details.map(cool_value_to_json),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcRequest {
pub id: u64,
pub op: String,
pub input: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub idem: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcResponseFrame {
pub id: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<RpcErrorBody>,
}
impl RpcResponseFrame {
pub fn ok(id: u64, output: serde_json::Value) -> Self {
Self {
id,
output: Some(output),
error: None,
}
}
pub fn err(id: u64, error: &CoolError) -> Self {
Self {
id,
output: None,
error: Some(RpcErrorBody::from_cool(error)),
}
}
}
pub const fn rpc_code(error: &CoolError) -> &'static str {
match error {
CoolError::BadRequest(_)
| CoolError::NotAcceptable(_)
| CoolError::UnsupportedMediaType(_)
| CoolError::Codec(_)
| CoolError::Validation(_) => "invalid_argument",
CoolError::Unauthorized(_) => "unauthenticated",
CoolError::Forbidden(_) => "permission_denied",
CoolError::NotFound(_) => "not_found",
CoolError::Conflict(_) => "conflict",
CoolError::PreconditionFailed(_) => "failed_precondition",
CoolError::Database(_) | CoolError::DatabaseTyped(_) | CoolError::Internal(_) => "internal",
}
}
pub fn cool_error_code_to_rpc_code(code: &str) -> &'static str {
match code {
"BAD_REQUEST"
| "NOT_ACCEPTABLE"
| "UNSUPPORTED_MEDIA_TYPE"
| "VALIDATION_ERROR"
| "CODEC_ERROR" => "invalid_argument",
"UNAUTHORIZED" => "unauthenticated",
"FORBIDDEN" => "permission_denied",
"NOT_FOUND" => "not_found",
"CONFLICT" => "conflict",
"PRECONDITION_FAILED" => "failed_precondition",
"DATABASE_ERROR" | "INTERNAL_ERROR" => "internal",
_ => "internal",
}
}
fn cool_value_to_json(value: crate::Value) -> serde_json::Value {
serde_json::to_value(&value).unwrap_or(serde_json::Value::Null)
}