Skip to content

Commit

Permalink
Merge pull request #1 from devalexandre/feature/add-milt-args
Browse files Browse the repository at this point in the history
Feature/add milt args
  • Loading branch information
devalexandre authored Feb 27, 2024
2 parents bd659b4 + 8472eb8 commit 88e22b3
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 93 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/devalexandre/pipe

go 1.20

require github.com/devalexandre/gofn v1.0.1 // indirect
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/devalexandre/gofn v1.0.1 h1:+pM0fEU5E4ebYt7ReR3FgQMjyESsHNvROJWU1kLa6lg=
github.com/devalexandre/gofn v1.0.1/go.mod h1:fQLcy6EQZaySBvbJAoxcyjKCHHby01Cm24+IHbJU+qY=
94 changes: 39 additions & 55 deletions v1/pipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,71 +2,55 @@ package v1

import (
"errors"
"fmt"
"reflect"
"runtime"
)

type Pipeline func(args ...interface{}) (interface{}, error)
// Pipeline type.
type Pipeline func(...interface{}) (interface{}, error)

// Pipe creates a pipeline from a series of functions.
func Pipe(fs ...interface{}) Pipeline {
return func(args ...interface{}) (interface{}, error) {
var result interface{}
var err error

inputs := make([]reflect.Value, len(args))
for i, arg := range args {
inputs[i] = reflect.ValueOf(arg)
}

for i, f := range fs {
funcValue := reflect.ValueOf(f)
funcType := funcValue.Type()

if len(inputs) != funcType.NumIn() {
funcName := runtime.FuncForPC(funcValue.Pointer()).Name()
return nil, fmt.Errorf("número incorreto de argumentos para a função: %s", funcName)
}

outputs := funcValue.Call(inputs)

if len(outputs) > 0 {
lastOutput := outputs[len(outputs)-1]
if lastOutput.Type().Implements(reflect.TypeOf((*error)(nil)).Elem()) && !lastOutput.IsNil() {
return nil, lastOutput.Interface().(error)
}
if i == len(fs)-1 && len(outputs) > 0 {
result = outputs[0].Interface()
return func(initialArgs ...interface{}) (interface{}, error) {
var currentArgs []interface{} = initialArgs
for _, f := range fs {
fnVal := reflect.ValueOf(f)
fnType := fnVal.Type()
numIn := fnType.NumIn()

// Prepare the input arguments for the current function.
in := make([]reflect.Value, numIn)
for i := 0; i < numIn; i++ {
if i < len(currentArgs) {
in[i] = reflect.ValueOf(currentArgs[i])
} else if i < len(initialArgs) { // Allow passing manual arguments if not enough currentArgs.
in[i] = reflect.ValueOf(initialArgs[i])
} else {
// If there are not enough arguments to pass to the function, return an error.
return nil, errors.New("not enough arguments to pass to function")
}
}

if len(outputs) > 1 {
inputs = outputs[:len(outputs)-1]
} else {
inputs = []reflect.Value{}
// Call the current function in the pipeline.
results := fnVal.Call(in)

// Assume the last function call results will be used as next input.
currentArgs = []interface{}{} // Reset currentArgs for next function.
for _, result := range results {
if result.Type().Implements(reflect.TypeOf((*error)(nil)).Elem()) {
if !result.IsNil() { // If the result is an error, return it.
return nil, result.Interface().(error)
}
// If it's a nil error, ignore it for the output.
} else {
currentArgs = append(currentArgs, result.Interface())
}
}
}

return result, err
}
}

func ParseTo(result interface{}, target interface{}) error {

if reflect.TypeOf(target).Kind() != reflect.Ptr {
return errors.New("target deve ser um ponteiro")
}

resultValue := reflect.ValueOf(result)
targetValue := reflect.ValueOf(target).Elem()

if !resultValue.Type().ConvertibleTo(targetValue.Type()) {
return fmt.Errorf("não é possível converter o resultado do tipo %s para o tipo %s",
resultValue.Type(), targetValue.Type())
// Return the final result which should match the last function's output type.
if len(currentArgs) == 1 {
return currentArgs[0], nil // Return single value if only one result.
}
return currentArgs, nil // Return as slice if multiple values.
}

convertedValue := resultValue.Convert(targetValue.Type())
targetValue.Set(convertedValue)

return nil
}
186 changes: 148 additions & 38 deletions v1/pite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,84 @@ package v1

import (
"fmt"
"github.com/devalexandre/gofn/pipe"
"reflect"
"strings"
"testing"
)

// Exemplos de funções adaptadas para usar com generics
func ToUpper(s string) (string, error) {
return strings.ToUpper(s), nil
}

// Wrapper para strings.TrimSpace que se encaixa na assinatura esperada por Pipe.
func Trim(s string) (string, error) {
return strings.TrimSpace(s), nil
}

func TestPipe(t *testing.T) {
func ValidateCPF(cpf string) (string, error) {
if len(cpf) != 11 {
return "", fmt.Errorf("CPF must have 11 digits")
}
return cpf, nil
}

func FormatCPF(cpf string) (string, error) {
return fmt.Sprintf("%s.%s.%s-%s", cpf[0:3], cpf[3:6], cpf[6:9], cpf[9:11]), nil
}

func Sum(a, b int) (int, error) {
return a + b, nil
}

func Multiply(a int) (int, error) {
return a * 2, nil
}

func ShowResult(result int) (int, error) {
fmt.Println(result)
return result, nil
}

// Estrutura Person e funções de validação ajustadas
type Person struct {
Name string
Email string
Phone string
}

func ValidatePersonName(p Person) (Person, error) {
if p.Name == "" {
return p, fmt.Errorf("Name is required")
}
return p, nil
}

func ValidatePersonEmail(p Person) (Person, error) {
if p.Email == "" || !strings.Contains(p.Email, "@") {
return p, fmt.Errorf("Valid email is required")
}
return p, nil
}

func ValidatePersonPhone(p Person) (Person, error) {
if p.Phone == "" {
return p, fmt.Errorf("Phone is required")
}
return p, nil
}

func SumMore(a, b, c int) (int, error) {
return a + b + c, nil
}

func GenerateTreeNumbers(n int) (int, int, int) {
return n, n * 2, n * 3
}

// Testes ajustados para usar a função Pipe com generics

func TestStringPipeline(t *testing.T) {
pipeline := Pipe(
ToUpper,
Trim,
Expand All @@ -34,63 +97,110 @@ func TestPipe(t *testing.T) {
}
}

func TestParseTo(t *testing.T) {

result := "test result"
var target string
func TestCPFPipeline(t *testing.T) {
pipeline := Pipe(
ValidateCPF,
FormatCPF,
)

err := ParseTo(result, &target)
input := "12345678901"
expected := "123.456.789-01"
result, err := pipeline(input)
if err != nil {
t.Errorf("ParseTo returned an error: %v", err)
t.Errorf("Pipeline returned an error: %v", err)
}

if result != expected {
t.Errorf("Expected %s, got %s", expected, result)
}
}

func TestMathPipeline(t *testing.T) {
pipeline := Pipe(
Sum,
Multiply,
ShowResult,
)

if target != result {
t.Errorf("Expected target to be %s, got %s", result, target)
expected := 18
result, err := pipeline(5, 4)
if err != nil {
t.Errorf("Pipeline returned an error: %v", err)
}

var intTarget int
err = ParseTo(result, &intTarget)
if err == nil {
t.Errorf("Expected ParseTo to fail when types are incompatible")
if result != expected {
t.Errorf("Expected %d, got %d", expected, result)
}
}

// Sum recebe dois números e retorna a sua soma.
func Sum(a, b int) (int, error) {
return a + b, nil
}
func TestPersonPipeline(t *testing.T) {
pipeline := Pipe(
ValidatePersonName,
ValidatePersonEmail,
ValidatePersonPhone,
)

input := Person{
Name: "John Doe",
Email: "[email protected]",
Phone: "1234567890",
}
expected := input // assumindo que a entrada é válida e esperada como saída

// IntToString recebe um número e retorna sua representação em string.
func IntToString(n int) (string, error) {
return fmt.Sprintf("%d", n), nil
result, err := pipeline(input)
if err != nil {
t.Errorf("Pipeline returned an error: %v", err)
}

if result != expected {
t.Errorf("Expected %+v, got %+v", expected, result)
}
}

func TestSumPipeline(t *testing.T) {
// Cria o pipeline.
func TestMoreArgs(t *testing.T) {
pipeline := Pipe(
Sum,
IntToString,
GenerateTreeNumbers,
SumMore,
)

// Define a entrada e a saída esperada.
input1 := 5
input2 := 7
expected := "12"
expected := 6
result, err := pipeline(1)
if err != nil {
t.Errorf("Pipeline returned an error: %v", err)
}

if result != expected {
t.Errorf("Expected %d, got %d", expected, result)
}
}

func TestPipeGoFn(t *testing.T) {
data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
expected := []int{4, 8, 12, 16, 20}
// Definindo o pipeline
process := Pipe(
pipe.Filter(func(i int) bool { return i%2 == 0 }),
pipe.Map(func(i int) int { return i * 2 }),
)

// Executa o pipeline com os números de entrada.
resultInterface, err := pipeline(input1, input2)
// Aplicando o pipeline aos dados
resultInterface, err := process(data)
if err != nil {
t.Fatalf("Pipeline returned an error: %v", err)
fmt.Println("Erro ao processar:", err)
return
}

// Converte o resultado para string.
result, ok := resultInterface.(string)
result, ok := resultInterface.([]int)
if !ok {
t.Fatalf("Failed to convert result to string")
t.Errorf("Expected result type []int, got %T", resultInterface)
}

// Verifica se o resultado é o esperado.
if result != expected {
t.Errorf("Expected %s, got %s", expected, result)
if len(result) != len(expected) {
t.Errorf("Expected %v, got %v", expected, result)
}

if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v, got %v", expected, result)
}

}

0 comments on commit 88e22b3

Please sign in to comment.