Keyboard shortcuts

Press โ† or โ†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Error Codes

xrat categorizes errors into application-level errors and test failure classifications.

AppError

AppError is the primary error type returned by command handlers and services.

VariantDescriptionUse Case
ConfigNotFoundConfig ID not found in databasexrat connect <id> with invalid ID
NoActiveSessionNo active proxy sessionxrat disconnect with no session
XraySpawnFailed to spawn Xray processXray binary not found or invalid
XrayExitedXray process exited unexpectedlyProcess crashed during startup
XrayStartupTimeoutXray port not ready within timeoutSlow startup or port conflict
DaemonNotRunningDaemon IPC socket not reachablexrat proxy start without daemon
DaemonConnectFailed to connect to daemon socketPermission denied or socket missing
DatabaseDatabase query or connection errorConnection failure or constraint violation
IoFilesystem I/O errorPermission denied or disk full
ConfigConfiguration file errorInvalid TOML or missing required field
MissingPostgresUserPostgreSQL user not configureddatabase.postgres.user is empty
MissingPostgresDatabaseNamePostgreSQL database name not configureddatabase.postgres.db_name is empty
InvalidConfigValueInvalid configuration valueUnknown enum variant or out-of-range
SerializationJSON serialization/deserialization errorInvalid JSON or schema mismatch
ProbeProbe test execution errorICMP ping command failed
ParseConfig link parsing errorInvalid URI format or unsupported scheme

Error Messages

Errors implement Display for user-friendly messages:

Error: config not found (id: 42)
Error: daemon socket not reachable at /home/user/.config/xrat/runtime/daemon.sock
Error: Xray process failed to start: No such file or directory (os error 2)

DbError

DbError represents database-specific errors.

VariantDescription
QuerySQL query execution error
PoolConnection pool acquisition error
ConnectionDatabase connection error
UniqueViolationDuplicate key violation (used for dedup)
ForeignKeyViolationReferential integrity violation
NotFoundExpected row not found
MigrationSchema migration error
ConfigDatabase configuration error

FailureKind

FailureKind classifies test stage failures. Used by the testing pipeline and displayed in test results.

CategoryDescriptionExample
DNSDNS resolution failednodename nor servname provided, or not known
TimeoutConnection or request timed outconnection timed out after 5000ms
RefusedConnection refusedConnection refused (os error 111)
UnreachableNetwork unreachableNo route to host (os error 113)
PermissionDeniedPermission deniedOperation not permitted
TLSTLS handshake failedtls: first record does not look like a TLS handshake
AuthAuthentication failedproxy authentication required
ProcessProxy process failed to startxray binary not found
ProxyProxy returned an error statusHTTP 503 Service Unavailable
UnknownUnclassified failureAny other error

Failure Classification Logic

TCP failures are classified by matching error strings:

#![allow(unused)]
fn main() {
fn classify_tcp_error(error: &io::Error) -> FailureKind {
    match error.kind() {
        io::ErrorKind::ConnectionRefused => FailureKind::Refused,
        io::ErrorKind::ConnectionReset => FailureKind::Refused,
        io::ErrorKind::TimedOut => FailureKind::Timeout,
        io::ErrorKind::ConnectionAborted => FailureKind::Timeout,
        io::ErrorKind::NotConnected => FailureKind::Unreachable,
        io::ErrorKind::AddrInUse => FailureKind::PermissionDenied,
        io::ErrorKind::AddrNotAvailable => FailureKind::PermissionDenied,
        io::ErrorKind::PermissionDenied => FailureKind::PermissionDenied,
        io::ErrorKind::HostUnreachable => FailureKind::Unreachable,
        io::ErrorKind::NetworkUnreachable => FailureKind::Unreachable,
        io::ErrorKind::InvalidInput => FailureKind::Unknown,
        _ => {
            let msg = error.to_string().to_lowercase();
            if msg.contains("dns") || msg.contains("resolve") {
                FailureKind::DNS
            } else {
                FailureKind::Unknown
            }
        }
    }
}
}

ConfigParseError

ConfigParseError is returned by the config parser when parsing share links.

VariantDescription
UrlInvalid URL format
JsonInvalid JSON (vmess://)
DecodeInvalid base64 payload
ParseIntInvalid numeric value
MissingAddressOrPortURI missing address or port
MissingBase64UserinfoURI missing base64-encoded userinfo
InvalidShadowsocksUserinfoInvalid Shadowsocks userinfo format
MissingRequiredFieldRequired field not found in JSON
UnsupportedSchemeUnknown protocol scheme

XrayProcessError

XrayProcessError is returned by the Xray process manager.

VariantDescription
TempFileErrorFailed to create temporary config file
SerializationErrorFailed to serialize config JSON
SpawnErrorFailed to spawn Xray process
StartupTimeoutXray failed to start within timeout
ProcessExitedXray exited unexpectedly (with stderr)
PortNotReadyInbound port not ready within timeout

ImportParseError

ImportParseError is returned by the import parser.

VariantDescription
InvalidShareLinkInput is not a valid share link
DecodeInvalid base64 decoding
JsonInvalid JSON
MissingSip008ServersSIP008 JSON missing servers array
MissingSip008FieldSIP008 server missing required field
XrayInvalid Xray JSON
ConfigInvalid config node

Error Handling Best Practices

CLI Commands

Command handlers return Result<(), AppError>:

#![allow(unused)]
fn main() {
pub async fn run(context: &AppContext, args: &ConnectArgs) -> Result<()> {
    let config = context.db.get_config(args.id).await?
        .ok_or(AppError::ConfigNotFound(args.id))?;
    // ...
}
}

Logging

Errors are logged at error level before returning:

#![allow(unused)]
fn main() {
if let Err(err) = run(&context, &args.command).await {
    tracing::error!(error = %err, "command failed");
    std::process::exit(1);
}
}

User-Facing Messages

Errors display actionable messages when possible:

Error: daemon socket not reachable
Hint: start the daemon with 'xrat daemon start'