imagegen_bridge_core/
error.rs1use std::collections::BTreeMap;
4
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
11#[serde(rename_all = "snake_case")]
12pub enum ErrorCode {
13 InvalidRequest,
15 UnsupportedCapability,
17 Configuration,
19 Authentication,
21 PermissionDenied,
23 SafetyRejected,
25 RateLimited,
27 Overloaded,
29 Timeout,
31 Cancelled,
33 Upstream,
35 Protocol,
37 Input,
39 Artifact,
41 Session,
43 IdempotencyConflict,
45 Internal,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Error)]
51#[error("{code:?}: {message}")]
52#[serde(deny_unknown_fields)]
53pub struct BridgeError {
54 pub code: ErrorCode,
56 pub message: String,
58 pub retryable: bool,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub provider: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub upstream_request_id: Option<String>,
66 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
68 pub details: BTreeMap<String, serde_json::Value>,
69}
70
71impl BridgeError {
72 #[must_use]
74 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
75 Self {
76 code,
77 message: message.into(),
78 retryable: false,
79 provider: None,
80 upstream_request_id: None,
81 details: BTreeMap::new(),
82 }
83 }
84
85 #[must_use]
87 pub fn safety_rejected(message: impl Into<String>) -> Self {
88 Self::new(ErrorCode::SafetyRejected, message)
89 .with_detail("safety_category", "content_policy")
90 .with_detail("recovery", "revise_prompt_or_inputs")
91 .with_detail("retry_same_request", false)
92 .with_detail("safety_controls_relaxed", false)
93 }
94
95 #[must_use]
97 pub const fn retryable(mut self, value: bool) -> Self {
98 self.retryable = value;
99 self
100 }
101
102 #[must_use]
104 pub fn with_provider(mut self, provider: impl Into<String>) -> Self {
105 self.provider = Some(provider.into());
106 self
107 }
108
109 #[must_use]
111 pub fn with_detail(mut self, key: impl Into<String>, value: impl Serialize) -> Self {
112 if let Ok(value) = serde_json::to_value(value) {
113 self.details.insert(key.into(), value);
114 }
115 self
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn safety_rejections_include_actionable_non_bypass_guidance() {
125 let error = BridgeError::safety_rejected("request rejected");
126 assert_eq!(error.code, ErrorCode::SafetyRejected);
127 assert!(!error.retryable);
128 assert_eq!(error.details["recovery"], "revise_prompt_or_inputs");
129 assert_eq!(error.details["retry_same_request"], false);
130 assert_eq!(error.details["safety_controls_relaxed"], false);
131 }
132}