Skip to content

Commit

Permalink
Add res package with Success, PartialSuccess and Failure types.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpfourny committed Mar 22, 2024
1 parent 386f341 commit 91f7237
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 0 deletions.
47 changes: 47 additions & 0 deletions pkg/res/failure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package res

import (
"fmt"
"github.com/jpfourny/papaya/pkg/opt"
)

// Failure represents a failed result.
// A failed result has an error and no value.
type Failure[T any] struct {
Err error
}

// Assert that Failure[T] implements Result[T]
var _ Result[any] = Failure[any]{}

func (r Failure[T]) Succeeded() bool {
return false
}

func (r Failure[T]) PartiallySucceeded() bool {
return false
}

func (r Failure[T]) Failed() bool {
return true
}

func (r Failure[T]) HasError() bool {
return true
}

func (r Failure[T]) HasValue() bool {
return false
}

func (r Failure[T]) Value() opt.Optional[T] {
return opt.Empty[T]()
}

func (r Failure[T]) Error() opt.Optional[error] {
return opt.Of(r.Err)
}

func (r Failure[T]) String() string {
return fmt.Sprintf("Failure(%v)", r.Err)
}
49 changes: 49 additions & 0 deletions pkg/res/partial_success.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package res

import (
"fmt"
"github.com/jpfourny/papaya/pkg/opt"
)

// PartialSuccess represents a partially-successful result.
// A partially successful result has a value and an error.
// This is useful when a result is expected to have multiple values, and some of them are missing.
type PartialSuccess[T any] struct {
Val T
Err error
}

// Assert that PartialSuccess[T] implements Result[T]
var _ Result[any] = PartialSuccess[any]{}

func (r PartialSuccess[T]) Succeeded() bool {
return false
}

func (r PartialSuccess[T]) PartiallySucceeded() bool {
return true
}

func (r PartialSuccess[T]) Failed() bool {
return false
}

func (r PartialSuccess[T]) HasError() bool {
return true
}

func (r PartialSuccess[T]) HasValue() bool {
return true
}

func (r PartialSuccess[T]) Value() opt.Optional[T] {
return opt.Of(r.Val)
}

func (r PartialSuccess[T]) Error() opt.Optional[error] {
return opt.Of(r.Err)
}

func (r PartialSuccess[T]) String() string {
return fmt.Sprintf("PartialSuccess(%#v, %v)", r.Val, r.Err)
}
69 changes: 69 additions & 0 deletions pkg/res/result.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package res

import (
"fmt"
"github.com/jpfourny/papaya/pkg/opt"
)

// Result represents the result of an operation that may have a value and/or an error.
// The result can be successful, failed, or partially successful.
// A successful result has a value and no error.
// A failed result has an error and no value.
// A partially successful result has both a value and an error.
// The result can be queried for its value and/or error, and for its success/failure status.
type Result[T any] interface {
fmt.Stringer

// Succeeded returns true for a successful result; false otherwise.
// A successful result is one that has a value and no error.
Succeeded() bool

// PartiallySucceeded returns true for a partially successful result that includes both a value and an error.
// This is useful when a result is expected to have multiple values, and some of them are missing.
PartiallySucceeded() bool

// Failed returns true for a failed result; false otherwise.
// A failed result is one that has an error and no value.
Failed() bool

// HasError returns true if the result has an error; false otherwise.
// This is true for both failed and partially successful results.
HasError() bool

// HasValue returns true if the result has a value; false otherwise.
// This is true for both successful and partially successful results.
HasValue() bool

// Value returns the value of the result as an optional.
// If the result has a value, the optional is non-empty; otherwise, it is empty.
Value() opt.Optional[T]

// Error returns the error of the result as an optional.
// If the result has an error, the optional is non-empty; otherwise, it is empty.
Error() opt.Optional[error]
}

// OfSuccess returns a successful result with the provided value.
func OfSuccess[T any](val T) Result[T] {
return Success[T]{Val: val}
}

// OfPartialSuccess returns a partially successful result with the provided value and error.
func OfPartialSuccess[T any](val T, err error) Result[T] {
return PartialSuccess[T]{Val: val, Err: err}
}

// OfFailure returns a failed result with the provided error.
func OfFailure[T any](err error) Result[T] {
return Failure[T]{Err: err}
}

// Of returns a result with the provided value and error.
// If the error is nil, the result is successful; otherwise, it is failed.
// This is a convenience function that creates a successful or failed result based on the provided error.
func Of[T any](val T, err error) Result[T] {
if err != nil {
return OfFailure[T](err)
}
return OfSuccess[T](val)
}
148 changes: 148 additions & 0 deletions pkg/res/result_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package res

import (
"errors"
"testing"
)

func TestOfSuccess(t *testing.T) {
r := OfSuccess(42)
if !r.Succeeded() {
t.Errorf("expected Succeeded() to be true")
}
if r.PartiallySucceeded() {
t.Errorf("expected PartiallySucceeded() to be false")
}
if r.Failed() {
t.Errorf("expected Failed() to be false")
}
if !r.HasValue() {
t.Errorf("expected HasValue() to be true")
}
if r.HasError() {
t.Errorf("expected HasError() to be false")
}
if r.Value().GetOrZero() != 42 {
t.Errorf("expected Value() to return 42")
}
if r.Error().Present() {
t.Errorf("expected Error() to be empty")
}
if r.String() != "Success(42)" {
t.Errorf("expected String() to return Success(42)")
}
}

func TestOfFailure(t *testing.T) {
r := OfFailure[any](errors.New("error"))
if r.Succeeded() {
t.Errorf("expected Succeeded() to be false")
}
if r.PartiallySucceeded() {
t.Errorf("expected PartiallySucceeded() to be false")
}
if !r.Failed() {
t.Errorf("expected Failed() to be true")
}
if r.HasValue() {
t.Errorf("expected HasValue() to be false")
}
if !r.HasError() {
t.Errorf("expected HasError() to be true")
}
if r.Error().GetOrZero().Error() != "error" {
t.Errorf("expected Error() to return error")
}
if r.Value().Present() {
t.Errorf("expected Value() to be empty")
}
if r.String() != "Failure(error)" {
t.Errorf("expected String() to return Failure(error)")
}
}

func TestOfPartialSuccess(t *testing.T) {
r := OfPartialSuccess(42, errors.New("error"))
if r.Succeeded() {
t.Errorf("expected Succeeded() to be false")
}
if !r.PartiallySucceeded() {
t.Errorf("expected PartiallySucceeded() to be true")
}
if r.Failed() {
t.Errorf("expected Failed() to be false")
}
if !r.HasValue() {
t.Errorf("expected HasValue() to be true")
}
if !r.HasError() {
t.Errorf("expected HasError() to be true")
}
if r.Value().GetOrZero() != 42 {
t.Errorf("expected Value() to return 42")
}
if !r.Error().Present() {
t.Errorf("expected Error() to be non-empty")
}
if r.String() != "PartialSuccess(42, error)" {
t.Errorf("expected String() to return PartialSuccess(42, error)")
}
}

func TestOf(t *testing.T) {
t.Run("Success", func(t *testing.T) {
r := Of(42, nil)
if !r.Succeeded() {
t.Errorf("expected Succeeded() to be true")
}
if r.PartiallySucceeded() {
t.Errorf("expected PartiallySucceeded() to be false")
}
if r.Failed() {
t.Errorf("expected Failed() to be false")
}
if !r.HasValue() {
t.Errorf("expected HasValue() to be true")
}
if r.HasError() {
t.Errorf("expected HasError() to be false")
}
if r.Value().GetOrZero() != 42 {
t.Errorf("expected Value() to return 42")
}
if r.Error().Present() {
t.Errorf("expected Error() to be empty")
}
if r.String() != "Success(42)" {
t.Errorf("expected String() to return Success(42)")
}
})

t.Run("Failure", func(t *testing.T) {
r := Of(42, errors.New("error"))
if r.Succeeded() {
t.Errorf("expected Succeeded() to be false")
}
if r.PartiallySucceeded() {
t.Errorf("expected PartiallySucceeded() to be false")
}
if !r.Failed() {
t.Errorf("expected Failed() to be true")
}
if r.HasValue() {
t.Errorf("expected HasValue() to be false")
}
if !r.HasError() {
t.Errorf("expected HasError() to be true")
}
if r.Error().GetOrZero().Error() != "error" {
t.Errorf("expected Error() to return error")
}
if r.Value().Present() {
t.Errorf("expected Value() to be empty")
}
if r.String() != "Failure(error)" {
t.Errorf("expected String() to return Failure(error)")
}
})
}
47 changes: 47 additions & 0 deletions pkg/res/success.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package res

import (
"fmt"
"github.com/jpfourny/papaya/pkg/opt"
)

// Success represents a successful result.
// A successful result has a value and no error.
type Success[T any] struct {
Val T
}

// Assert that Success[T] implements Result[T]
var _ Result[any] = Success[any]{}

func (r Success[T]) Succeeded() bool {
return true
}

func (r Success[T]) PartiallySucceeded() bool {
return false
}

func (r Success[T]) Failed() bool {
return false
}

func (r Success[T]) HasError() bool {
return false
}

func (r Success[T]) HasValue() bool {
return true
}

func (r Success[T]) Value() opt.Optional[T] {
return opt.Of(r.Val)
}

func (r Success[T]) Error() opt.Optional[error] {
return opt.Empty[error]()
}

func (r Success[T]) String() string {
return fmt.Sprintf("Success(%#v)", r.Val)
}

0 comments on commit 91f7237

Please sign in to comment.