fix(vector_stores): translate the two MongoDB driver errors that still reached callers as 500s

A connection string whose password holds an unescaped '/' makes pymongo's URI
parser raise a plain ValueError, not a PyMongoError, and a URI with no
credentials at all makes Atlas close the connection, which surfaces as
AutoReconnect. Neither was handled, so both fell through to litellm's generic
wrapper and were served as 500s with a traceback for what are routine typos.
Both now return a 400 naming the cause. The ConnectionFailure branch sits after
the ServerSelectionTimeoutError and NetworkTimeout branches, which subclass it,
and two ordering tests pin that.
This commit is contained in:
Yuneng Jiang 2026-09-02 14:55:13 -07:00
parent 52de1bb1d3
commit cfe247ebfe
No known key found for this signature in database
2 changed files with 52 additions and 0 deletions

View file

@ -181,6 +181,7 @@ def translate_mongo_error(error: Exception, index_name: str, database: str, coll
try:
from pymongo.errors import (
ConfigurationError,
ConnectionFailure,
ExecutionTimeout,
InvalidOperation,
NetworkTimeout,
@ -202,6 +203,14 @@ def translate_mongo_error(error: Exception, index_name: str, database: str, coll
f"The MongoDB vector search against '{database}.{collection}' timed out before returning. "
f"Driver detail: {error}"
)
# ServerSelectionTimeoutError and NetworkTimeout both sit under ConnectionFailure, so this
# only sees what those two branches left: a dropped or refused connection
if isinstance(error, ConnectionFailure):
return config_error(
f"The connection to '{database}.{collection}' was refused or dropped. On Atlas this is "
"usually a connection string with no username and password, or a TLS failure. Confirm "
f"the URI is the one Atlas shows under Connect, Drivers. Driver detail: {error}"
)
if isinstance(error, OperationFailure):
code: Final = error.code
detail: Final = str(error).lower()
@ -247,4 +256,11 @@ def translate_mongo_error(error: Exception, index_name: str, database: str, coll
)
if isinstance(error, InvalidOperation):
return config_error(f"The MongoDB client was already closed or is unusable. Driver detail: {error}")
# pymongo's URI parser raises a plain ValueError, not a PyMongoError, for a password holding an
# unescaped '/', which would otherwise reach the caller as a 500
if isinstance(error, ValueError):
return config_error(
"mongodb_connection_string could not be parsed. A username or password containing "
f"'@', '/', ':' or '%' has to be percent-encoded per RFC 3986. Driver detail: {error}"
)
return error

View file

@ -759,6 +759,42 @@ class TestErrorTranslation:
assert "rejected the credentials" in str(translated)
def test_a_dropped_connection_is_a_400_not_an_unhandled_driver_error(self):
"""AutoReconnect sits under ConnectionFailure alongside the two timeout classes, and Atlas
answers a URI with no credentials by closing the connection rather than failing auth. Left
untranslated it is not a litellm exception type, so it reaches the caller as a 500."""
from pymongo.errors import AutoReconnect
translated = self._translate(AutoReconnect("connection closed"))
assert isinstance(translated, BadRequestError)
assert "refused or dropped" in str(translated)
assert "no username and password" in str(translated)
def test_server_selection_timeout_still_wins_over_the_connection_branch(self):
from pymongo.errors import ServerSelectionTimeoutError
translated = self._translate(ServerSelectionTimeoutError("no servers"))
assert isinstance(translated, Timeout)
assert "refused or dropped" not in str(translated)
def test_network_timeout_still_wins_over_the_connection_branch(self):
from pymongo.errors import NetworkTimeout
translated = self._translate(NetworkTimeout("socket timed out"))
assert isinstance(translated, Timeout)
assert "refused or dropped" not in str(translated)
def test_an_unescaped_password_character_is_a_400_not_a_500(self):
"""pymongo's URI parser raises a plain ValueError, not a PyMongoError, when a password
holds an unescaped '/'. That is a routine mistake and it must not be a 500."""
translated = self._translate(ValueError("Port contains non-digit characters"))
assert isinstance(translated, BadRequestError)
assert "percent-encoded" in str(translated)
def test_unauthorized_points_at_the_database_user_permissions(self):
from pymongo.errors import OperationFailure