⚠️ Errors

Errors / 错误

zero_auth never lets a raw Exception cross its public surface. Every failure is expressed through one of two mechanisms.

zero_auth 绝不允许裸 Exception 越过公共边界。每个失败都通过下面两种机制之一表达。

AppException / 异常类型

All domain failures extend AppException:

所有领域失败都继承 AppException:

sealed class AppException implements Exception {
  final String code;       // stable, machine-readable / 稳定、机器可读
  final String message;    // human-readable / 人类可读
  final Object? cause;     // original error, if any / 原始错误
}
 
final class AuthException extends AppException {
  // Base type for every auth failure / 所有认证失败的基类型
}
 
// Concrete subtypes — catch these to branch on what actually went wrong.
// 具体子类型——捕获它们即可按实际错误分支处理。
final class InvalidCredentialsException extends AuthException {} // invalid_credentials / 凭据无效
final class SessionExpiredException extends AuthException {}     // session_expired / 会话过期
final class NoActiveSessionException extends AuthException {}    // no_active_session / 无活动会话
final class RefreshTokenMissingException extends AuthException {} // refresh_token_missing / 缺少刷新令牌
final class UnexpectedAuthException extends AuthException {}     // unexpected_auth_failure / 意外失败

AuthStrategy authors opt into these subtypes simply by throwing an AuthException — or the bare AuthFail domain type — carrying the matching code; mapAuthFailure performs the mapping, preserving any vocabulary it does not recognise.

AuthStrategy 实现者只需抛出携带对应 code 的 AuthException —— 或裸的领域类型 AuthFail —— 即可参与映射;mapAuthFailure 负责转换,并保留它无法识别的自定义错误类型。

// Both of these map to InvalidCredentialsException / 两者都会映射为 InvalidCredentialsException
throw AuthException('Wrong password', code: 'invalid_credentials');
throw const AuthFail('Wrong password', code: 'invalid_credentials');
codeMapped type / 映射结果
invalid_credentialsInvalidCredentialsException
invalid_grant, invalid_refresh_token, token_expired, session_expiredSessionExpiredException
no_active_sessionNoActiveSessionException
refresh_token_missingRefreshTokenMissingException
anything else / 其它preserved as-is, or UnexpectedAuthException / 原样保留或包装为 UnexpectedAuthException

Codes raised by the manager itself (not your strategy) / 由管理器自身产生的 code:

codeRaised when / 何时触发
auth_flow_in_progressAnother login / register / loginWith is already running / 已有登录流程在执行
manager_disposedAn operation is called after dispose() / dispose() 后又调用操作

Overlapping authentication flows are rejected rather than racing: a second login() while one is in flight throws auth_flow_in_progress.

重叠的登录流程会被拒绝而非争抢:进行中再次 login() 会抛 auth_flow_in_progress。

AuthStrategy implementations should map transport/API errors into AuthException (or a custom AppException subclass) so callers get a stable code.

AuthStrategy 实现方应将传输层/API 错误映射为 AuthException(或自定义 AppException 子类),让调用方拿到稳定的 code。

Result<T> / 结果类型

For explicit, exception-free handling, use Result:

如需显式、无异常的处理,可使用 Result:

sealed class Result<T> {
  const factory Result.ok(T value) = Ok<T>;
  const factory Result.err(AppException error) = Err<T>;
}
final result = await auth.tryLogin(user, pass); // returns Result<AuthSession>
switch (result) {
  case Ok(:final value):
    /* authenticated / 已认证 */
  case Err(:final error):
    /* inspect error.code / 读取 error.code */
}

How failures surface / 失败如何呈现

SituationSurfaced as
login/register failsAuthState becomes AuthError(error) and the future throws AppException / 状态变为 AuthError,且 future 抛出 AppException
refresh failsAuthError(error) emitted, then Unauthenticated (unrecoverable) or back to Authenticated (transient) per the failure policy / 发出 AuthError,随后按失败策略转为 Unauthenticated(不可恢复)或回到 Authenticated(瞬时)
restore() finds nothingStays Unauthenticated (not an error) / 保持 Unauthenticated(不算错误)
restore() renewal fails transientlySession kept (the next validAccessToken() retries); only a terminal failure clears the store / 保留会话(下次 validAccessToken() 会重试);只有终局失败才清空存储
TokenStore.load() throwsMapped to AppException, treated as “no session” / 映射为 AppException,按”无会话”处理

Next Steps / 下一步