dani-garcia/vaultwarden · error · Error

Can't convert to number

Error message

Can't convert to number

What it means

NumberOrString is the type for request/config fields that arrive either as JSON numbers or numeric strings. into_i32() parses the string form with str::parse::<i32>(); a non-numeric string or a value outside i32 range produces this crate::Error carrying the underlying ParseIntError text. Callers such as two-factor activation (data.r#type.into_i32()?) and WebAuthn operations (data.id.into_i32()?) turn it into a failed request.

Source

Thrown at src/util.rs:710

        match self {
            NumberOrString::Number(n) => n.to_string(),
            NumberOrString::String(s) => s,
        }
    }

    #[expect(clippy::wrong_self_convention)]
    pub fn into_i32(&self) -> Result<i32, crate::Error> {
        use std::num::ParseIntError as PIE;
        match self {
            NumberOrString::Number(n) => {
                if let Some(n) = n.to_i32() {
                    Ok(n)
                } else {
                    err!("Number does not fit in i32")
                }
            }
            NumberOrString::String(s) => {
                s.parse().map_err(|e: PIE| crate::Error::new("Can't convert to number", e.to_string()))
            }
        }
    }

    #[expect(clippy::wrong_self_convention)]
    pub fn into_i64(&self) -> Result<i64, crate::Error> {
        use std::num::ParseIntError as PIE;
        match self {
            NumberOrString::Number(n) => Ok(*n),
            NumberOrString::String(s) => {
                s.parse().map_err(|e: PIE| crate::Error::new("Can't convert to number", e.to_string()))
            }
        }
    }
}

//
// Retry methods

View on GitHub (pinned to 0cefa4cca7)

Solutions

  1. Send the field as a plain JSON number, or a clean numeric string like "1"
  2. Strip spaces, unit suffixes, thousand separators, and decimals from the value
  3. If the value legitimately exceeds i32, it belongs in an i64-backed field/endpoint (e.g. file sizes)

Example fix

// before
{ "type": "authenticator" }
// after
{ "type": 0 }
Defensive patterns

Strategy: validation

Validate before calling

// Client-side pre-submit check
function assertI32(value, field) {
  const n = typeof value === 'number' ? value : Number.parseInt(String(value), 10);
  if (!Number.isInteger(n) || n < -2147483648 || n > 2147483647) {
    throw new Error(`${field} must be an integer within i32 range, got: ${JSON.stringify(value)}`);
  }
}

Type guard

const isI32Like = (v) =>
  typeof v === 'number'
    ? Number.isInteger(v) && v >= -2147483648 && v <= 2147483647
    : /^-?\d+$/.test(String(v).trim()) && Number(v) >= -2147483648 && Number(v) <= 2147483647;

Prevention

When it happens

Trigger: POST /two-factor/... with "type": "abc" or "1x"; enabling/managing WebAuthn credentials with "id": "12x" or an out-of-range value like "99999999999"; any client sending these integer fields as unparsable strings or exceeding i32 range (-2147483648..2147483647).

Common situations: Third-party clients or scripts hand-crafting JSON with quoted numbers; payloads copied from docs containing placeholders; client libraries that stringify every value.

Related errors


AI-assisted analysis of dani-garcia/vaultwarden@0cefa4cca7 (2026-08-16). Data as JSON: /api/errors/27b73cf79f25c84d. Report an issue: GitHub.