module Moped module Errors # Mongo's exceptions are sparsely documented, but this is the most accurate # source of information on error codes. ERROR_REFERENCE = "https://github.com/mongodb/mongo/blob/master/docs/errors.md" # Raised when the connection pool is saturated and no new connection is # reaped during the wait time. class PoolSaturated < RuntimeError; end # Raised when attempting to checkout a connection on a thread that already # has a connection checked out. class ConnectionInUse < RuntimeError; end # Raised when attempting to checkout a pinned connection from the pool but # it is already in use by another object on the same thread. class PoolTimeout < RuntimeError; end # Generic error class for exceptions related to connection failures. class ConnectionFailure < StandardError; end # Raised when a database name is invalid. class InvalidDatabaseName < StandardError; end # Raised when a Mongo URI is invalid. class InvalidMongoURI < StandardError; end # Raised when providing an invalid string from an object id. class InvalidObjectId < StandardError # Create the new error. # # @example Create the new error. # InvalidObjectId.new("test") # # @param [ String ] string The provided id. # # @since 1.0.0 def initialize(string) super("'#{string}' is not a valid object id.") end end # Generic error class for exceptions generated on the remote MongoDB # server. class MongoError < StandardError # @attribute [r] details The details about the error. # @attribute [r] command The command that generated the error. attr_reader :details, :command # Create a new operation failure exception. # # @example Create the new error. # MongoError.new(command, details) # # @param [ Object ] command The command that generated the error. # @param [ Hash ] details The details about the error. # # @since 1.0.0 def initialize(command, details) @command, @details = command, details super(build_message) end private # Build the error message. # # @api private # # @example Build the message. # error.build_message # # @return [ String ] The message. # # @since 1.0.0 def build_message "The operation: #{command.inspect}\n#{error_message}" end # Get the error message. # # @api private # # @example Get the error message. # error.error_message # # @return [ String ] The message. # # @since 1.0.0 def error_message err = details["err"] || details["errmsg"] || details["$err"] if code = details["code"] "failed with error #{code}: #{err.inspect}\n\n" << "See #{ERROR_REFERENCE}\nfor details about this error." elsif code = details["assertionCode"] assertion = details["assertion"] "failed with error #{code}: #{assertion.inspect}\n\n" << "See #{ERROR_REFERENCE}\nfor details about this error." else "failed with error #{err.inspect}" end end end # Classes of errors that should not disconnect connections. class DoNotDisconnect < MongoError; end # Classes of errors that could be caused by a replica set reconfiguration. class PotentialReconfiguration < MongoError # Not master error codes. NOT_MASTER = [ 13435, 13436, 10009, 15986, 83 ] # Error codes received around reconfiguration CONNECTION_ERRORS_RECONFIGURATION = [ 15988, 10276, 11600, 9001, 13639, 10009, 11002, 7 ] # Replica set reconfigurations can be either in the form of an operation # error with code 13435, or with an error message stating the server is # not a master. (This encapsulates codes 10054, 10056, 10058) def reconfiguring_replica_set? err = details["err"] || details["errmsg"] || details["$err"] || "" NOT_MASTER.include?(details["code"]) || err.include?("not master") end def connection_failure? err = details["err"] || details["errmsg"] || details["$err"] || "" CONNECTION_ERRORS_RECONFIGURATION.include?(details["code"]) || err.include?("could not get last error") || err.include?("connection attempt failed") end # Is the error due to a namespace not being found? # # @example Is the namespace not found? # error.ns_not_found? # # @return [ true, false ] If the namespace was not found. # # @since 2.0.0 def ns_not_found? details["errmsg"] == "ns not found" end # Is the error due to a namespace not existing? # # @example Doest the namespace not exist? # error.ns_not_exists? # # @return [ true, false ] If the namespace was not found. # # @since 2.0.0 def ns_not_exists? details["errmsg"] =~ /namespace does not exist/ end end # Exception raised when authentication fails. class AuthenticationFailure < DoNotDisconnect; end # Exception class for exceptions generated as a direct result of an # operation, such as a failed insert or an invalid command. class OperationFailure < PotentialReconfiguration; end # Exception raised on invalid queries. class QueryFailure < PotentialReconfiguration; end # Exception raised if the cursor could not be found. class CursorNotFound < DoNotDisconnect def initialize(operation, cursor_id) super(operation, {"errmsg" => "cursor #{cursor_id} not found"}) end end # @api private # # Internal exception raised by Node#ensure_primary and captured by # Cluster#with_primary. class ReplicaSetReconfigured < DoNotDisconnect; end # Tag applied to unhandled exceptions on a node. module SocketError; end end end