1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum ProxyDiagnosticSeverity {
9 Info,
10 Warning,
11 Error,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17#[non_exhaustive]
18pub enum ProxyDiagnosticCode {
19 CredentialNotFound,
20 CredentialUnavailable,
21 OAuthClientIdUnavailable,
22 OAuthClientSecretUnavailable,
23 OAuthTokenExchangeFailed,
24}
25
26impl ProxyDiagnosticCode {
27 #[must_use]
29 pub fn as_str(self) -> &'static str {
30 match self {
31 Self::CredentialNotFound => "credential_not_found",
32 Self::CredentialUnavailable => "credential_unavailable",
33 Self::OAuthClientIdUnavailable => "oauth_client_id_unavailable",
34 Self::OAuthClientSecretUnavailable => "oauth_client_secret_unavailable",
35 Self::OAuthTokenExchangeFailed => "oauth_token_exchange_failed",
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct ProxyDiagnostic {
43 pub code: ProxyDiagnosticCode,
44 pub severity: ProxyDiagnosticSeverity,
45 pub route_prefix: String,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub credential_ref: Option<String>,
48 pub message: String,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub hint: Option<String>,
51}
52
53impl ProxyDiagnostic {
54 #[must_use]
55 pub fn warning(
56 code: ProxyDiagnosticCode,
57 route_prefix: impl Into<String>,
58 message: impl Into<String>,
59 ) -> Self {
60 Self {
61 code,
62 severity: ProxyDiagnosticSeverity::Warning,
63 route_prefix: route_prefix.into(),
64 credential_ref: None,
65 message: message.into(),
66 hint: None,
67 }
68 }
69
70 #[must_use]
71 pub fn with_credential_ref(mut self, credential_ref: impl Into<String>) -> Self {
72 self.credential_ref = Some(credential_ref.into());
73 self
74 }
75
76 #[must_use]
77 pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
78 self.hint = Some(hint.into());
79 self
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn proxy_diagnostic_serializes_stable_code() {
89 let diagnostic = ProxyDiagnostic::warning(
90 ProxyDiagnosticCode::CredentialNotFound,
91 "openai",
92 "Credential not found",
93 )
94 .with_credential_ref("op://vault/item/secret");
95 let json = serde_json::to_string(&diagnostic).expect("json");
96 assert!(json.contains("\"credential_not_found\""));
97 assert!(json.contains("\"openai\""));
98 }
99}