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