github.com/packtpublishing/learning-functional-programming-in-go@v0.0.0-20230130084745-8b849f6d58c4/Chapter08/main.go (about) 1 package main 2 3 import ( 4 "log" 5 "fmt" 6 gc "github.com/go-goodies/go_currency" 7 gu "github.com/go-goodies/go_utils" 8 "strings" 9 "crypto/aes" 10 "crypto/cipher" 11 "crypto/rand" 12 "encoding/base64" 13 "errors" 14 "io" 15 "bytes" 16 ) 17 18 func main() { 19 pipeline := BuildPipeline(Decrypt{}, Charge{}, Authenticate{}) 20 21 go func(){ 22 orders := GetOrders() 23 for _, order := range orders { 24 fmt.Printf("order: %v\n", order) 25 pipeline.Send(*order) 26 } 27 log.Println("Close Pipeline") 28 pipeline.Close() 29 }() 30 31 pipeline.Receive(func(o Order){ 32 log.Printf("Received: %v", o) 33 }) 34 } 35 36 37 type LineItem struct { 38 Description string 39 Count int 40 PriceUSD gc.USD 41 } 42 43 type Order struct { 44 OrderNumber int 45 IsAuthenticated bool 46 IsDecrypted bool 47 Credentials string 48 CCardNumber string 49 CCardExpDate string 50 LineItems []LineItem 51 } 52 53 func GetOrders() []*Order { 54 55 order1 := &Order{ 56 10001, 57 false, 58 false, 59 "alice,secret", 60 "7b/HWvtIB9a16AYk+Yv6WWwer3GFbxpjoR+GO9iHIYY=", 61 "0922", 62 []LineItem{ 63 LineItem{"Apples", 1, gc.USD{4, 50}}, 64 LineItem{"Oranges", 4, gc.USD{12, 00}}, 65 }, 66 } 67 68 order2 := &Order{ 69 10002, 70 false, 71 false, 72 "bob,secret", 73 "EOc3kF/OmxY+dRCaYRrey8h24QoGzVU0/T2QKVCHb1Q=", 74 "0123", 75 []LineItem{ 76 LineItem{"Milk", 2, gc.USD{8, 00}}, 77 LineItem{"Sugar", 1, gc.USD{2, 25}}, 78 LineItem{"Salt", 3, gc.USD{3, 75}}, 79 }, 80 } 81 orders := []*Order{order1, order2} 82 return orders 83 } 84 85 var AESEncryptionKey = "a very very very very secret key" 86 87 func encrypt(rawString string) (string, error) { 88 rawBytes := []byte(rawString) 89 block, err := aes.NewCipher([]byte(AESEncryptionKey)) 90 if err != nil { 91 return "", err 92 } 93 if len(rawBytes)%aes.BlockSize != 0 { 94 padding := aes.BlockSize - len(rawBytes)%aes.BlockSize 95 padText := bytes.Repeat([]byte{byte(0)}, padding) 96 rawBytes = append(rawBytes, padText...) 97 } 98 ciphertext := make([]byte, aes.BlockSize+len(rawBytes)) 99 iv := ciphertext[:aes.BlockSize] 100 if _, err := io.ReadFull(rand.Reader, iv); err != nil { 101 return "", err 102 } 103 mode := cipher.NewCBCEncrypter(block, iv) 104 mode.CryptBlocks(ciphertext[aes.BlockSize:], rawBytes) 105 return base64.StdEncoding.EncodeToString(ciphertext), nil 106 } 107 108 func decrypt(encodedValue string) (string, error) { 109 block, err := aes.NewCipher([]byte(AESEncryptionKey)) 110 if err != nil { 111 return "", err 112 } 113 b, err := base64.StdEncoding.DecodeString(encodedValue) 114 if err != nil { 115 return "", err 116 } 117 if len(b) < aes.BlockSize { 118 return "", errors.New("ciphertext too short") 119 } 120 iv := b[:aes.BlockSize] 121 b = b[aes.BlockSize:] 122 if len(b)%aes.BlockSize != 0 { 123 return "", errors.New("ciphertext is not a multiple of the block size") 124 } 125 mode := cipher.NewCBCDecrypter(block, iv) 126 mode.CryptBlocks(b, b) 127 b = bytes.TrimRight(b, "\x00") 128 return string(b), nil 129 } 130 131 type Filterer interface { 132 Filter(input chan Order) chan Order 133 } 134 135 type Authenticate struct {} 136 func (a Authenticate) Filter(input chan Order) chan Order { 137 output := make(chan Order) 138 go func(){ 139 for order := range input { 140 usernamePwd := strings.Split(order.Credentials, ",") 141 if usernamePwd[1] == "secret" { 142 order.IsAuthenticated = true 143 output <- order 144 } else { 145 order.IsAuthenticated = false 146 errMsg := fmt.Sprintf("Error: Invalid password for order Id: %d", order.OrderNumber) 147 log.Println("Error:", errors.New(errMsg)) 148 output <- order 149 } 150 } 151 close(output) 152 }() 153 return output 154 } 155 156 type Decrypt struct {} 157 func (d Decrypt) Filter(input chan Order) chan Order { 158 output := make(chan Order) 159 go func(){ 160 for order := range input { 161 creditCardNo, err := decrypt(order.CCardNumber) 162 if err != nil { 163 order.IsDecrypted = false 164 log.Println("Error:", err.Error()) 165 } else { 166 order.IsDecrypted = true 167 order.CCardNumber = creditCardNo 168 output <- order 169 } 170 } 171 close(output) 172 }() 173 return output 174 } 175 176 func ChargeCard(ccardNo string, amount gc.USD) { 177 fmt.Printf("Credit card %v%v charged %v\n", gu.Dashes(len(ccardNo)-4, "X"), ccardNo[len(ccardNo)-4:], amount) 178 } 179 180 type Charge struct {} 181 func (c Charge) Filter(input chan Order) chan Order { 182 output := make(chan Order) 183 go func(){ 184 for order := range input { 185 if order.IsAuthenticated && order.IsDecrypted { 186 total := gc.USD{0, 0} 187 for _, li := range order.LineItems { 188 total, _ = total.Add(li.PriceUSD) 189 } 190 ChargeCard(order.CCardNumber, total) 191 output <- order 192 } else { 193 errMsg := fmt.Sprintf("Error: Unable to charge order Id: %d", order.OrderNumber) 194 log.Println("Error:", errors.New(errMsg)) 195 } 196 } 197 close(output) 198 }() 199 return output 200 } 201 202 func BuildPipeline(filters ...Filterer) Filter { 203 source := make(chan Order) 204 var nextFilter chan Order 205 for _, filter := range filters { 206 if nextFilter == nil { 207 nextFilter = filter.Filter(source) 208 } else { 209 nextFilter = filter.Filter(nextFilter) 210 } 211 } 212 return Filter{ input: source, output: nextFilter } 213 } 214 215 type Filter struct { 216 input chan Order 217 output chan Order 218 } 219 220 func (f *Filter) Send(order Order) { 221 f.input <- order 222 } 223 224 func (f *Filter) Receive(callback func(Order)){ 225 for o := range f.output { 226 callback(o) 227 } 228 } 229 230 func (f *Filter) Close() { 231 close(f.input) 232 } 233