errs

package
v1.0.44 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 29, 2026 License: MIT Imports: 1 Imported by: 0

Documentation

Overview

Package errs is the public error-contract surface for lark-cli.

It defines a closed taxonomy (9 Categories) and a small set of typed errors that embed Problem — an RFC 7807-aligned shared shape. External consumers (AI agents, shell scripts, integrating SDKs) read structured fields instead of regex-parsing free-string error messages.

The Problem shape

Every typed error embeds Problem so the JSON wire shape (`type`, `subtype`, `code`, `message`, `hint`, `log_id`, `retryable`) is uniform across categories. Typed extensions (PermissionError.MissingScopes, SecurityPolicyError.ChallengeURL, etc.) appear at the top level of the envelope alongside the shared fields, not nested under a `detail` key.

Working with typed errors

Use ProblemOf to read shared fields polymorphically:

if p, ok := errs.ProblemOf(err); ok {
    log.Printf("category=%s subtype=%s retryable=%t", p.Category, p.Subtype, p.Retryable)
}

Use the IsXxx predicates or stdlib errors.As to branch on concrete type:

if errs.IsPermission(err) {
    var pe *errs.PermissionError
    _ = errors.As(err, &pe)
    fmt.Println("missing scopes:", pe.MissingScopes)
}

Use WrapInternal at boundaries to lift any non-typed error to *InternalError; typed errors pass through unchanged.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsAPI

func IsAPI(err error) bool

IsAPI reports whether err is an *APIError.

func IsAuthentication

func IsAuthentication(err error) bool

IsAuthentication reports whether err is an *AuthenticationError.

func IsConfig

func IsConfig(err error) bool

IsConfig reports whether err is a *ConfigError.

func IsConfirmationRequired

func IsConfirmationRequired(err error) bool

IsConfirmationRequired reports whether err is a *ConfirmationRequiredError.

func IsContentSafety

func IsContentSafety(err error) bool

IsContentSafety reports whether err is a *ContentSafetyError.

func IsInternal

func IsInternal(err error) bool

IsInternal reports whether err is an *InternalError.

func IsNetwork

func IsNetwork(err error) bool

IsNetwork reports whether err is a *NetworkError.

func IsPermission

func IsPermission(err error) bool

IsPermission reports whether err is a *PermissionError.

func IsRetryable

func IsRetryable(err error) bool

IsRetryable reads Problem.Retryable; non-typed errors are non-retryable by default.

func IsSecurityPolicy

func IsSecurityPolicy(err error) bool

IsSecurityPolicy reports whether err is a *SecurityPolicyError.

func IsValidation

func IsValidation(err error) bool

IsValidation reports whether err is a *ValidationError.

func UnwrapTypedError

func UnwrapTypedError(err error) (error, bool)

UnwrapTypedError walks the wrap chain and returns the first error that embeds Problem (i.e. any typed error in this package). Returns the typed error itself (as error) so callers — notably JSON marshaling — see the concrete value's own struct tags rather than an opaque wrapper.

func WrapInternal

func WrapInternal(err error) error

WrapInternal wraps a non-typed error into *InternalError. Typed errors (anything implementing problemCarrier) pass through unchanged. Component is metric-only and derived by the dispatcher, so it is not a parameter here.

Types

type APIError

type APIError struct {
	Problem
	Detail map[string]any `json:"detail,omitempty"`
}

APIError is the typed error for CategoryAPI (catch-all for classified Lark API business errors). Detail preserves the raw Lark error map for diagnostics.

type AuthenticationError

type AuthenticationError struct {
	Problem
	UserOpenID string `json:"user_open_id,omitempty"`
	Cause      error  `json:"-"`
}

AuthenticationError is the typed error for CategoryAuthentication. Cause preserves an optional wrapped sentinel for errors.Is / errors.Unwrap; it is intentionally not serialized.

func (*AuthenticationError) Unwrap

func (e *AuthenticationError) Unwrap() error

Unwrap is nil-receiver safe; see ValidationError.Unwrap.

type Category

type Category string

Category is the top-level taxonomy axis. Wire JSON: "type".

const (
	CategoryValidation     Category = "validation"
	CategoryAuthentication Category = "authentication"
	CategoryAuthorization  Category = "authorization"
	CategoryConfig         Category = "config"
	CategoryNetwork        Category = "network"
	CategoryAPI            Category = "api"
	CategoryPolicy         Category = "policy"
	CategoryInternal       Category = "internal"
	CategoryConfirmation   Category = "confirmation"
)

func CategoryOf

func CategoryOf(err error) Category

CategoryOf returns the error's Category for metrics/logging/dispatch routing. Falls back to CategoryInternal for non-typed errors.

type ConfigError

type ConfigError struct {
	Problem
	Field string `json:"field,omitempty"`
	Cause error  `json:"-"`
}

ConfigError is the typed error for CategoryConfig. Cause preserves an optional wrapped sentinel for errors.Is / errors.Unwrap; it is intentionally not serialized.

func (*ConfigError) Unwrap

func (e *ConfigError) Unwrap() error

Unwrap is nil-receiver safe; see ValidationError.Unwrap.

type ConfirmationRequiredError

type ConfirmationRequiredError struct {
	Problem
	Risk   string `json:"risk"`
	Action string `json:"action"`
}

ConfirmationRequiredError is the typed error for CategoryConfirmation. Risk is one of: "read" | "write" | "high-risk-write".

type ContentSafetyError

type ContentSafetyError struct {
	Problem
	Rules []string `json:"rules,omitempty"`
}

ContentSafetyError is the typed error for CategoryPolicy content-safety subtypes.

type InternalError

type InternalError struct {
	Problem
	Cause error `json:"-"`
}

InternalError is the typed error for CategoryInternal. Cause is preserved for logging but not emitted on the wire.

func (*InternalError) Unwrap

func (e *InternalError) Unwrap() error

Unwrap is nil-receiver safe; see ValidationError.Unwrap.

type NetworkError

type NetworkError struct {
	Problem
	CauseKind string `json:"cause,omitempty"`
	Cause     error  `json:"-"`
}

NetworkError is the typed error for CategoryNetwork. CauseKind (string) is one of: "timeout" | "tls" | "dns" | "5xx" — the canonical wire taxonomy (emitted as JSON key "cause"). Cause preserves an optional wrapped sentinel for errors.Is / errors.Unwrap; it is intentionally not serialized.

func (*NetworkError) Unwrap

func (e *NetworkError) Unwrap() error

Unwrap is nil-receiver safe; see ValidationError.Unwrap.

type PermissionError

type PermissionError struct {
	Problem
	MissingScopes []string `json:"missing_scopes,omitempty"`
	Identity      string   `json:"identity,omitempty"`
	ConsoleURL    string   `json:"console_url,omitempty"`
}

PermissionError is the typed error for CategoryAuthorization.

type Problem

type Problem struct {
	Category  Category `json:"type"`
	Subtype   Subtype  `json:"subtype,omitempty"`
	Code      int      `json:"code,omitempty"`
	Message   string   `json:"message"`
	Hint      string   `json:"hint,omitempty"`
	LogID     string   `json:"log_id,omitempty"`
	Retryable bool     `json:"retryable,omitempty"`
}

Problem is the RFC 7807-aligned shared shape embedded by every typed error.

Message is REQUIRED. Producers must populate it; an empty Message will make Error() return "" — a known Go footgun for fmt.Errorf("...: %v", err).

Wire-format notes:

  • No Component field. Service / shortcut component is metric-only enrichment derived by the dispatcher from the cobra command path; it never appears on the wire.
  • No DocURL field. PermissionError carries the same intent via its typed ConsoleURL extension; other typed errors do not link out.
  • Retryable uses omitempty so only `true` is emitted; consumers treat absence as false.

func ProblemOf

func ProblemOf(err error) (*Problem, bool)

ProblemOf extracts the embedded Problem via the non-exported problemCarrier interface. This is the supported way to read shared fields without depending on a specific typed error.

A typed error whose embedded *Problem is nil is treated as "not a problem carrier" — returning (nil, true) here would cause CategoryOf / IsRetryable and other downstream readers to dereference nil.

func (*Problem) Error

func (p *Problem) Error() string

Error satisfies the standard `error` interface. A nil receiver is treated as the empty string so a stray nil *Problem stored in an error interface cannot panic the dispatcher.

func (*Problem) ProblemDetail

func (p *Problem) ProblemDetail() *Problem

type SecurityPolicyError

type SecurityPolicyError struct {
	Problem
	ChallengeURL string `json:"challenge_url,omitempty"`
	Cause        error  `json:"-"`
}

SecurityPolicyError is the typed error for CategoryPolicy security-policy subtypes. Subtype is "challenge_required" or "access_denied"; Code is 21000 or 21001.

func (*SecurityPolicyError) Unwrap

func (e *SecurityPolicyError) Unwrap() error

Unwrap is nil-receiver safe; see ValidationError.Unwrap.

type Subtype

type Subtype string

Subtype is the second-level taxonomy axis. Wire JSON: "subtype".

const (
	SubtypeTokenMissing        Subtype = "token_missing"         // no token in request (Authorization header absent / no local token cache)
	SubtypeTokenInvalid        Subtype = "token_invalid"         // token present but content/format wrong
	SubtypeTokenExpired        Subtype = "token_expired"         // token explicitly expired
	SubtypeRefreshTokenInvalid Subtype = "refresh_token_invalid" // refresh_token is v1 legacy format, unusable
	SubtypeRefreshTokenExpired Subtype = "refresh_token_expired" // refresh_token expired
	SubtypeRefreshTokenRevoked Subtype = "refresh_token_revoked" // refresh_token revoked (user logout / admin action)
	SubtypeRefreshTokenReused  Subtype = "refresh_token_reused"  // refresh_token already used (single-use rotation triggered)
	SubtypeRefreshServerError  Subtype = "refresh_server_error"  // refresh endpoint transient error (retryable)
)

CategoryAuthentication subtypes

const (
	SubtypeMissingScope           Subtype = "missing_scope"            // user authorized app but did not grant this scope
	SubtypeUserUnauthorized       Subtype = "user_unauthorized"        // user never authorized the app
	SubtypeAppScopeNotApplied     Subtype = "app_scope_not_applied"    // app did not apply for this scope on the open platform
	SubtypeTokenScopeInsufficient Subtype = "token_scope_insufficient" // token was issued without this scope (RFC 6750 alignment)
	SubtypeAppUnavailable         Subtype = "app_unavailable"          // app status unavailable
	SubtypeAppNotInstalled        Subtype = "app_not_installed"        // app not enabled / not installed in this tenant
)

CategoryAuthorization subtypes

const (
	SubtypeInvalidClient Subtype = "invalid_client" // app_id / app_secret incorrect (RFC 6749 §5.2 alignment)
	SubtypeNotConfigured Subtype = "not_configured" // local config file absent (user has not run `config init`)
	SubtypeInvalidConfig Subtype = "invalid_config" // local config file present but malformed
)

CategoryConfig subtypes

const (
	SubtypeRateLimit         Subtype = "rate_limit"         // request rate limit exceeded
	SubtypeConflict          Subtype = "conflict"           // resource state conflict (e.g. concurrent modification)
	SubtypeCrossTenant       Subtype = "cross_tenant"       // operation crosses tenant boundary (not supported)
	SubtypeCrossBrand        Subtype = "cross_brand"        // operation crosses brand boundary (feishu vs lark, not supported)
	SubtypeInvalidParameters Subtype = "invalid_parameters" // API-side parameter validation rejected the request
	SubtypeOwnershipMismatch Subtype = "ownership_mismatch" // caller is not the resource owner
)

CategoryAPI subtypes

const (
	SubtypeChallengeRequired Subtype = "challenge_required" // user must complete browser challenge / MFA
	SubtypeAccessDenied      Subtype = "access_denied"      // policy denies access outright
)

CategoryPolicy subtypes (security-policy envelope shape)

const (
	SubtypeSDKError        Subtype = "sdk_error"        // lark SDK Do() returned an unexpected error
	SubtypeInvalidResponse Subtype = "invalid_response" // SDK response body not parsable as JSON

)

CategoryInternal subtypes

const (
	SubtypeTaskInvalidParams       Subtype = "task_invalid_params"
	SubtypeTaskPermissionDenied    Subtype = "task_permission_denied"
	SubtypeTaskNotFound            Subtype = "task_not_found"
	SubtypeTaskConflict            Subtype = "task_conflict"
	SubtypeTaskServerError         Subtype = "task_server_error"
	SubtypeTaskAssigneeLimit       Subtype = "task_assignee_limit"
	SubtypeTaskFollowerLimit       Subtype = "task_follower_limit"
	SubtypeTaskTasklistMemberLimit Subtype = "task_tasklist_member_limit"
	SubtypeTaskReminderExists      Subtype = "task_reminder_exists"
)

Task service subtypes — consumed by internal/errclass/codemeta_task.go.

const (
	SubtypeInvalidArgument Subtype = "invalid_argument" // user-supplied flag / arg failed validation (gRPC INVALID_ARGUMENT alignment)
)

CategoryValidation subtypes

const (
	SubtypeNetworkTransport Subtype = "transport" // transport-layer failure (timeout / TLS / DNS / 5xx); see NetworkError.CauseKind
)

CategoryNetwork subtypes

const (
	SubtypeUnknown Subtype = "unknown" // catch-all fallback; producers must prefer a specific subtype
)

type ValidationError

type ValidationError struct {
	Problem
	Param string `json:"param,omitempty"`
	Cause error  `json:"-"`
}

ValidationError is the typed error for CategoryValidation. Cause preserves an optional wrapped sentinel for errors.Is / errors.Unwrap; it is intentionally not serialized.

func (*ValidationError) Unwrap

func (e *ValidationError) Unwrap() error

Unwrap exposes the wrapped cause so errors.Unwrap / errors.Is can traverse it. A nil typed-pointer held inside an error interface is treated as "no cause" so callers cannot panic on `errors.Unwrap(err)`.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL