docs: def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False):
      """Parse and validate a MongoDB URI.
  
      Returns a dict of the form::
  
          {
              'nodelist': <list of (host, port) tuples>,
              'username': <username> or None,
              'password': <password> or None,
              'database': <database name> or None,
              'collection': <collection name> or None,
              'options': <dict of MongoDB URI options>
          }
  
      :Parameters:
          - `uri`: The MongoDB URI to parse.
          - `default_port`: The port number to use when one wasn't specified
            for a host in the URI.
          - `validate`: If ``True`` (the default), validate and normalize all
            options.
          - `warn` (optional): When validating, if ``True`` then will warn
            the user then ignore any invalid options or values. If ``False``,
            validation will error when options are unsupported or values are
            invalid.
  
      .. versionchanged:: 3.5
          Return the original value of the ``readPreference`` MongoDB URI option
          instead of the validated read preference mode.
  
      .. versionchanged:: 3.1
          ``warn`` added so invalid options can be ignored.
      """
      if not uri.startswith(SCHEME):
          raise InvalidURI("Invalid URI scheme: URI "
                           "must begin with '%s'" % (SCHEME,))
  
      scheme_free = uri[SCHEME_LEN:]
  
      if not scheme_free:
          raise InvalidURI("Must provide at least one hostname or IP.")
  
      user = None
      passwd = None
      dbase = None
      collection = None
      options = {}
  
      host_part, _, path_part = _partition(scheme_free, '/')
      if not host_part:
          host_part = path_part
          path_part = ""
  
      if not path_part and '?' in host_part:
          raise InvalidURI("A '/' is required between "
                           "the host list and any options.")
  
      if '@' in host_part:
          userinfo, _, hosts = _rpartition(host_part, '@')
          user, passwd = parse_userinfo(userinfo)
      else:
          hosts = host_part
  
      if '/' in hosts:
          raise InvalidURI("Any '/' in a unix domain socket must be"
                           " percent-encoded: %s" % host_part)
  
      hosts = unquote_plus(hosts)
      nodes = split_hosts(hosts, default_port=default_port)
  
      if path_part:
          if path_part[0] == '?':
              opts = unquote_plus(path_part[1:])
          else:
              dbase, _, opts = map(unquote_plus, _partition(path_part, '?'))
              if '.' in dbase:
                  dbase, collection = dbase.split('.', 1)
  
              if _BAD_DB_CHARS.search(dbase):
                  raise InvalidURI('Bad database name "%s"' % dbase)
  
          if opts:
              options = split_options(opts, validate, warn)
  
      if dbase is not None:
          dbase = unquote_plus(dbase)
      if collection is not None:
          collection = unquote_plus(collection)
  
      return {
          'nodelist': nodes,
          'username': user,
          'password': passwd,
          'database': dbase,
          'collection': collection,
          'options': options
      }