systemprompt_agent/services/a2a_server/errors/
jsonrpc.rs1use crate::models::a2a::jsonrpc::NumberOrString;
12use axum::http::StatusCode;
13use serde_json::{Value, json};
14use systemprompt_logging::LogLevel;
15use systemprompt_traits::RepositoryError;
16
17pub fn classify_database_error(error: &RepositoryError) -> String {
18 let error_str = error.to_string();
19
20 if error_str.contains("FOREIGN KEY constraint failed") {
21 format!(
22 "Database constraint error: Referenced entity does not exist - {}",
23 error
24 )
25 } else if error_str.contains("UNIQUE constraint failed") {
26 format!("Database constraint error: Duplicate entry - {error}")
27 } else if error_str.contains("NOT NULL constraint failed") {
28 format!(
29 "Database constraint error: Required field missing - {}",
30 error
31 )
32 } else {
33 format!("Database error: {error}")
34 }
35}
36
37#[derive(Debug)]
38pub struct JsonRpcErrorBuilder {
39 code: i32,
40 message: String,
41 data: Option<Value>,
42 log_message: Option<String>,
43 log_level: LogLevel,
44}
45
46impl JsonRpcErrorBuilder {
47 pub fn new(code: i32, message: impl Into<String>) -> Self {
48 Self {
49 code,
50 message: message.into(),
51 data: None,
52 log_message: None,
53 log_level: LogLevel::Error,
54 }
55 }
56
57 pub fn with_data(mut self, data: Value) -> Self {
58 self.data = Some(data);
59 self
60 }
61
62 pub fn with_log(mut self, message: impl Into<String>, level: LogLevel) -> Self {
63 self.log_message = Some(message.into());
64 self.log_level = level;
65 self
66 }
67
68 pub fn log_error(mut self, message: impl Into<String>) -> Self {
69 self.log_message = Some(message.into());
70 self.log_level = LogLevel::Error;
71 self
72 }
73
74 pub fn log_warn(mut self, message: impl Into<String>) -> Self {
75 self.log_message = Some(message.into());
76 self.log_level = LogLevel::Warn;
77 self
78 }
79
80 pub fn build(self, request_id: &NumberOrString) -> Value {
81 if let Some(log_msg) = self.log_message {
82 match self.log_level {
83 LogLevel::Error => {
84 tracing::error!(topic = "a2a_jsonrpc", "{}", log_msg);
85 },
86 LogLevel::Warn => {
87 tracing::warn!(topic = "a2a_jsonrpc", "{}", log_msg);
88 },
89 LogLevel::Info => {
90 tracing::info!(topic = "a2a_jsonrpc", "{}", log_msg);
91 },
92 LogLevel::Debug => {
93 tracing::debug!(topic = "a2a_jsonrpc", "{}", log_msg);
94 },
95 LogLevel::Trace => {
96 tracing::trace!(topic = "a2a_jsonrpc", "{}", log_msg);
97 },
98 }
99 }
100
101 let mut error = json!({
102 "code": self.code,
103 "message": self.message
104 });
105
106 if let Some(data) = self.data {
107 error["data"] = data;
108 }
109
110 json!({
111 "jsonrpc": "2.0",
112 "error": error,
113 "id": request_id
114 })
115 }
116
117 pub fn build_with_status(self, request_id: &NumberOrString) -> (StatusCode, Value) {
118 let status = match self.code {
119 -32600 => StatusCode::BAD_REQUEST,
120 -32601 => StatusCode::NOT_FOUND,
121 -32602 => StatusCode::BAD_REQUEST,
122 -32603 => StatusCode::INTERNAL_SERVER_ERROR,
123 -32700 => StatusCode::BAD_REQUEST,
124 _ => StatusCode::INTERNAL_SERVER_ERROR,
125 };
126
127 (status, self.build(request_id))
128 }
129
130 pub fn invalid_request() -> Self {
131 Self::new(-32600, "Invalid Request")
132 }
133
134 pub fn method_not_found() -> Self {
135 Self::new(-32601, "Method not found")
136 }
137
138 pub fn invalid_params() -> Self {
139 Self::new(-32602, "Invalid params")
140 }
141
142 pub fn internal_error() -> Self {
143 Self::new(-32603, "Internal error")
144 }
145
146 pub fn parse_error() -> Self {
147 Self::new(-32700, "Parse error")
148 }
149
150 pub fn unauthorized(reason: impl Into<String>) -> Self {
151 Self::new(-32600, "Unauthorized").with_data(json!({
152 "reason": reason.into()
153 }))
154 }
155
156 pub fn forbidden(reason: impl Into<String>) -> Self {
157 Self::new(-32600, "Forbidden").with_data(json!({
158 "reason": reason.into()
159 }))
160 }
161}
162
163pub fn unauthorized_response(
164 reason: impl Into<String>,
165 request_id: &NumberOrString,
166) -> (StatusCode, Value) {
167 let reason_str = reason.into();
168 (
169 StatusCode::UNAUTHORIZED,
170 JsonRpcErrorBuilder::unauthorized(&reason_str)
171 .log_warn(&reason_str)
172 .build(request_id),
173 )
174}
175
176pub fn forbidden_response(
177 reason: impl Into<String>,
178 request_id: &NumberOrString,
179) -> (StatusCode, Value) {
180 let reason_str = reason.into();
181 (
182 StatusCode::FORBIDDEN,
183 JsonRpcErrorBuilder::forbidden(&reason_str)
184 .log_warn(&reason_str)
185 .build(request_id),
186 )
187}