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.

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.

Appendix: 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)