From c6ff8d0a3ff37809dffcb726b52ecce74c35070b Mon Sep 17 00:00:00 2001 From: Rene Nochebuena Guerrero Date: Mon, 11 May 2026 17:49:52 -0600 Subject: [PATCH] =?UTF-8?q?feat(xerrors)!:=20promote=20to=20v1.0.0=20?= =?UTF-8?q?=E2=80=94=20add=20Unauthorized=20and=20PermissionDenied=20const?= =?UTF-8?q?ructors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Unauthorized and PermissionDenied convenience constructors to complete the set of the five most-used error codes (InvalidInput, NotFound, Internal, Unauthorized, PermissionDenied). All roadmap items from v0.9.0 resolved. API committed as stable. --- CHANGELOG.md | 18 ++++++++++++++++++ xerrors.go | 12 ++++++++++++ xerrors_test.go | 20 ++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6013bbc..7e86d6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this module will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.0] — 2026-05-08 + +### Added + +- `Unauthorized(msg string, args ...any) *Err` — convenience constructor for + `ErrUnauthorized`; completes the set of the five most-used codes alongside + `InvalidInput`, `NotFound`, `Internal`, and `PermissionDenied` +- `PermissionDenied(msg string, args ...any) *Err` — convenience constructor for + `ErrPermissionDenied` + +### Unchanged + +All existing API (`Code`, `Err`, `New`, `Wrap`, `InvalidInput`, `NotFound`, +`Internal`, `WithContext`, `WithError`, `WithPlatformCode`, `ErrorCode`, +`ErrorContext`, `MarshalJSON`) is API-compatible with v0.10.0. + +[1.0.0]: https://code.nochebuena.dev/go/xerrors/releases/tag/v1.0.0 + ## [0.10.0] - 2026-03-25 ### Added diff --git a/xerrors.go b/xerrors.go index 0d872c5..fab664e 100644 --- a/xerrors.go +++ b/xerrors.go @@ -67,6 +67,18 @@ func Internal(msg string, args ...any) *Err { return New(ErrInternal, fmt.Sprintf(msg, args...)) } +// Unauthorized creates an Err with [ErrUnauthorized] code. +// msg is formatted with args using [fmt.Sprintf] rules. +func Unauthorized(msg string, args ...any) *Err { + return New(ErrUnauthorized, fmt.Sprintf(msg, args...)) +} + +// PermissionDenied creates an Err with [ErrPermissionDenied] code. +// msg is formatted with args using [fmt.Sprintf] rules. +func PermissionDenied(msg string, args ...any) *Err { + return New(ErrPermissionDenied, fmt.Sprintf(msg, args...)) +} + // WithContext adds a key-value pair to the error's context fields and returns // the receiver for chaining. Calling it multiple times with the same key // overwrites the previous value. diff --git a/xerrors_test.go b/xerrors_test.go index 08c4f42..d3164c4 100644 --- a/xerrors_test.go +++ b/xerrors_test.go @@ -70,6 +70,26 @@ func TestConvenienceConstructors(t *testing.T) { t.Errorf("unexpected message: %s", err.message) } }) + + t.Run("Unauthorized", func(t *testing.T) { + err := Unauthorized("token expired for %s", "uid1") + if err.code != ErrUnauthorized { + t.Errorf("expected code %s, got %s", ErrUnauthorized, err.code) + } + if err.message != "token expired for uid1" { + t.Errorf("unexpected message: %s", err.message) + } + }) + + t.Run("PermissionDenied", func(t *testing.T) { + err := PermissionDenied("role %s cannot delete", "viewer") + if err.code != ErrPermissionDenied { + t.Errorf("expected code %s, got %s", ErrPermissionDenied, err.code) + } + if err.message != "role viewer cannot delete" { + t.Errorf("unexpected message: %s", err.message) + } + }) } func TestErr_Error(t *testing.T) {