11use backon:: ExponentialBuilder ;
22use backon:: Retryable ;
3+ use chroma_api_types:: ErrorResponse ;
34use chroma_error:: ChromaValidationError ;
45use chroma_types:: Collection ;
56use parking_lot:: Mutex ;
@@ -25,6 +26,8 @@ const USER_AGENT: &str = concat!(
2526pub enum ChromaClientError {
2627 #[ error( "Request error: {0:?}" ) ]
2728 RequestError ( #[ from] reqwest:: Error ) ,
29+ #[ error( "API error: {0:?} ({1})" ) ]
30+ ApiError ( String , reqwest:: StatusCode ) ,
2831 #[ error( "Could not resolve database ID: {0}" ) ]
2932 CouldNotResolveDatabaseId ( String ) ,
3033 #[ error( "Serialization/Deserialization error: {0}" ) ]
@@ -397,7 +400,24 @@ impl ChromaClient {
397400 Ok ( response) => response,
398401 Err ( ( err, maybe_response) ) => {
399402 if let Some ( response) = maybe_response {
400- let json = response. json :: < serde_json:: Value > ( ) . await ?;
403+ let status = response. status ( ) ;
404+ let text = response. text ( ) . await . unwrap_or_default ( ) ;
405+ let json = match serde_json:: from_str :: < serde_json:: Value > ( & text) {
406+ Ok ( json) => json,
407+ Err ( _) => {
408+ tracing:: trace!(
409+ url = %url,
410+ method =? method,
411+ "Received non-JSON error response: {}" ,
412+ text
413+ ) ;
414+
415+ return Err ( ChromaClientError :: ApiError (
416+ format ! ( "Non-JSON error response: {}" , text) ,
417+ status,
418+ ) ) ;
419+ }
420+ } ;
401421
402422 if tracing:: enabled!( tracing:: Level :: TRACE ) {
403423 tracing:: trace!(
@@ -407,6 +427,13 @@ impl ChromaClient {
407427 serde_json:: to_string_pretty( & json) . unwrap_or_else( |_| "<failed to serialize>" . to_string( ) )
408428 ) ;
409429 }
430+
431+ if let Ok ( api_error) = serde_json:: from_value :: < ErrorResponse > ( json) {
432+ return Err ( ChromaClientError :: ApiError (
433+ format ! ( "{}: {}" , api_error. error, api_error. message) ,
434+ status,
435+ ) ) ;
436+ }
410437 }
411438
412439 return Err ( ChromaClientError :: RequestError ( err) ) ;
@@ -617,6 +644,39 @@ mod tests {
617644 assert_eq ! ( mock. calls( ) , 2 ) ;
618645 }
619646
647+ #[ tokio:: test]
648+ #[ test_log:: test]
649+ async fn test_live_cloud_parses_error ( ) {
650+ with_client ( |client| async move {
651+ client
652+ . create_collection (
653+ CreateCollectionRequest :: builder ( )
654+ . name ( "foo" . to_string ( ) )
655+ . build ( ) ,
656+ )
657+ . await
658+ . unwrap ( ) ;
659+
660+ let err = client
661+ . create_collection (
662+ CreateCollectionRequest :: builder ( )
663+ . name ( "foo" . to_string ( ) )
664+ . build ( ) ,
665+ )
666+ . await
667+ . unwrap_err ( ) ;
668+
669+ match err {
670+ ChromaClientError :: ApiError ( msg, status) => {
671+ assert_eq ! ( status, StatusCode :: CONFLICT ) ;
672+ assert ! ( msg. contains( "already exists" ) ) ;
673+ }
674+ _ => panic ! ( "Expected ApiError" ) ,
675+ } ;
676+ } )
677+ . await ;
678+ }
679+
620680 #[ tokio:: test]
621681 #[ test_log:: test]
622682 async fn test_live_cloud_list_collections ( ) {
0 commit comments