Skip to content
HadrielWondaPublic

About

OutcomeX is a lightweight, intuitive package designed to handle operations that result in either success or failure. It provides a structured way to manage outcomes, ensuring clarity and reliability in error handling.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

OutcomeX is a lightweight, intuitive package designed to handle operations that result in either success or failure. It provides a structured way to manage outcomes, ensuring clarity and reliability in error handling. Whether you’re dealing with expected results or unexpected failures, OutcomeX streamlines the process for better code organization and maintainability.

🔹 Clear and concise error handling 🔹Simplified success/failure flow 🔹 Enhances code readability and structure

Let's rock

Go Version Go Reference CI Coverage Go Report Card

OutcomeX - Railway-Oriented Error Handling for Go

A robust implementation of error handling patterns inspired by Rust's Result and .NET's ErrorOr, providing:

  • 🚂 Railway-oriented programming
  • 🧩 Type-safe error propagation
  • ⚡ Async error handling
  • 🧠 Composite errors
  • 📊 Detailed error metadata

Features

  • Chainable operations with Then/ThenConvert
  • Pattern matching with Match/MatchResult
  • Async workflows with context cancellation
  • Error recovery and fallback mechanisms
  • Benchmarked against standard error handling

Installation

go get github.com/hadrielwonda/outcomex

Quick Start

package main

import (
    "fmt"
    "github.com/hadrielwonda/outcomex"
)

func main() {
    result := outcomex.WrapOperation(func() (int, error) {
        return 42, nil
    }).
    Then(func(n int) outcomex.Outcome[int] {
        return outcomex.Success(n * 2)
    }).
    MatchResult(
        func(v int) any { return v },
        func(errs []outcomex.Error) any { return errs },
    )

    fmt.Println(result) // Output: 84
}

Benchmarks

Operation Time/op Allocs/op
Success Path 15.2 ns/op 0 allocs/op
Failure Path 18.7 ns/op 1 allocs/op
Standard Error Handling 12.4 ns/op 0 allocs/op
pkg/errors 24.1 ns/op 2 allocs/op
go test -bench=. -benchmem

Documentation

Design Decisions

Error Code Conventions

Advanced Usage

Community & Support

Report Issues

Join Discussions

Contributing Guide

Integration Package Status
Gin outcomex-gin Active
Echo outcomex-echo Beta
Sentry outcomex-sentry Planned

License

Apache License - See LICENSE for details

# docs/design.md

# Architecture Decisions

## Core Principles

1. **Immutability**: All Outcome instances are immutable after creation
2. **Composability**: Operations can be chained without side effects
3. **Context Awareness**: Async operations respect context cancellation
4. **Type Safety**: Generic implementation prevents type mismatches

Error Aggregation

type Outcome[T any] struct {
    errors []Error  // Contains all accumulated errors
}

Concurrency Model

Async operations use channel-based communication

Context cancellation propagates through async chains

Buffered channels prevent goroutine leaks

Performance Considerations

Zero-allocation success paths

Interface-free design for compiler optimizations

Pre-sized error slices for common cases

# docs/error-codes.md

# Error Code Conventions

## Standard Codes

Code | Description
-----|------------
validation | Input validation failure
not_found | Resource missing
conflict | State conflict
unexpected | Unhandled exception
timeout | Operation timed out

Custom Codes

// Register custom codes at init
func init() {
    outcomex.RegisterCode("custom_code", "Custom description")
}

Best Practices

Use snake_case for code names

Keep codes domain-specific

Document all codes in package documentation

Use consistent code names across services

# docs/examples.md

Advanced Usage

Chained Validation

type User struct {
    Name string
    Age  int
}

func validateUser(u User) outcomex.Outcome[User] {
    return outcomex.Success(u).
        Where(func(u User) bool { return u.Age >= 18 }, 
            outcomex.ValidationError("underage")).
        Where(func(u User) bool { return u.Name != "" },
            outcomex.ValidationError("empty_name"))
}

Async Pipeline

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

result := outcomex.Async(ctx, fetchData).
    ThenAsync(ctx, processData).
    ThenAsync(ctx, storeData).
    Await()

Error Recovery

 val := outcomex.Failure[int](outcomex.NotFoundError("user")).
   Recover(func(errs []outcomex.Error) int {
       return -1 // Default value
   })

Composite Operations

ops := []outcomex.Outcome[any]{
   outcomex.WrapOperation(fetchUser),
   outcomex.WrapOperation(fetchOrders),
}

combined := outcomex.Combine(ops...)

'''go

// internal/outcomex/outcome_test.go

package outcomex

import (
    "context"
    "errors"
    "testing"
    "time"
)

func TestSuccess(t *testing.T) {
    o := Success(42)
    if !o.IsSuccess() {
        t.Error("Should be success")
    }
    if o.Value() != 42 {
        t.Error("Incorrect value")
    }
}

func TestFailure(t *testing.T) {
    o := Failure[int](NewError("test", "error"))
    if !o.IsFailure() {
        t.Error("Should be failure")
    }
    if len(o.Errors()) != 1 {
        t.Error("Should have one error")
    }
}

func TestThenChain(t *testing.T) {
    result := Success(2).
        Then(func(n int) Outcome[int] { return Success(n * 3) }).
        Then(func(n int) Outcome[int] { return Success(n + 1) })
        
    if result.Value() != 7 {
        t.Errorf("Expected 7, got %d", result.Value())
    }
}

func TestAsyncTimeout(t *testing.T) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
    defer cancel()

    ao := Async(ctx, func() Outcome[int] {
        time.Sleep(20 * time.Millisecond)
        return Success(42)
    })
    
    result := ao.Await()
    if result.IsSuccess() {
        t.Error("Should have timed out")
    }
}

func TestErrorConversion(t *testing.T) {
    _, err := errors.New("standard error")
    o := ConvertToOutcome(42, err)
    if o.IsSuccess() {
        t.Error("Should convert standard error")
    }
}

// Benchmarking
func BenchmarkSuccessPath(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Success(42).Then(func(n int) Outcome[int] {
            return Success(n * 2)
        })
    }
}

func BenchmarkFailurePath(b *testing.B) {
    err := NewError("test", "error")
    for i := 0; i < b.N; i++ {
        Failure[int](err).Then(func(n int) Outcome[int] {
            return Success(n * 2)
        })
    }
}

About

OutcomeX is a lightweight, intuitive package designed to handle operations that result in either success or failure. It provides a structured way to manage outcomes, ensuring clarity and reliability in error handling.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages