t8y2/dbx · error

invalid TDengine username

Error message

invalid TDengine username

What it means

In build_from_fields, after the host is applied the driver sets the username on the parsed Url. If url::Url::set_username rejects the value (username containing characters illegal in a URL userinfo component, such as ':' or control characters), the driver maps it to 'invalid TDengine username'. Percent-encodable values are normally accepted, so this fires only for structurally invalid values.

Source

Thrown at agents/drivers/tdengine/src/config.rs:59

        .and_then(|mut segments| segments.find(|segment| !segment.is_empty()))
        .map(|segment| percent_decode_str(segment).decode_utf8_lossy().into_owned())
        .unwrap_or_default();
    Ok(BuiltDsn { value: url.into(), database })
}

fn build_from_fields(params: &ConnectParams) -> Result<Url> {
    let scheme = if params.ssl { "wss" } else { "ws" };
    let host = if params.host.trim().is_empty() { DEFAULT_HOST } else { params.host.trim() };
    let port = if params.port == 0 { DEFAULT_PORT } else { params.port };
    let username = if params.username.is_empty() { DEFAULT_USER } else { &params.username };
    let password = if params.password.is_empty() { DEFAULT_PASSWORD } else { &params.password };
    let mut url = Url::parse(&format!("{scheme}://{DEFAULT_HOST}:{port}/"))?;
    if let Ok(address) = host.parse::<IpAddr>() {
        url.set_ip_host(address).map_err(|_| anyhow::anyhow!("invalid TDengine host"))?;
    } else {
        url.set_host(Some(host)).map_err(|_| anyhow::anyhow!("invalid TDengine host"))?;
    }
    url.set_username(username).map_err(|_| anyhow::anyhow!("invalid TDengine username"))?;
    url.set_password(Some(password)).map_err(|_| anyhow::anyhow!("invalid TDengine password"))?;
    if !params.database.trim().is_empty() {
        url.set_path(&format!("/{}", params.database.trim()));
    }
    Ok(url)
}

fn apply_connection_fields(url: &mut Url, params: &ConnectParams) -> Result<()> {
    if url.username().is_empty() {
        let username = if params.username.is_empty() { DEFAULT_USER } else { &params.username };
        url.set_username(username).map_err(|_| anyhow::anyhow!("invalid TDengine username"))?;
    }
    if url.password().is_none() {
        let password = if params.password.is_empty() { DEFAULT_PASSWORD } else { &params.password };
        url.set_password(Some(password)).map_err(|_| anyhow::anyhow!("invalid TDengine password"))?;
    }
    if url.path().trim_matches('/').is_empty() && !params.database.trim().is_empty() {
        url.set_path(&format!("/{}", params.database.trim()));

View on GitHub (pinned to c0390bff16)

Solutions

  1. Move the password portion out of the username field into params.password.
  2. Ensure the username is a bare identifier (TDengine default is 'root').
  3. Trim whitespace and strip control characters from the username before building ConnectParams.

Example fix

// before
ConnectParams { username: "root:taosdata".into(), password: "".into(), ..Default::default() }
// after
ConnectParams { username: "root".into(), password: "taosdata".into(), ..Default::default() }
Defensive patterns

Strategy: validation

Validate before calling

// Rust
fn is_valid_username(u: &str) -> bool {
    !u.is_empty() && !u.contains(':') && u.chars().all(|c| !c.is_control())
}

Prevention

When it happens

Trigger: Calling build_dsn with a ConnectParams.username that Url::set_username cannot apply — typically a username containing a ':' (which separates userinfo from password), or raw non-encodable control characters.

Common situations: Users putting 'user:password' in the username field instead of splitting them into username/password, or copying credentials with hidden whitespace/control characters from a terminal or config file.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/19cf5ca2790d4d9d. Report an issue: GitHub.