// voice/errors.go — wire error codes + sentinel rehydration. package voice import ( "encoding/json" "errors" "fmt" ) // Sentinel errors. Mirrored 1:1 to wire codes below; the client rehydrates a // wire RpcError into one of these so callers can use errors.Is the same way // in-process and over-the-wire. var ( ErrUnknownMethod = errors.New("voice: unknown method") ErrBadParams = errors.New("voice: bad params") ErrForbidden = errors.New("voice: forbidden") ErrNoSession = errors.New("voice: no live client session") // voicesink unable to push ) // Sentinel wire codes. Stable; do not rename. const ( codeUnknownMethod = "unknown_method" codeBadParams = "bad_params" codeForbidden = "forbidden" codeInternal = "internal" ) // codeOf maps a server-side sentinel to its wire code. Unknown ⇒ // codeInternal; the server logs the real text and the client sees a // generic internal code (no authority-bearing text exposure). func codeOf(err error) string { switch { case err == nil: return "" case errors.Is(err, ErrUnknownMethod): return codeUnknownMethod case errors.Is(err, ErrBadParams): return codeBadParams case errors.Is(err, ErrForbidden): return codeForbidden default: return codeInternal } } func rpcErr(err error) *RpcError { c := codeOf(err) if c == codeInternal || c == codeBadParams { return &RpcError{Code: c, Message: err.Error()} } return &RpcError{Code: c} } // hydrate rehydrates a wire RpcError into a package sentinel. func hydrate(e *RpcError) error { switch e.Code { case codeUnknownMethod: return fmt.Errorf("%w: %s", ErrUnknownMethod, e.Message) case codeBadParams: return fmt.Errorf("%w: %s", ErrBadParams, e.Message) case codeForbidden: return ErrForbidden default: if e.Message != "" { return fmt.Errorf("voice: %s: %s", e.Code, e.Message) } return fmt.Errorf("voice: %s", e.Code) } } // json helpers kept local so call sites read clean. func jsonMarshal(v any) ([]byte, error) { return json.Marshal(v) } func jsonUnmarshal(b []byte, v any) error { return json.Unmarshal(b, v) }