Skip to main content

rig_core/client/
verify.rs

1use crate::{http_client, provider_response, wasm_compat::WasmCompatSend};
2use thiserror::Error;
3
4/// Errors from provider client verification.
5///
6/// Inspect provider failures with [`Self::provider_response_body`],
7/// [`Self::provider_response_json`], and [`Self::provider_response_status`].
8///
9/// Note: no provider path currently constructs [`Self::ProviderResponse`] for
10/// verification; real verify failures surface as [`Self::HttpError`], which
11/// the helpers read. The variant is kept for symmetry with the other capability
12/// errors and for future provider paths that preserve a 2xx error envelope.
13#[derive(Debug, Error)]
14#[non_exhaustive]
15pub enum VerifyError {
16    #[error("invalid authentication")]
17    InvalidAuthentication,
18    #[error("provider error: {0}")]
19    ProviderError(String),
20    /// Raw error response preserved from the provider
21    #[error("provider response error: {0}")]
22    ProviderResponse(provider_response::ProviderResponseError),
23    #[error("http error: {0}")]
24    HttpError(
25        #[from]
26        #[source]
27        http_client::Error,
28    ),
29}
30
31crate::provider_response::impl_provider_response_helpers!(VerifyError);
32
33/// A provider client that can verify the configuration.
34/// Clone is required for conversions between client types.
35pub trait VerifyClient {
36    /// Verify the configuration.
37    fn verify(&self) -> impl Future<Output = Result<(), VerifyError>> + WasmCompatSend;
38}
39
40#[cfg(test)]
41mod provider_response_tests {
42    use super::*;
43    use http::StatusCode;
44
45    #[test]
46    fn verify_error_provider_response_helpers_with_preserved_json_body() {
47        let body = r#"{"error":{"message":"rate limited"}}"#;
48        let error = VerifyError::ProviderResponse(provider_response::ProviderResponseError {
49            status: None,
50            body: body.to_string(),
51        });
52
53        assert_eq!(error.provider_response_body(), Some(body));
54        assert_eq!(error.provider_response_status(), None);
55        assert_eq!(
56            error.provider_response_json().expect("valid JSON"),
57            Some(serde_json::json!({ "error": { "message": "rate limited" } }))
58        );
59    }
60
61    #[test]
62    fn verify_error_provider_response_helpers_with_http_non_success() {
63        let body = r#"{"error":{"message":"bad request"}}"#;
64        let error = VerifyError::HttpError(http_client::Error::InvalidStatusCodeWithMessage(
65            StatusCode::BAD_REQUEST,
66            body.to_string(),
67        ));
68
69        assert_eq!(error.provider_response_body(), Some(body));
70        assert_eq!(
71            error.provider_response_status(),
72            Some(StatusCode::BAD_REQUEST)
73        );
74        assert_eq!(
75            error.provider_response_json().expect("valid JSON"),
76            Some(serde_json::json!({ "error": { "message": "bad request" } }))
77        );
78    }
79
80    #[test]
81    fn verify_error_provider_response_helpers_with_preserved_plain_text_body() {
82        let error = VerifyError::ProviderResponse(provider_response::ProviderResponseError {
83            status: None,
84            body: "not json".to_string(),
85        });
86
87        assert_eq!(error.provider_response_body(), Some("not json"));
88        assert!(error.provider_response_json().is_err());
89    }
90
91    #[test]
92    fn verify_error_provider_error_is_not_a_provider_response() {
93        let error = VerifyError::ProviderError("internal diagnostic".to_string());
94
95        assert_eq!(error.provider_response_body(), None);
96        assert_eq!(error.provider_response_status(), None);
97        assert_eq!(error.provider_response_json().expect("no body"), None);
98    }
99
100    #[test]
101    fn verify_error_provider_response_helpers_with_unrelated_variant() {
102        let error = VerifyError::InvalidAuthentication;
103
104        assert_eq!(error.provider_response_body(), None);
105        assert_eq!(error.provider_response_status(), None);
106        assert_eq!(error.provider_response_json().expect("no body"), None);
107    }
108
109    #[tokio::test]
110    async fn verify_preserves_status_and_body_on_provider_error_response() {
111        use crate::client::VerifyClient;
112        use crate::providers::openai::Client;
113        use crate::test_utils::RecordingHttpClient;
114
115        let body = r#"{"error":{"message":"server exploded","type":"server_error"}}"#;
116        let http_client =
117            RecordingHttpClient::with_error_response(StatusCode::INTERNAL_SERVER_ERROR, body);
118        let client = Client::builder()
119            .api_key("test-key")
120            .http_client(http_client)
121            .build()
122            .expect("build client");
123
124        let error = client
125            .verify()
126            .await
127            .expect_err("verify should fail on a 500 response");
128
129        assert_eq!(
130            error.provider_response_status(),
131            Some(StatusCode::INTERNAL_SERVER_ERROR)
132        );
133        assert_eq!(error.provider_response_body(), Some(body));
134        let json = error
135            .provider_response_json()
136            .expect("raw body should be valid JSON")
137            .expect("parsed JSON should be present");
138        assert_eq!(json["error"]["type"], "server_error");
139    }
140}