├── .github ├── static │ └── pipeline.png └── workflows │ └── go.yml ├── .gitignore ├── go.mod ├── license.md ├── pipeline.go ├── pipeline_test.go └── readme.md /.github/static/pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/izniburak/pipeline-go/727d3c78797f1e0d25bccf6aeb495febfdf679fb/.github/static/pipeline.png -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.20' 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/izniburak/pipeline-go 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023, İzni Burak Demirtaş 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /pipeline.go: -------------------------------------------------------------------------------- 1 | package pipeline 2 | 3 | type PipeValue interface{} 4 | type PipeNext func(PipeValue) PipeValue 5 | 6 | type PipeInterface interface { 7 | Handle(PipeValue, PipeNext) PipeValue 8 | } 9 | 10 | type Pipeline struct { 11 | value PipeValue 12 | pipes []PipeInterface 13 | } 14 | 15 | func Send(value interface{}) *Pipeline { 16 | return &Pipeline{value: value} 17 | } 18 | 19 | func (p *Pipeline) Through(pipes []PipeInterface) *Pipeline { 20 | p.pipes = pipes 21 | return p 22 | } 23 | 24 | func (p *Pipeline) Then(destination PipeNext) PipeValue { 25 | carry := p.carry() 26 | stack := destination 27 | for i := len(p.pipes) - 1; i >= 0; i-- { 28 | stack = carry(stack, p.pipes[i]) 29 | } 30 | return stack(p.value) 31 | } 32 | 33 | func (p *Pipeline) ThenReturn() PipeValue { 34 | return p.Then(func(value PipeValue) PipeValue { 35 | return value 36 | }) 37 | } 38 | 39 | func (p *Pipeline) carry() func(PipeNext, PipeInterface) PipeNext { 40 | return func(stack PipeNext, pipe PipeInterface) PipeNext { 41 | return func(value PipeValue) PipeValue { 42 | return pipe.Handle(value, stack) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pipeline_test.go: -------------------------------------------------------------------------------- 1 | package pipeline 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | // 9 | // StringPipes 10 | // 11 | 12 | type UpperCasePipe struct{} 13 | 14 | func (u *UpperCasePipe) Handle(value PipeValue, next PipeNext) PipeValue { 15 | // get value 16 | text := value.(string) 17 | 18 | capitalized := strings.ToUpper(text) 19 | return next(capitalized) 20 | } 21 | 22 | type TrimSpacePipe struct{} 23 | 24 | func (t *TrimSpacePipe) Handle(value PipeValue, next PipeNext) PipeValue { 25 | // get value 26 | text := value.(string) 27 | 28 | trimmed := strings.Trim(text, " ") 29 | return next(trimmed) 30 | } 31 | 32 | type ReplaceTextPipe struct{} 33 | 34 | func (r *ReplaceTextPipe) Handle(value PipeValue, next PipeNext) PipeValue { 35 | // get value 36 | text := value.(string) 37 | 38 | replaced := strings.ReplaceAll(text, "BURAKDEMIRTAS.ORG", "BUKI.DEV") 39 | return next(replaced) 40 | } 41 | 42 | func TestStringPipelines(t *testing.T) { 43 | pipes := []PipeInterface{new(UpperCasePipe), new(TrimSpacePipe)} 44 | data := Send(" burakdemirtas.org ").Through(pipes).ThenReturn() 45 | 46 | if data != "BURAKDEMIRTAS.ORG" { 47 | t.Errorf("got %s, want BURAKDEMIRTAS.ORG", data) 48 | } 49 | 50 | pipes = append(pipes, new(ReplaceTextPipe)) 51 | data = Send(" burakdemirtas.org ").Through(pipes).ThenReturn() 52 | 53 | if data != "BUKI.DEV" { 54 | t.Errorf("got %s, want BUKI.DEV", data) 55 | } 56 | 57 | data = Send(" burakdemirtas.org ").Through(pipes).Then(func(value PipeValue) PipeValue { 58 | return strings.ReplaceAll(value.(string), ".DEV", "") 59 | }) 60 | 61 | if data != "BUKI" { 62 | t.Errorf("got %s, want BUKI", data) 63 | } 64 | } 65 | 66 | // 67 | // ArrayPipes 68 | // 69 | 70 | type DoublePipe struct{} 71 | 72 | func (s *DoublePipe) Handle(value PipeValue, next PipeNext) PipeValue { 73 | // get value 74 | numbers := value.([]int) 75 | 76 | result := make([]int, len(numbers)) 77 | for i, v := range numbers { 78 | result[i] = v * v 79 | } 80 | 81 | return next(result) 82 | } 83 | 84 | type SumPipe struct{} 85 | 86 | func (s *SumPipe) Handle(value PipeValue, next PipeNext) PipeValue { 87 | // get value 88 | numbers := value.([]int) 89 | 90 | sum := 0 91 | for _, v := range numbers { 92 | sum += v 93 | } 94 | 95 | return next(sum) 96 | } 97 | 98 | func TestArrayPipelines(t *testing.T) { 99 | pipes := []PipeInterface{new(DoublePipe), new(SumPipe)} 100 | result := Send([]int{1, 2, 3}).Through(pipes).ThenReturn() 101 | 102 | if result != 14 { 103 | t.Errorf("got %d, want 14", result) 104 | } 105 | } 106 | 107 | // 108 | // MapPipes 109 | // 110 | 111 | func TestMapPipelines(t *testing.T) { 112 | 113 | } 114 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # pipeline-go [![](https://github.com/izniburak/pipeline-go/actions/workflows/go.yml/badge.svg)](https://github.com/izniburak/pipeline-go/actions) [![PkgGoDev](https://pkg.go.dev/badge/github.com/izniburak/pipeline-go)](https://pkg.go.dev/github.com/izniburak/pipeline-go) 2 | 3 | This package allows you to use the Pipeline pattern in your processes, and it's built upon the Chain of Responsibility (CoR) design pattern. 4 | 5 | CoR is a behavioral design pattern that processes given data through a series of handlers. When a request reaches the pipe class, it processes the data and then forwards it to the next handler. The principle behind this pattern is straightforward. 6 | 7 | ![pipeline](/.github/static/pipeline.png) 8 | 9 | > In summary, the Pipeline is a design pattern tailored for managing sequential modifications to an object. Imagine it as an assembly line: each station represents a pipe, and by the end of the line, you're left with a transformed object. 10 | 11 | ## Install 12 | 13 | ```bash 14 | go get github.com/izniburak/pipeline-go 15 | ``` 16 | 17 | ## Examples 18 | ```go 19 | package main 20 | 21 | import ( 22 | "fmt" 23 | "strings" 24 | "github.com/izniburak/pipeline-go" 25 | ) 26 | 27 | type UpperCasePipe struct{} 28 | 29 | func (u *UpperCasePipe) Handle(value pipeline.PipeValue, next pipeline.PipeNext) pipeline.PipeValue { 30 | // get value 31 | text := value.(string) 32 | capitalized := strings.ToUpper(text) 33 | return next(capitalized) 34 | } 35 | 36 | type TrimSpacePipe struct{} 37 | 38 | func (t *TrimSpacePipe) Handle(value pipeline.PipeValue, next pipeline.PipeNext) pipeline.PipeValue { 39 | // get value 40 | text := value.(string) 41 | trimmed := strings.Trim(text, " ") 42 | return next(trimmed) 43 | } 44 | 45 | func main() { 46 | text := " buki.dev " 47 | 48 | pipes := []pipeline.PipeInterface{ 49 | new(UpperCasePipe), 50 | new(TrimSpacePipe), 51 | } 52 | result := pipeline.Send(text).Through(pipes).ThenReturn() 53 | 54 | fmt.Println(result) // BUKI.DEV 55 | } 56 | ``` 57 | 58 | ## Contributing 59 | 60 | 1. Fork the repo (https://github.com/izniburak/pipeline-go/fork) 61 | 2. Create your feature branch (git checkout -b my-new-feature) 62 | 3. Commit your changes (git commit -am 'Add some feature') 63 | 4. Push to the branch (git push origin my-new-feature) 64 | 5. Create a new Pull Request 65 | 66 | ## Contributors 67 | 68 | - [izniburak](https://github.com/izniburak) İzni Burak Demirtaş - creator, maintainer 69 | 70 | ## License 71 | The MIT License (MIT) - see [`license.md`](https://github.com/izniburak/pipeline-go/blob/main/license.md) for more details 72 | --------------------------------------------------------------------------------