1use serde::{Deserialize, Serialize};
15
16use crate::error::{CoolError, CoolErrorResponse};
17
18pub const RPC_UNARY_PATH: &str = "/rpc/{op_id}";
21
22pub const RPC_BATCH_PATH: &str = "/rpc/batch";
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct RpcErrorBody {
30 pub code: String,
34 pub message: String,
36 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub details: Option<serde_json::Value>,
39}
40
41impl RpcErrorBody {
42 pub fn from_cool(error: &CoolError) -> Self {
43 Self {
44 code: rpc_code(error).to_owned(),
45 message: error.public_message().into_owned(),
46 details: None,
47 }
48 }
49
50 pub fn from_cool_response(response: CoolErrorResponse) -> Self {
55 let CoolErrorResponse {
56 code,
57 message,
58 details,
59 } = response;
60 Self {
61 code: cool_error_code_to_rpc_code(&code).to_owned(),
62 message,
63 details: details.map(cool_value_to_json),
64 }
65 }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct RpcRequest {
71 pub id: u64,
73 pub op: String,
76 pub input: serde_json::Value,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub idem: Option<String>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct RpcResponseFrame {
87 pub id: u64,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub output: Option<serde_json::Value>,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub error: Option<RpcErrorBody>,
92}
93
94impl RpcResponseFrame {
95 pub fn ok(id: u64, output: serde_json::Value) -> Self {
96 Self {
97 id,
98 output: Some(output),
99 error: None,
100 }
101 }
102
103 pub fn err(id: u64, error: &CoolError) -> Self {
104 Self {
105 id,
106 output: None,
107 error: Some(RpcErrorBody::from_cool(error)),
108 }
109 }
110}
111
112pub const fn rpc_code(error: &CoolError) -> &'static str {
114 match error {
115 CoolError::BadRequest(_)
116 | CoolError::NotAcceptable(_)
117 | CoolError::UnsupportedMediaType(_)
118 | CoolError::Codec(_)
119 | CoolError::Validation(_) => "invalid_argument",
120 CoolError::Unauthorized(_) => "unauthenticated",
121 CoolError::Forbidden(_) => "permission_denied",
122 CoolError::NotFound(_) => "not_found",
123 CoolError::Conflict(_) => "conflict",
124 CoolError::PreconditionFailed(_) => "failed_precondition",
125 CoolError::Database(_) | CoolError::DatabaseTyped(_) | CoolError::Internal(_) => "internal",
126 }
127}
128
129pub fn cool_error_code_to_rpc_code(code: &str) -> &'static str {
133 match code {
134 "BAD_REQUEST"
135 | "NOT_ACCEPTABLE"
136 | "UNSUPPORTED_MEDIA_TYPE"
137 | "VALIDATION_ERROR"
138 | "CODEC_ERROR" => "invalid_argument",
139 "UNAUTHORIZED" => "unauthenticated",
140 "FORBIDDEN" => "permission_denied",
141 "NOT_FOUND" => "not_found",
142 "CONFLICT" => "conflict",
143 "PRECONDITION_FAILED" => "failed_precondition",
144 "DATABASE_ERROR" | "INTERNAL_ERROR" => "internal",
145 _ => "internal",
146 }
147}
148
149fn cool_value_to_json(value: crate::Value) -> serde_json::Value {
150 serde_json::to_value(&value).unwrap_or(serde_json::Value::Null)
151}