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