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