package xerr import "google.golang.org/grpc/codes" const ( httpStatusBadRequest = 400 httpStatusUnauthorized = 401 httpStatusForbidden = 403 httpStatusNotFound = 404 httpStatusConflict = 409 httpStatusInternalServerError = 500 httpStatusBadGateway = 502 ) // Spec 是错误码注册表中的一条稳定契约。 // GRPCCode 是服务间传输语义;PublicCode/PublicMessage 是 gateway 对 App 暴露的 envelope。 type Spec struct { GRPCCode codes.Code HTTPStatus int PublicCode string PublicMessage string Retryable bool } var catalog = map[Code]Spec{ InvalidArgument: spec(codes.InvalidArgument, httpStatusBadRequest, InvalidArgument, "invalid argument"), NotFound: spec(codes.NotFound, httpStatusNotFound, NotFound, "not found"), Conflict: spec(codes.FailedPrecondition, httpStatusConflict, Conflict, "conflict"), RoomClosed: spec(codes.FailedPrecondition, httpStatusConflict, RoomClosed, "room closed"), Unauthorized: spec(codes.Unauthenticated, httpStatusUnauthorized, Unauthorized, "unauthorized"), PermissionDenied: spec(codes.PermissionDenied, httpStatusForbidden, PermissionDenied, "permission denied"), ProfileRequired: spec(codes.PermissionDenied, httpStatusForbidden, ProfileRequired, "profile required"), Unavailable: { GRPCCode: codes.Unavailable, HTTPStatus: httpStatusBadGateway, PublicCode: "UPSTREAM_ERROR", PublicMessage: "upstream service error", Retryable: true, }, Internal: spec(codes.Internal, httpStatusInternalServerError, Internal, "internal error"), AuthFailed: spec(codes.Unauthenticated, httpStatusUnauthorized, AuthFailed, "authentication failed"), AuthLoginBlocked: spec(codes.PermissionDenied, httpStatusForbidden, AuthLoginBlocked, "login blocked"), PasswordAlreadySet: spec(codes.AlreadyExists, httpStatusConflict, PasswordAlreadySet, "conflict"), UserDisabled: spec(codes.PermissionDenied, httpStatusForbidden, UserDisabled, "permission denied"), SessionExpired: spec(codes.Unauthenticated, httpStatusUnauthorized, SessionExpired, "unauthorized"), SessionRevoked: spec(codes.Unauthenticated, httpStatusUnauthorized, SessionRevoked, "unauthorized"), DisplayUserIDInvalid: spec(codes.InvalidArgument, httpStatusBadRequest, DisplayUserIDInvalid, "invalid argument"), DisplayUserIDExists: spec(codes.AlreadyExists, httpStatusConflict, DisplayUserIDExists, "conflict"), DisplayUserIDCooldown: spec(codes.FailedPrecondition, httpStatusConflict, DisplayUserIDCooldown, "conflict"), DisplayUserIDNotFound: spec(codes.NotFound, httpStatusNotFound, DisplayUserIDNotFound, "not found"), DisplayUserIDAllocateFailed: spec(codes.Internal, httpStatusInternalServerError, Internal, "internal error"), DisplayUserIDPrettyNotAvailable: spec(codes.FailedPrecondition, httpStatusConflict, DisplayUserIDPrettyNotAvailable, "conflict"), DisplayUserIDPrettyActive: spec(codes.FailedPrecondition, httpStatusConflict, DisplayUserIDPrettyActive, "conflict"), DisplayUserIDLeaseExpired: spec(codes.FailedPrecondition, httpStatusConflict, DisplayUserIDLeaseExpired, "conflict"), DisplayUserIDPaymentRequired: spec(codes.FailedPrecondition, httpStatusConflict, DisplayUserIDPaymentRequired, "conflict"), CountryChangeCooldown: spec(codes.FailedPrecondition, httpStatusConflict, CountryChangeCooldown, "conflict"), CountryNotFound: spec(codes.NotFound, httpStatusNotFound, CountryNotFound, "not found"), RegionNotFound: spec(codes.NotFound, httpStatusNotFound, RegionNotFound, "not found"), RegionCountryConflict: spec(codes.FailedPrecondition, httpStatusConflict, RegionCountryConflict, "conflict"), RegionDisabled: spec(codes.FailedPrecondition, httpStatusConflict, RegionDisabled, "conflict"), InvalidInviteCode: spec(codes.InvalidArgument, httpStatusBadRequest, InvalidInviteCode, "invalid argument"), InsufficientBalance: spec(codes.FailedPrecondition, httpStatusConflict, InsufficientBalance, "insufficient balance"), DuplicateBillingCommand: spec(codes.AlreadyExists, httpStatusConflict, DuplicateBillingCommand, "conflict"), LedgerConflict: spec(codes.Aborted, httpStatusConflict, LedgerConflict, "conflict"), CoinSellerNotActive: spec(codes.FailedPrecondition, httpStatusConflict, CoinSellerNotActive, "conflict"), CoinSellerStockTypeInvalid: spec(codes.InvalidArgument, httpStatusBadRequest, CoinSellerStockTypeInvalid, "invalid argument"), CoinSellerStockAmountInvalid: spec(codes.InvalidArgument, httpStatusBadRequest, CoinSellerStockAmountInvalid, "invalid argument"), CoinSellerPaymentRefDuplicated: spec(codes.AlreadyExists, httpStatusConflict, CoinSellerPaymentRefDuplicated, "conflict"), IdempotencyConflict: spec(codes.AlreadyExists, httpStatusConflict, IdempotencyConflict, "conflict"), VIPLevelNotFound: spec(codes.NotFound, httpStatusNotFound, VIPLevelNotFound, "not found"), VIPLevelDisabled: spec(codes.FailedPrecondition, httpStatusConflict, VIPLevelDisabled, "conflict"), VIPDowngradeNotAllowed: spec(codes.FailedPrecondition, httpStatusConflict, VIPDowngradeNotAllowed, "conflict"), RuleNotActive: spec(codes.FailedPrecondition, httpStatusConflict, RuleNotActive, "conflict"), EventAlreadyConsumed: spec(codes.AlreadyExists, httpStatusConflict, EventAlreadyConsumed, "conflict"), RewardPending: spec(codes.FailedPrecondition, httpStatusConflict, RewardPending, "conflict"), InvalidSection: spec(codes.InvalidArgument, httpStatusBadRequest, InvalidSection, "invalid argument"), MessageNotFound: spec(codes.NotFound, httpStatusNotFound, MessageNotFound, "not found"), MessageRecalled: spec(codes.NotFound, httpStatusNotFound, MessageRecalled, "not found"), PageTokenInvalid: spec(codes.InvalidArgument, httpStatusBadRequest, PageTokenInvalid, "invalid argument"), RequestConflict: spec(codes.FailedPrecondition, httpStatusConflict, RequestConflict, "conflict"), ProducerEventConflict: spec(codes.FailedPrecondition, httpStatusConflict, ProducerEventConflict, "conflict"), } func spec(grpcCode codes.Code, httpStatus int, publicCode Code, publicMessage string) Spec { return Spec{ GRPCCode: grpcCode, HTTPStatus: httpStatus, PublicCode: string(publicCode), PublicMessage: publicMessage, } } // SpecOf 返回业务码的注册表项。未知业务码降级为 INTERNAL_ERROR,避免泄漏未登记 reason。 func SpecOf(code Code) Spec { if registered, ok := catalog[code]; ok { return registered } return catalog[Internal] } // RegisteredCodes 返回当前注册表中全部业务码,供测试和文档生成使用。 func RegisteredCodes() []Code { registered := make([]Code, 0, len(catalog)) for code := range catalog { registered = append(registered, code) } return registered }