├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── address.go ├── address_test.go ├── credit.go ├── credit_test.go ├── email.go ├── email_test.go ├── examples ├── customize │ ├── go.mod │ └── main.go └── simple │ ├── go.mod │ └── main.go ├── go.mod ├── go.sum ├── id.go ├── id_test.go ├── masker.go ├── masker_test.go ├── mobile.go ├── mobile_test.go ├── name.go ├── name_test.go ├── none.go ├── password.go ├── password_test.go ├── telephone.go ├── telephone_test.go ├── url.go └── url_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.17 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -v -race -short -count=1 -coverprofile=.cover ./... 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | coverage.txt 4 | .cover -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 White Chang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | 3 | test: 4 | go test -v -race -short -count=1 -coverprofile=.cover ./... 5 | 6 | cover: 7 | go tool cover -html=.cover -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Masker v2 2 | 3 | [![build workflow](https://github.com/ggwhite/go-masker/actions/workflows/go.yml/badge.svg)](https://github.com/ggwhite/go-masker/actions) 4 | [![GoDoc](https://godoc.org/github.com/ggwhite/go-masker?status.svg)](https://godoc.org/github.com/ggwhite/go-masker) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/ggwhite/go-masker)](https://goreportcard.com/report/github.com/ggwhite/go-masker) 6 | [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/ggwhite/go-masker/blob/master/LICENSE) 7 | [![Release](https://img.shields.io/github/release/ggwhite/go-masker.svg?style=flat-square)](https://github.com/ggwhite/go-masker/releases/latest) 8 | 9 | Go Masker v2 is a tool for masking sensitive data in Go code. It provides a simple and convenient way to replace sensitive information, such as passwords or API keys, with placeholder values. 10 | 11 | * [Install](#install) 12 | * [Usage](#usage) 13 | * [Custom Masker](#custom-masker) 14 | * [Contributors](#contributors) 15 | 16 | ## Install 17 | 18 | To install Go Masker v2, you can use the following command: 19 | 20 | ```bash 21 | go get -u github.com/ggwhite/go-masker 22 | ``` 23 | 24 | ## Usage 25 | 26 | To use Go Masker v2, you can create a new instance of the `Masker` type and then use its methods to mask sensitive data. For example: 27 | 28 | ```go 29 | package main 30 | 31 | import ( 32 | "log" 33 | masker "github.com/ggwhite/go-masker/v2" 34 | ) 35 | 36 | type Foo struct { 37 | Name string `mask:"name"` 38 | Mobile string `mask:"mobile"` 39 | } 40 | 41 | func main() { 42 | foo := &Foo{ 43 | Name: "ggwhite", 44 | Mobile: "0987987987", 45 | } 46 | 47 | m := masker.NewMaskerMarshaler() 48 | 49 | t, err := m.Struct(foo) 50 | log.Println(t) 51 | log.Println(err) 52 | } 53 | ``` 54 | 55 | This will produce the following output: 56 | 57 | ``` 58 | t = &{g**hite 0987***987} 59 | err = 60 | ``` 61 | 62 | For more information about how to use Go Masker v2, please refer to the [documentation](https://pkg.go.dev/github.com/ggwhite/go-masker/v2). 63 | 64 | ## Custom Masker 65 | 66 | You can also create a custom masker by implementing the `Masker` interface. For example: 67 | 68 | ```go 69 | package main 70 | 71 | import ( 72 | "log" 73 | masker "github.com/ggwhite/go-masker/v2" 74 | ) 75 | 76 | type MyEmailMasker struct{} 77 | 78 | func (m *MyEmailMasker) Marshal(s, i string) string { 79 | return "myemailmasker" 80 | } 81 | 82 | type MyMasker struct{} 83 | 84 | func (m *MyMasker) Marshal(s, i string) string { 85 | return "mymasker" 86 | } 87 | 88 | func main() { 89 | m := masker.NewMaskerMarshaler() 90 | 91 | // Register custom masker and override default masker 92 | m.Register(masker.MaskerTypeEmail, &MyEmailMasker{}) 93 | 94 | log.Println(m.Marshal(masker.MaskerTypeEmail, "email")) // myemailmasker 95 | 96 | // Register custom masker and use it 97 | m.Register("mymasker", &MyMasker{}) 98 | 99 | log.Println(m.Marshal("mymasker", "1234567")) // mymasker 100 | } 101 | ``` 102 | 103 | 104 | ## Contributors 105 | 106 | Thanks to all the people who already contributed! 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /address.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "math" 4 | 5 | // AddressMasker is a masker for address 6 | type AddressMasker struct{} 7 | 8 | // Marshal masks address 9 | // It masks last 6 digits of address 10 | // Example: 11 | // 12 | // AddressMasker{}.Marshal("*", "台北市內湖區內湖路一段737巷1號1樓") // returns "台北市內湖區******" 13 | func (m *AddressMasker) Marshal(s string, i string) string { 14 | l := len([]rune(i)) 15 | if l == 0 { 16 | return "" 17 | } 18 | if l <= 6 { 19 | return strLoop(s, 6) 20 | } 21 | return overlay(i, strLoop(s, 6), 6, math.MaxInt) 22 | } 23 | -------------------------------------------------------------------------------- /address_test.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "testing" 4 | 5 | func TestAddressMasker_Marshal(t *testing.T) { 6 | type args struct { 7 | s string 8 | i string 9 | } 10 | tests := []struct { 11 | name string 12 | m *AddressMasker 13 | args args 14 | want string 15 | }{ 16 | { 17 | name: "Empty Input", 18 | args: args{ 19 | s: "*", 20 | i: "", 21 | }, 22 | want: "", 23 | }, 24 | { 25 | name: "Happy Pass", 26 | args: args{ 27 | s: "*", 28 | i: "台北市大安區敦化南路五段7788號378樓", 29 | }, 30 | want: "台北市大安區******", 31 | }, 32 | { 33 | name: "Length Less Than 6", 34 | args: args{ 35 | s: "*", 36 | i: "台北市", 37 | }, 38 | want: "******", 39 | }, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | m := &AddressMasker{} 44 | if got := m.Marshal(tt.args.s, tt.args.i); got != tt.want { 45 | t.Errorf("AddressMasker.Marshal() = %v, want %v", got, tt.want) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /credit.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | type CreditMasker struct{} 4 | 5 | // Marshal masks credit card number 6 | // It mask 6 digits from the 7'th digit 7 | // Example: 8 | // 9 | // CreditMasker{}.Marshal("*", "4111111111111111") // returns "**** **** **** 1111" 10 | func (m *CreditMasker) Marshal(s string, i string) string { 11 | l := len([]rune(i)) 12 | if l == 0 { 13 | return "" 14 | } 15 | return overlay(i, strLoop(s, 6), 6, 12) 16 | } 17 | -------------------------------------------------------------------------------- /credit_test.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "testing" 4 | 5 | func TestCreditMasker_Marshal(t *testing.T) { 6 | type args struct { 7 | s string 8 | i string 9 | } 10 | tests := []struct { 11 | name string 12 | m *CreditMasker 13 | args args 14 | want string 15 | }{ 16 | { 17 | name: "Empty Input", 18 | args: args{ 19 | s: "*", 20 | i: "", 21 | }, 22 | want: "", 23 | }, 24 | { 25 | name: "VISA JCB MasterCard", 26 | args: args{ 27 | s: "*", 28 | i: "1234567890123456", 29 | }, 30 | want: "123456******3456", 31 | }, 32 | { 33 | name: "American Express", 34 | args: args{ 35 | s: "*", 36 | i: "123456789012345", 37 | }, 38 | want: "123456******345", 39 | }, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | m := &CreditMasker{} 44 | if got := m.Marshal(tt.args.s, tt.args.i); got != tt.want { 45 | t.Errorf("CreditMasker.Marshal() = %v, want %v", got, tt.want) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /email.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "strings" 4 | 5 | // EmailMasker is a masker for email 6 | type EmailMasker struct{} 7 | 8 | // Marshal masks email 9 | // It keep domain and the first 3 letters 10 | // Example: 11 | // 12 | // AddressMasker{}.Marshal("*", "ggw.chang@gmail.com") // returns "ggw****@gmail.com" 13 | func (m *EmailMasker) Marshal(s string, i string) string { 14 | l := len([]rune(i)) 15 | if l == 0 { 16 | return "" 17 | } 18 | 19 | tmp := strings.Split(i, "@") 20 | if len(tmp) == 1 { 21 | return overlay(i, strLoop(s, 4), 3, 7) 22 | } 23 | 24 | addr := tmp[0] 25 | domain := tmp[1] 26 | 27 | addr = overlay(addr, strLoop(s, 4), 3, len(addr)) 28 | 29 | return addr + "@" + domain 30 | } 31 | -------------------------------------------------------------------------------- /email_test.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "testing" 4 | 5 | func TestEmailMasker_Marshal(t *testing.T) { 6 | type args struct { 7 | s string 8 | i string 9 | } 10 | tests := []struct { 11 | name string 12 | m *EmailMasker 13 | args args 14 | want string 15 | }{ 16 | { 17 | name: "Empty Input", 18 | args: args{ 19 | s: "*", 20 | i: "", 21 | }, 22 | want: "", 23 | }, 24 | { 25 | name: "Happy Pass", 26 | args: args{ 27 | s: "*", 28 | i: "ggw.chang@gmail.com", 29 | }, 30 | want: "ggw****@gmail.com", 31 | }, 32 | { 33 | name: "Address Less Than 3", 34 | args: args{ 35 | s: "*", 36 | i: "qq@gmail.com", 37 | }, 38 | want: "qq****@gmail.com", 39 | }, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | m := &EmailMasker{} 44 | if got := m.Marshal(tt.args.s, tt.args.i); got != tt.want { 45 | t.Errorf("EmailMasker.Marshal() = %v, want %v", got, tt.want) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/customize/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ggwhite/go-masker/v2/example/customize 2 | 3 | go 1.19 4 | 5 | require github.com/ggwhite/go-masker/v2 v2.0.0 6 | 7 | replace github.com/ggwhite/go-masker/v2 => ../../ 8 | -------------------------------------------------------------------------------- /examples/customize/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/ggwhite/go-masker/v2" 7 | ) 8 | 9 | type MyEmailMasker struct{} 10 | 11 | func (m *MyEmailMasker) Marshal(s, i string) string { 12 | return "myemailmasker" 13 | } 14 | 15 | type MyMasker struct{} 16 | 17 | func (m *MyMasker) Marshal(s, i string) string { 18 | return "mymasker" 19 | } 20 | 21 | func main() { 22 | 23 | m := masker.NewMaskerMarshaler() 24 | 25 | log.Println(m.Marshal(masker.MaskerTypeNone, "none")) // none 26 | log.Println(m.Marshal(masker.MaskerTypePassword, "password")) // ************** 27 | log.Println(m.Marshal(masker.MaskerTypeName, "name")) // n**e 28 | log.Println(m.Marshal(masker.MaskerTypeAddress, "address")) // addres****** 29 | log.Println(m.Marshal(masker.MaskerTypeEmail, "email")) // ema**** 30 | log.Println(m.Marshal(masker.MaskerTypeMobile, "mobile")) // mobi*** 31 | log.Println(m.Marshal(masker.MaskerTypeTel, "tel")) // tel 32 | log.Println(m.Marshal(masker.MaskerTypeID, "id")) // id**** 33 | log.Println(m.Marshal(masker.MaskerTypeCredit, "4111111111111111")) // 411111******1111 34 | log.Println(m.Marshal(masker.MaskerTypeURL, "http://john:password@localhost:3000")) // http://john:xxxxx@localhost:3000 35 | 36 | // Register custom masker and override default masker 37 | m.Register(masker.MaskerTypeEmail, &MyEmailMasker{}) 38 | 39 | log.Println(m.Marshal(masker.MaskerTypeEmail, "email")) 40 | 41 | // Register custom masker and use it 42 | m.Register("mymasker", &MyMasker{}) 43 | 44 | log.Println(m.Marshal("mymasker", "1234567")) 45 | } 46 | -------------------------------------------------------------------------------- /examples/simple/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ggwhite/go-masker/v2/example/simple 2 | 3 | go 1.19 4 | 5 | require github.com/ggwhite/go-masker/v2 v2.0.0 6 | 7 | replace github.com/ggwhite/go-masker/v2 => ../../ 8 | -------------------------------------------------------------------------------- /examples/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/ggwhite/go-masker/v2" 7 | ) 8 | 9 | type Foo struct { 10 | Name string `mask:"name"` 11 | Email string `mask:"email"` 12 | Password string `mask:"password"` 13 | ID string `mask:"id"` 14 | Address string `mask:"addr"` 15 | Mobile string `mask:"mobile"` 16 | Telephone string `mask:"tel"` 17 | Credit string `mask:"credit"` 18 | URL string `mask:"url"` 19 | Foo *Foo `mask:"struct"` 20 | } 21 | 22 | func main() { 23 | m := masker.NewMaskerMarshaler() 24 | 25 | log.Println(m.List()) 26 | 27 | foo1 := &Foo{ 28 | Name: "John Doe", 29 | Email: "john@gmail.com", 30 | Password: "password", 31 | ID: "1234567890", 32 | Address: "123 Main St", 33 | Mobile: "1234567890", 34 | Telephone: "1234567890", 35 | Credit: "4111111111111111", 36 | URL: "http://john:password@localhost:3000", 37 | Foo: &Foo{ 38 | Name: "John Doe", 39 | Email: "john@gmail.com", 40 | Password: "password", 41 | ID: "1234567890", 42 | Address: "123 Main St", 43 | Mobile: "1234567890", 44 | Telephone: "1234567890", 45 | Credit: "4111111111111111", 46 | URL: "http://john:password@localhost:3000", 47 | }, 48 | } 49 | 50 | foo2, _ := m.Struct(foo1) 51 | 52 | log.Println(foo1) 53 | log.Println(foo1.Foo) 54 | log.Println(foo2) 55 | log.Println(foo2.(*Foo).Foo) 56 | } 57 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ggwhite/go-masker/v2 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggwhite/go-masker/2e0bcb248e156bc26162b3ea9d73916bd1de421c/go.sum -------------------------------------------------------------------------------- /id.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | // IDMasker is a masker for ID 4 | type IDMasker struct{} 5 | 6 | // Marshal masks ID 7 | // It mask last 4 digits of ID number 8 | // Example: 9 | // 10 | // IDMasker{}.Marshal("*", "1234567890") // returns "1234****890" 11 | func (m *IDMasker) Marshal(s string, i string) string { 12 | l := len([]rune(i)) 13 | if l == 0 { 14 | return "" 15 | } 16 | return overlay(i, strLoop(s, 4), 6, 10) 17 | } 18 | -------------------------------------------------------------------------------- /id_test.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "testing" 4 | 5 | func TestIDMasker_Marshal(t *testing.T) { 6 | type args struct { 7 | s string 8 | i string 9 | } 10 | tests := []struct { 11 | name string 12 | m *IDMasker 13 | args args 14 | want string 15 | }{ 16 | { 17 | name: "Empty Input", 18 | args: args{ 19 | s: "*", 20 | i: "", 21 | }, 22 | want: "", 23 | }, 24 | { 25 | name: "Happy Pass", 26 | args: args{ 27 | s: "*", 28 | i: "A123456789", 29 | }, 30 | want: "A12345****", 31 | }, 32 | { 33 | name: "Length Less Than 6", 34 | args: args{ 35 | s: "*", 36 | i: "A12", 37 | }, 38 | want: "A12****", 39 | }, 40 | { 41 | name: "Length Less Than 6", 42 | args: args{ 43 | s: "*", 44 | i: "A", 45 | }, 46 | want: "A****", 47 | }, 48 | { 49 | name: "Length Between 6 and 10", 50 | args: args{ 51 | s: "*", 52 | i: "A123456", 53 | }, 54 | want: "A12345****", 55 | }, 56 | } 57 | for _, tt := range tests { 58 | t.Run(tt.name, func(t *testing.T) { 59 | m := &IDMasker{} 60 | if got := m.Marshal(tt.args.s, tt.args.i); got != tt.want { 61 | t.Errorf("IDMasker.Marshal() = %v, want %v", got, tt.want) 62 | } 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /masker.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | const tagName = "mask" 9 | 10 | // MaskerType is a string type for masker type 11 | type MaskerType string 12 | 13 | // MaskerType constants 14 | const ( 15 | MaskerTypeNone MaskerType = "none" 16 | MaskerTypePassword MaskerType = "password" 17 | MaskerTypeName MaskerType = "name" 18 | MaskerTypeAddress MaskerType = "addr" 19 | MaskerTypeEmail MaskerType = "email" 20 | MaskerTypeMobile MaskerType = "mobile" 21 | MaskerTypeTel MaskerType = "tel" 22 | MaskerTypeID MaskerType = "id" 23 | MaskerTypeCredit MaskerType = "credit" 24 | MaskerTypeURL MaskerType = "url" 25 | MaskerTypeStruct MaskerType = "struct" 26 | ) 27 | 28 | // Masker is an interface for masking sensitive data 29 | type Masker interface { 30 | Marshal(string, string) string 31 | } 32 | 33 | // MaskerMarshaler is a masker marshaler 34 | type MaskerMarshaler struct { 35 | Maskers map[MaskerType]Masker 36 | masker string // default masker 37 | } 38 | 39 | // Marshal returns a masked value by masker type 40 | // It is used for masking sensitive data 41 | // Example: 42 | // 43 | // m := masker.NewMaskerMarshaler() 44 | // log.Println(m.Marshal(masker.MaskerTypeNone, "none")) // none 45 | // log.Println(m.Marshal(masker.MaskerTypePassword, "password")) // ************** 46 | // log.Println(m.Marshal(masker.MaskerTypeName, "name")) // n**e 47 | // log.Println(m.Marshal(masker.MaskerTypeAddress, "address")) // addres****** 48 | // log.Println(m.Marshal(masker.MaskerTypeEmail, "email")) // ema**** 49 | // log.Println(m.Marshal(masker.MaskerTypeMobile, "mobile")) // mobi*** 50 | // log.Println(m.Marshal(masker.MaskerTypeTel, "tel")) // tel 51 | // log.Println(m.Marshal(masker.MaskerTypeID, "id")) // id**** 52 | // log.Println(m.Marshal(masker.MaskerTypeCredit, "4111111111111111")) // 411111******1111 53 | // log.Println(m.Marshal(masker.MaskerTypeURL, "http://john:password@localhost:3000")) // http://john:xxxxx@localhost:3000 54 | func (m *MaskerMarshaler) Marshal(t MaskerType, value string) (string, error) { 55 | masker, ok := m.Maskers[t] 56 | if !ok { 57 | return "", fmt.Errorf("masker %v not found", t) 58 | } 59 | return masker.Marshal(m.masker, value), nil 60 | } 61 | 62 | // Register adds a masker by masker type 63 | // It is used for adding or override a masker by masker type 64 | // Example: 65 | // 66 | // m := masker.NewMaskerMarshaler() 67 | // m.Register(masker.MaskerTypePassword, &PasswordMasker{}) 68 | // log.Println(m.List()) // [password name addr email tel id url none mobile credit] 69 | func (m *MaskerMarshaler) Register(t MaskerType, masker Masker) { 70 | m.Maskers[t] = masker 71 | } 72 | 73 | // Unregister removes a masker by masker type 74 | // It is used for removing a masker by masker type 75 | // Example: 76 | // 77 | // m := masker.NewMaskerMarshaler() 78 | // m.Unregister(masker.MaskerTypePassword) 79 | // log.Println(m.List()) // [name addr email tel id url none mobile credit] 80 | func (m *MaskerMarshaler) Unregister(t MaskerType) { 81 | delete(m.Maskers, t) 82 | } 83 | 84 | // Get returns a masker by masker type 85 | // It is used for getting a masker by masker type 86 | // Example: 87 | // 88 | // m := masker.NewMaskerMarshaler() 89 | // masker, _ := m.Get(masker.MaskerTypePassword) 90 | // log.Println(masker) // &{PasswordMasker} 91 | func (m *MaskerMarshaler) Get(t MaskerType) (Masker, error) { 92 | masker, ok := m.Maskers[t] 93 | if !ok { 94 | return nil, fmt.Errorf("masker %v not found", t) 95 | } 96 | return masker, nil 97 | } 98 | 99 | // List returns a list of masker types 100 | // It is used for listing all masker types 101 | // Example: 102 | // 103 | // m := masker.NewMaskerMarshaler() 104 | // log.Println(m.List()) // [password name addr email tel id url none mobile credit] 105 | func (m *MaskerMarshaler) List() []MaskerType { 106 | var list []MaskerType 107 | for t := range m.Maskers { 108 | list = append(list, t) 109 | } 110 | return list 111 | } 112 | 113 | func (m *MaskerMarshaler) SetMasker(masker string) { 114 | m.masker = masker 115 | } 116 | 117 | // Struct must input a interface{}, add tag mask on struct fields, after Struct(), return a pointer interface{} of input type and it will be masked with the tag format type 118 | // 119 | // Example: 120 | // 121 | // type Foo struct { 122 | // Name string `mask:"name"` 123 | // Email string `mask:"email"` 124 | // Password string `mask:"password"` 125 | // ID string `mask:"id"` 126 | // Address string `mask:"addr"` 127 | // Mobile string `mask:"mobile"` 128 | // Telephone string `mask:"tel"` 129 | // Credit string `mask:"credit"` 130 | // URL string `mask:"url"` 131 | // Foo *Foo `mask:"struct"` 132 | // } 133 | // 134 | // func main() { 135 | // m := masker.NewMaskerMarshaler() 136 | // log.Println(m.List()) // [password name addr email tel id url none mobile credit] 137 | // foo1 := &Foo{ 138 | // Name: "John Doe", 139 | // Email: "john@gmail.com", 140 | // Password: "password", 141 | // ID: "1234567890", 142 | // Address: "123 Main St", 143 | // Mobile: "1234567890", 144 | // Telephone: "1234567890", 145 | // Credit: "4111111111111111", 146 | // URL: "http://john:password@localhost:3000", 147 | // Foo: &Foo{ 148 | // Name: "John Doe", 149 | // Email: "john@gmail.com", 150 | // Password: "password", 151 | // ID: "1234567890", 152 | // Address: "123 Main St", 153 | // Mobile: "1234567890", 154 | // Telephone: "1234567890", 155 | // Credit: "4111111111111111", 156 | // URL: "http://john:password@localhost:3000", 157 | // }, 158 | // } 159 | // 160 | // foo2, _ := m.Struct(foo1) 161 | // 162 | // log.Println(foo1) // &{John Doe john@gmail.com password 1234567890 123 Main St 1234567890 1234567890 4111111111111111 http://john:password@localhost:3000 0xc0000001e0} 163 | // log.Println(foo1.Foo) // &{John Doe john@gmail.com password 1234567890 123 Main St 1234567890 1234567890 4111111111111111 http://john:password@localhost:3000 } 164 | // log.Println(foo2) // &{J**n D**e joh****@gmail.com ************** 123456**** 123 Ma****** 1234***890 (12)3456-**** 411111******1111 http://john:xxxxx@localhost:3000 0xc000000320} 165 | // log.Println(foo2.(*Foo).Foo) // &{J**n D**e joh****@gmail.com ************** 123456**** 123 Ma****** 1234***890 (12)3456-**** 411111******1111 http://john:xxxxx@localhost:3000 } 166 | // } 167 | func (m *MaskerMarshaler) Struct(s interface{}) (interface{}, error) { 168 | if s == nil { 169 | return nil, fmt.Errorf("input is nil") 170 | } 171 | 172 | var selem, tptr reflect.Value 173 | 174 | st := reflect.TypeOf(s) 175 | 176 | if st.Kind() == reflect.Ptr { 177 | tptr = reflect.New(st.Elem()) 178 | selem = reflect.ValueOf(s).Elem() 179 | } else { 180 | tptr = reflect.New(st) 181 | selem = reflect.ValueOf(s) 182 | } 183 | 184 | for i := 0; i < selem.NumField(); i++ { 185 | if !selem.Type().Field(i).IsExported() { 186 | continue 187 | } 188 | mtag := selem.Type().Field(i).Tag.Get(tagName) 189 | if len(mtag) == 0 { 190 | tptr.Elem().Field(i).Set(selem.Field(i)) 191 | continue 192 | } 193 | switch selem.Field(i).Type().Kind() { 194 | default: 195 | tptr.Elem().Field(i).Set(selem.Field(i)) 196 | case reflect.String: 197 | v, err := m.Marshal(MaskerType(mtag), selem.Field(i).String()) 198 | if err != nil { 199 | return nil, err 200 | } 201 | tptr.Elem().Field(i).SetString(v) 202 | case reflect.Struct: 203 | if MaskerType(mtag) == MaskerTypeStruct { 204 | _t, err := m.Struct(selem.Field(i).Interface()) 205 | if err != nil { 206 | return nil, err 207 | } 208 | tptr.Elem().Field(i).Set(reflect.ValueOf(_t).Elem()) 209 | } 210 | case reflect.Ptr: 211 | if selem.Field(i).IsNil() { 212 | continue 213 | } 214 | if MaskerType(mtag) == MaskerTypeStruct { 215 | _t, err := m.Struct(selem.Field(i).Interface()) 216 | if err != nil { 217 | return nil, err 218 | } 219 | tptr.Elem().Field(i).Set(reflect.ValueOf(_t)) 220 | } 221 | case reflect.Slice: 222 | if selem.Field(i).IsNil() { 223 | continue 224 | } 225 | if selem.Field(i).Type().Elem().Kind() == reflect.String { 226 | orgval := selem.Field(i).Interface().([]string) 227 | newval := make([]string, len(orgval)) 228 | for i, val := range selem.Field(i).Interface().([]string) { 229 | v, err := m.Marshal(MaskerType(mtag), val) 230 | if err != nil { 231 | return nil, err 232 | } 233 | newval[i] = v 234 | } 235 | tptr.Elem().Field(i).Set(reflect.ValueOf(newval)) 236 | continue 237 | } 238 | if selem.Field(i).Type().Elem().Kind() == reflect.Struct && MaskerType(mtag) == MaskerTypeStruct { 239 | newval := reflect.MakeSlice(selem.Field(i).Type(), 0, selem.Field(i).Len()) 240 | for j, l := 0, selem.Field(i).Len(); j < l; j++ { 241 | _n, err := m.Struct(selem.Field(i).Index(j).Interface()) 242 | if err != nil { 243 | return nil, err 244 | } 245 | newval = reflect.Append(newval, reflect.ValueOf(_n).Elem()) 246 | } 247 | tptr.Elem().Field(i).Set(newval) 248 | continue 249 | } 250 | if selem.Field(i).Type().Elem().Kind() == reflect.Ptr && MaskerType(mtag) == MaskerTypeStruct { 251 | newval := reflect.MakeSlice(selem.Field(i).Type(), 0, selem.Field(i).Len()) 252 | for j, l := 0, selem.Field(i).Len(); j < l; j++ { 253 | _n, err := m.Struct(selem.Field(i).Index(j).Interface()) 254 | if err != nil { 255 | return nil, err 256 | } 257 | newval = reflect.Append(newval, reflect.ValueOf(_n)) 258 | } 259 | tptr.Elem().Field(i).Set(newval) 260 | continue 261 | } 262 | if selem.Field(i).Type().Elem().Kind() == reflect.Interface && MaskerType(mtag) == MaskerTypeStruct { 263 | newval := reflect.MakeSlice(selem.Field(i).Type(), 0, selem.Field(i).Len()) 264 | for j, l := 0, selem.Field(i).Len(); j < l; j++ { 265 | _n, err := m.Struct(selem.Field(i).Index(j).Interface()) 266 | if err != nil { 267 | return nil, err 268 | } 269 | if reflect.TypeOf(selem.Field(i).Index(j).Interface()).Kind() != reflect.Ptr { 270 | newval = reflect.Append(newval, reflect.ValueOf(_n).Elem()) 271 | } else { 272 | newval = reflect.Append(newval, reflect.ValueOf(_n)) 273 | } 274 | } 275 | tptr.Elem().Field(i).Set(newval) 276 | continue 277 | } 278 | case reflect.Interface: 279 | if selem.Field(i).IsNil() { 280 | continue 281 | } 282 | if MaskerType(mtag) != MaskerTypeStruct { 283 | continue 284 | } 285 | _t, err := m.Struct(selem.Field(i).Interface()) 286 | if err != nil { 287 | return nil, err 288 | } 289 | if reflect.TypeOf(selem.Field(i).Interface()).Kind() != reflect.Ptr { 290 | tptr.Elem().Field(i).Set(reflect.ValueOf(_t).Elem()) 291 | } else { 292 | tptr.Elem().Field(i).Set(reflect.ValueOf(_t)) 293 | } 294 | } 295 | } 296 | 297 | return tptr.Interface(), nil 298 | } 299 | 300 | // NewMaskerMarshaler returns a new masker marshaler 301 | // It has default maskers and default masker 302 | // Default maskers are: 303 | // - NoneMasker 304 | // - PasswordMasker 305 | // - NameMasker 306 | // - AddressMasker 307 | // - EmailMasker 308 | // - MobileMasker 309 | // - TelephoneMasker 310 | // - IDMasker 311 | // - CreditMasker 312 | // - URLMasker 313 | // 314 | // Default masker is "*" 315 | // It is used for masking sensitive data 316 | func NewMaskerMarshaler() *MaskerMarshaler { 317 | return &MaskerMarshaler{ 318 | Maskers: map[MaskerType]Masker{ 319 | MaskerTypeNone: &NoneMasker{}, 320 | MaskerTypePassword: &PasswordMasker{}, 321 | MaskerTypeName: &NameMasker{}, 322 | MaskerTypeAddress: &AddressMasker{}, 323 | MaskerTypeEmail: &EmailMasker{}, 324 | MaskerTypeMobile: &MobileMasker{}, 325 | MaskerTypeTel: &TelephoneMasker{}, 326 | MaskerTypeID: &IDMasker{}, 327 | MaskerTypeCredit: &CreditMasker{}, 328 | MaskerTypeURL: &URLMasker{}, 329 | }, 330 | masker: "*", 331 | } 332 | } 333 | 334 | // DefaultMaskerMarshaler is a default masker marshaler 335 | // It has default maskers and default masker 336 | // Default maskers are: 337 | // - NoneMasker 338 | // - PasswordMasker 339 | // - NameMasker 340 | // - AddressMasker 341 | // - EmailMasker 342 | // - MobileMasker 343 | // - TelephoneMasker 344 | // - IDMasker 345 | // - CreditMasker 346 | // - URLMasker 347 | // 348 | // Default masker is "*" 349 | // It is used for masking sensitive data 350 | var DefaultMaskerMarshaler = &MaskerMarshaler{ 351 | Maskers: map[MaskerType]Masker{ 352 | MaskerTypeNone: &NoneMasker{}, 353 | MaskerTypePassword: &PasswordMasker{}, 354 | MaskerTypeName: &NameMasker{}, 355 | MaskerTypeAddress: &AddressMasker{}, 356 | MaskerTypeEmail: &EmailMasker{}, 357 | MaskerTypeMobile: &MobileMasker{}, 358 | MaskerTypeTel: &TelephoneMasker{}, 359 | MaskerTypeID: &IDMasker{}, 360 | MaskerTypeCredit: &CreditMasker{}, 361 | MaskerTypeURL: &URLMasker{}, 362 | }, 363 | masker: "*", 364 | } 365 | 366 | func strLoop(str string, length int) string { 367 | var mask string 368 | for i := 1; i <= length; i++ { 369 | mask += str 370 | } 371 | return mask 372 | } 373 | 374 | func overlay(str string, overlay string, start int, end int) (overlayed string) { 375 | r := []rune(str) 376 | l := len([]rune(r)) 377 | 378 | if l == 0 { 379 | return "" 380 | } 381 | 382 | if start < 0 { 383 | start = 0 384 | } 385 | if start > l { 386 | start = l 387 | } 388 | if end < 0 { 389 | end = 0 390 | } 391 | if end > l { 392 | end = l 393 | } 394 | if start > end { 395 | tmp := start 396 | start = end 397 | end = tmp 398 | } 399 | 400 | overlayed = "" 401 | overlayed += string(r[:start]) 402 | overlayed += overlay 403 | overlayed += string(r[end:]) 404 | return overlayed 405 | } 406 | -------------------------------------------------------------------------------- /masker_test.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestMaskerMarshaler_Marshal(t *testing.T) { 9 | type fields struct { 10 | Maskers map[MaskerType]Masker 11 | masker string 12 | } 13 | type args struct { 14 | t MaskerType 15 | value string 16 | } 17 | tests := []struct { 18 | name string 19 | fields fields 20 | args args 21 | want string 22 | wantErr bool 23 | }{ 24 | { 25 | name: "test marshal", 26 | fields: fields{ 27 | Maskers: map[MaskerType]Masker{ 28 | MaskerTypeNone: &NoneMasker{}, 29 | MaskerTypeID: &IDMasker{}, 30 | }, 31 | masker: "*", 32 | }, 33 | args: args{ 34 | t: MaskerTypeID, 35 | value: "A123456789", 36 | }, 37 | want: "A12345****", 38 | wantErr: false, 39 | }, 40 | { 41 | name: "test marshal not found", 42 | fields: fields{ 43 | Maskers: map[MaskerType]Masker{ 44 | MaskerTypeNone: &NoneMasker{}, 45 | }, 46 | masker: "*", 47 | }, 48 | args: args{ 49 | t: MaskerTypeID, 50 | value: "A123456789", 51 | }, 52 | want: "", 53 | wantErr: true, 54 | }, 55 | } 56 | for _, tt := range tests { 57 | t.Run(tt.name, func(t *testing.T) { 58 | m := &MaskerMarshaler{ 59 | Maskers: tt.fields.Maskers, 60 | masker: tt.fields.masker, 61 | } 62 | got, err := m.Marshal(tt.args.t, tt.args.value) 63 | if (err != nil) != tt.wantErr { 64 | t.Errorf("MaskerMarshaler.Marshal() error = %v, wantErr %v", err, tt.wantErr) 65 | return 66 | } 67 | if got != tt.want { 68 | t.Errorf("MaskerMarshaler.Marshal() = %v, want %v", got, tt.want) 69 | } 70 | }) 71 | } 72 | } 73 | 74 | func TestMaskerMarshaler_Register(t *testing.T) { 75 | type fields struct { 76 | Maskers map[MaskerType]Masker 77 | masker string 78 | } 79 | type args struct { 80 | t MaskerType 81 | masker Masker 82 | } 83 | tests := []struct { 84 | name string 85 | fields fields 86 | args args 87 | }{ 88 | { 89 | name: "test register", 90 | fields: fields{ 91 | Maskers: map[MaskerType]Masker{ 92 | MaskerTypeNone: &NoneMasker{}, 93 | }, 94 | masker: "*", 95 | }, 96 | args: args{ 97 | t: MaskerTypeID, 98 | masker: &IDMasker{}, 99 | }, 100 | }, 101 | } 102 | for _, tt := range tests { 103 | t.Run(tt.name, func(t *testing.T) { 104 | m := &MaskerMarshaler{ 105 | Maskers: tt.fields.Maskers, 106 | masker: tt.fields.masker, 107 | } 108 | m.Register(tt.args.t, tt.args.masker) 109 | }) 110 | } 111 | } 112 | 113 | func TestMaskerMarshaler_Unregister(t *testing.T) { 114 | type fields struct { 115 | Maskers map[MaskerType]Masker 116 | masker string 117 | } 118 | type args struct { 119 | t MaskerType 120 | } 121 | tests := []struct { 122 | name string 123 | fields fields 124 | args args 125 | }{ 126 | { 127 | name: "test unregister", 128 | fields: fields{ 129 | Maskers: map[MaskerType]Masker{ 130 | MaskerTypeNone: &NoneMasker{}, 131 | MaskerTypeID: &IDMasker{}, 132 | }, 133 | masker: "*", 134 | }, 135 | args: args{ 136 | t: MaskerTypeID, 137 | }, 138 | }, 139 | } 140 | for _, tt := range tests { 141 | t.Run(tt.name, func(t *testing.T) { 142 | m := &MaskerMarshaler{ 143 | Maskers: tt.fields.Maskers, 144 | masker: tt.fields.masker, 145 | } 146 | m.Unregister(tt.args.t) 147 | }) 148 | } 149 | } 150 | 151 | func TestMaskerMarshaler_Get(t *testing.T) { 152 | type fields struct { 153 | Maskers map[MaskerType]Masker 154 | masker string 155 | } 156 | type args struct { 157 | t MaskerType 158 | } 159 | tests := []struct { 160 | name string 161 | fields fields 162 | args args 163 | want Masker 164 | wantErr bool 165 | }{ 166 | { 167 | name: "test get", 168 | fields: fields{ 169 | Maskers: map[MaskerType]Masker{ 170 | MaskerTypeNone: &NoneMasker{}, 171 | MaskerTypeID: &IDMasker{}, 172 | }, 173 | masker: "*", 174 | }, 175 | args: args{ 176 | t: MaskerTypeID, 177 | }, 178 | want: &IDMasker{}, 179 | wantErr: false, 180 | }, 181 | { 182 | name: "test get not found", 183 | fields: fields{ 184 | Maskers: map[MaskerType]Masker{ 185 | MaskerTypeNone: &NoneMasker{}, 186 | }, 187 | masker: "*", 188 | }, 189 | args: args{ 190 | t: MaskerTypeID, 191 | }, 192 | want: nil, 193 | wantErr: true, 194 | }, 195 | } 196 | for _, tt := range tests { 197 | t.Run(tt.name, func(t *testing.T) { 198 | m := &MaskerMarshaler{ 199 | Maskers: tt.fields.Maskers, 200 | masker: tt.fields.masker, 201 | } 202 | got, err := m.Get(tt.args.t) 203 | if (err != nil) != tt.wantErr { 204 | t.Errorf("MaskerMarshaler.Get() error = %v, wantErr %v", err, tt.wantErr) 205 | return 206 | } 207 | if !reflect.DeepEqual(got, tt.want) { 208 | t.Errorf("MaskerMarshaler.Get() = %v, want %v", got, tt.want) 209 | } 210 | }) 211 | } 212 | } 213 | 214 | func TestMaskerMarshaler_List(t *testing.T) { 215 | type fields struct { 216 | Maskers map[MaskerType]Masker 217 | masker string 218 | } 219 | tests := []struct { 220 | name string 221 | fields fields 222 | want []MaskerType 223 | }{ 224 | { 225 | name: "test list", 226 | fields: fields{ 227 | Maskers: map[MaskerType]Masker{ 228 | MaskerTypeNone: &NoneMasker{}, 229 | MaskerTypeID: &IDMasker{}, 230 | }, 231 | masker: "*", 232 | }, 233 | want: []MaskerType{MaskerTypeNone, MaskerTypeID}, 234 | }, 235 | } 236 | for _, tt := range tests { 237 | t.Run(tt.name, func(t *testing.T) { 238 | m := &MaskerMarshaler{ 239 | Maskers: tt.fields.Maskers, 240 | masker: tt.fields.masker, 241 | } 242 | if got := m.List(); !reflect.DeepEqual(got, tt.want) { 243 | t.Errorf("MaskerMarshaler.List() = %v, want %v", got, tt.want) 244 | } 245 | }) 246 | } 247 | } 248 | 249 | func TestMaskerMarshaler_SetMasker(t *testing.T) { 250 | type fields struct { 251 | Maskers map[MaskerType]Masker 252 | masker string 253 | } 254 | type args struct { 255 | masker string 256 | } 257 | tests := []struct { 258 | name string 259 | fields fields 260 | args args 261 | }{ 262 | { 263 | name: "test set masker", 264 | fields: fields{ 265 | Maskers: map[MaskerType]Masker{ 266 | MaskerTypeNone: &NoneMasker{}, 267 | MaskerTypeID: &IDMasker{}, 268 | }, 269 | masker: "*", 270 | }, 271 | args: args{ 272 | masker: "#", 273 | }, 274 | }, 275 | } 276 | for _, tt := range tests { 277 | t.Run(tt.name, func(t *testing.T) { 278 | m := &MaskerMarshaler{ 279 | Maskers: tt.fields.Maskers, 280 | masker: tt.fields.masker, 281 | } 282 | m.SetMasker(tt.args.masker) 283 | }) 284 | } 285 | } 286 | 287 | func TestMaskerMarshaler_Struct(t *testing.T) { 288 | type fields struct { 289 | Maskers map[MaskerType]Masker 290 | masker string 291 | } 292 | type args struct { 293 | s interface{} 294 | } 295 | tests := []struct { 296 | name string 297 | fields fields 298 | args args 299 | want interface{} 300 | wantErr bool 301 | }{ 302 | { 303 | name: "test struct", 304 | fields: fields{ 305 | Maskers: map[MaskerType]Masker{ 306 | MaskerTypeNone: &NoneMasker{}, 307 | MaskerTypeID: &IDMasker{}, 308 | MaskerTypeName: &NameMasker{}, 309 | MaskerTypeEmail: &EmailMasker{}, 310 | }, 311 | masker: "*", 312 | }, 313 | args: args{ 314 | s: struct { 315 | ID string `mask:"id"` 316 | Name string `mask:"name"` 317 | Account struct { 318 | Name string `mask:"name"` 319 | Email string `mask:"email"` 320 | } `mask:"struct"` 321 | }{ 322 | ID: "A123456789", 323 | Name: "John Doe", 324 | Account: struct { 325 | Name string `mask:"name"` 326 | Email string `mask:"email"` 327 | }{ 328 | Name: "John Doe", 329 | Email: "ggw.chang@gmail.com", 330 | }, 331 | }, 332 | }, 333 | want: &struct { 334 | ID string `mask:"id"` 335 | Name string `mask:"name"` 336 | Account struct { 337 | Name string `mask:"name"` 338 | Email string `mask:"email"` 339 | } `mask:"struct"` 340 | }{ 341 | ID: "A12345****", 342 | Name: "J**n D**e", 343 | Account: struct { 344 | Name string `mask:"name"` 345 | Email string `mask:"email"` 346 | }{ 347 | Name: "J**n D**e", 348 | Email: "ggw****@gmail.com", 349 | }, 350 | }, 351 | }, 352 | { 353 | name: "test struct not found", 354 | fields: fields{ 355 | Maskers: map[MaskerType]Masker{ 356 | MaskerTypeNone: &NoneMasker{}, 357 | }, 358 | masker: "*", 359 | }, 360 | args: args{ 361 | s: struct { 362 | ID string `mask:"id"` 363 | }{ 364 | ID: "A123456789", 365 | }, 366 | }, 367 | want: nil, 368 | wantErr: true, 369 | }, 370 | { 371 | name: "test struct with slice string", 372 | fields: fields{ 373 | Maskers: map[MaskerType]Masker{ 374 | MaskerTypeNone: &NoneMasker{}, 375 | MaskerTypeID: &IDMasker{}, 376 | }, 377 | masker: "*", 378 | }, 379 | args: args{ 380 | s: struct { 381 | IDs []string `mask:"id"` 382 | }{ 383 | IDs: []string{"A123456789", "A123456789"}, 384 | }, 385 | }, 386 | want: &struct { 387 | IDs []string `mask:"id"` 388 | }{ 389 | IDs: []string{"A12345****", "A12345****"}, 390 | }, 391 | wantErr: false, 392 | }, 393 | } 394 | for _, tt := range tests { 395 | t.Run(tt.name, func(t *testing.T) { 396 | m := &MaskerMarshaler{ 397 | Maskers: tt.fields.Maskers, 398 | masker: tt.fields.masker, 399 | } 400 | got, err := m.Struct(tt.args.s) 401 | if (err != nil) != tt.wantErr { 402 | t.Errorf("MaskerMarshaler.Struct() error = %v, wantErr %v", err, tt.wantErr) 403 | return 404 | } 405 | if !reflect.DeepEqual(got, tt.want) { 406 | t.Errorf("MaskerMarshaler.Struct() = %v, want %v", got, tt.want) 407 | } 408 | }) 409 | } 410 | } 411 | 412 | func TestNewMaskerMarshaler(t *testing.T) { 413 | tests := []struct { 414 | name string 415 | want *MaskerMarshaler 416 | }{ 417 | { 418 | name: "test new masker marshaler", 419 | want: &MaskerMarshaler{ 420 | Maskers: map[MaskerType]Masker{ 421 | MaskerTypeNone: &NoneMasker{}, 422 | MaskerTypePassword: &PasswordMasker{}, 423 | MaskerTypeName: &NameMasker{}, 424 | MaskerTypeAddress: &AddressMasker{}, 425 | MaskerTypeEmail: &EmailMasker{}, 426 | MaskerTypeMobile: &MobileMasker{}, 427 | MaskerTypeTel: &TelephoneMasker{}, 428 | MaskerTypeID: &IDMasker{}, 429 | MaskerTypeCredit: &CreditMasker{}, 430 | MaskerTypeURL: &URLMasker{}, 431 | }, 432 | masker: "*", 433 | }, 434 | }, 435 | } 436 | for _, tt := range tests { 437 | t.Run(tt.name, func(t *testing.T) { 438 | if got := NewMaskerMarshaler(); !reflect.DeepEqual(got, tt.want) { 439 | t.Errorf("NewMaskerMarshaler() = %v, want %v", got, tt.want) 440 | } 441 | }) 442 | } 443 | } 444 | 445 | func Test_strLoop(t *testing.T) { 446 | type args struct { 447 | str string 448 | length int 449 | } 450 | tests := []struct { 451 | name string 452 | args args 453 | want string 454 | }{ 455 | { 456 | name: "test str loop", 457 | args: args{ 458 | str: "*", 459 | length: 6, 460 | }, 461 | want: "******", 462 | }, 463 | } 464 | for _, tt := range tests { 465 | t.Run(tt.name, func(t *testing.T) { 466 | if got := strLoop(tt.args.str, tt.args.length); got != tt.want { 467 | t.Errorf("strLoop() = %v, want %v", got, tt.want) 468 | } 469 | }) 470 | } 471 | } 472 | 473 | func Test_overlay(t *testing.T) { 474 | type args struct { 475 | str string 476 | overlay string 477 | start int 478 | end int 479 | } 480 | tests := []struct { 481 | name string 482 | args args 483 | wantOverlayed string 484 | }{ 485 | { 486 | name: "test overlay", 487 | args: args{ 488 | str: "A123456789", 489 | overlay: "*", 490 | start: 6, 491 | end: 10, 492 | }, 493 | wantOverlayed: "A12345*", 494 | }, 495 | } 496 | for _, tt := range tests { 497 | t.Run(tt.name, func(t *testing.T) { 498 | if gotOverlayed := overlay(tt.args.str, tt.args.overlay, tt.args.start, tt.args.end); gotOverlayed != tt.wantOverlayed { 499 | t.Errorf("overlay() = %v, want %v", gotOverlayed, tt.wantOverlayed) 500 | } 501 | }) 502 | } 503 | } 504 | -------------------------------------------------------------------------------- /mobile.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | // MobileMasker is a masker for mobile 4 | type MobileMasker struct{} 5 | 6 | // Marshal masks mobile 7 | // It mask 3 digits from the 4'th digit 8 | // Example: 9 | // 10 | // MobileMasker{}.Marshal("*", "0987654321") // returns "0987***321" 11 | func (m *MobileMasker) Marshal(s string, i string) string { 12 | if len(i) == 0 { 13 | return "" 14 | } 15 | return overlay(i, strLoop(s, 3), 4, 7) 16 | } 17 | -------------------------------------------------------------------------------- /mobile_test.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "testing" 4 | 5 | func TestMobileMasker_Marshal(t *testing.T) { 6 | type args struct { 7 | s string 8 | i string 9 | } 10 | tests := []struct { 11 | name string 12 | m *MobileMasker 13 | args args 14 | want string 15 | }{ 16 | { 17 | name: "Empty Input", 18 | args: args{ 19 | s: "*", 20 | i: "", 21 | }, 22 | want: "", 23 | }, 24 | { 25 | name: "Happy Pass", 26 | args: args{ 27 | s: "*", 28 | i: "0978978978", 29 | }, 30 | want: "0978***978", 31 | }, 32 | { 33 | name: "Happy Pass", 34 | args: args{ 35 | s: "*", 36 | i: "0912345678", 37 | }, 38 | want: "0912***678", 39 | }, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | m := &MobileMasker{} 44 | if got := m.Marshal(tt.args.s, tt.args.i); got != tt.want { 45 | t.Errorf("MobileMasker.Marshal() = %v, want %v", got, tt.want) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /name.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "strings" 4 | 5 | type NameMasker struct{} 6 | 7 | // Marshal masks name 8 | // It mask the second letter and the third letter 9 | // Example: 10 | // 11 | // NameMasker{}.Marshal("*", "name") // returns "n**e" 12 | // NameMasker{}.Marshal("*", "ABCD") // returns "A**D" 13 | func (m *NameMasker) Marshal(s string, i string) string { 14 | l := len([]rune(i)) 15 | 16 | if l == 0 { 17 | return "" 18 | } 19 | 20 | // if has space 21 | if strs := strings.Split(i, " "); len(strs) > 1 { 22 | tmp := make([]string, len(strs)) 23 | for idx, str := range strs { 24 | tmp[idx] = m.Marshal(s, str) 25 | } 26 | return strings.Join(tmp, " ") 27 | } 28 | 29 | if l == 2 || l == 3 { 30 | return overlay(i, strLoop(s, 2), 1, 2) 31 | } 32 | 33 | if l > 3 { 34 | return overlay(i, strLoop(s, 2), 1, 3) 35 | } 36 | 37 | return strLoop(s, 2) 38 | } 39 | -------------------------------------------------------------------------------- /name_test.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "testing" 4 | 5 | func TestNameMasker_Marshal(t *testing.T) { 6 | type args struct { 7 | s string 8 | i string 9 | } 10 | tests := []struct { 11 | name string 12 | m *NameMasker 13 | args args 14 | want string 15 | }{ 16 | { 17 | name: "Empty Input", 18 | args: args{ 19 | s: "*", 20 | i: "", 21 | }, 22 | want: "", 23 | }, 24 | { 25 | name: "Chinese Length 1", 26 | args: args{ 27 | s: "*", 28 | i: "王", 29 | }, 30 | want: "**", 31 | }, 32 | { 33 | name: "Chinese Length 2", 34 | args: args{ 35 | s: "*", 36 | i: "王蛋", 37 | }, 38 | want: "王**", 39 | }, 40 | { 41 | name: "Chinese Length 3", 42 | args: args{ 43 | s: "*", 44 | i: "王八蛋", 45 | }, 46 | want: "王**蛋", 47 | }, 48 | { 49 | name: "Chinese Length 4", 50 | args: args{ 51 | s: "*", 52 | i: "王七八蛋", 53 | }, 54 | want: "王**蛋", 55 | }, 56 | { 57 | name: "Chinese Length 5", 58 | args: args{ 59 | s: "*", 60 | i: "王七八九蛋", 61 | }, 62 | want: "王**九蛋", 63 | }, 64 | { 65 | name: "Chinese Length 6", 66 | args: args{ 67 | s: "*", 68 | i: "王七八九十蛋", 69 | }, 70 | want: "王**九十蛋", 71 | }, 72 | { 73 | name: "English Length 4", 74 | args: args{ 75 | s: "*", 76 | i: "Alen", 77 | }, 78 | want: "A**n", 79 | }, 80 | { 81 | name: "English Full Name", 82 | args: args{ 83 | s: "*", 84 | i: "Alen Lin", 85 | }, 86 | want: "A**n L**n", 87 | }, 88 | { 89 | name: "English Full Name", 90 | args: args{ 91 | s: "*", 92 | i: "Jorge Marry", 93 | }, 94 | want: "J**ge M**ry", 95 | }, 96 | } 97 | for _, tt := range tests { 98 | t.Run(tt.name, func(t *testing.T) { 99 | m := &NameMasker{} 100 | if got := m.Marshal(tt.args.s, tt.args.i); got != tt.want { 101 | t.Errorf("NameMasker.Marshal() = %v, want %v", got, tt.want) 102 | } 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /none.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | // NameMasker is a masker for name 4 | type NoneMasker struct{} 5 | 6 | // Marshal masks name 7 | // It returns the same value 8 | // Example: 9 | // 10 | // NoneMasker{}.Marshal("*", "name") // returns "name" 11 | func (m *NoneMasker) Marshal(i string, value string) string { 12 | return value 13 | } 14 | -------------------------------------------------------------------------------- /password.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | // PasswordMasker is a masker for password 4 | type PasswordMasker struct{} 5 | 6 | // Marshal masks password 7 | // It returns 14 asterisks 8 | // Example: 9 | // 10 | // PasswordMasker{}.Marshal("*", "password") // returns "**************" 11 | // PasswordMasker{}.Marshal("&", "password") // returns "&&&&&&&&&&&&&&" 12 | func (m *PasswordMasker) Marshal(s string, i string) string { 13 | return strLoop(s, 14) 14 | } 15 | -------------------------------------------------------------------------------- /password_test.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "testing" 4 | 5 | func TestPasswordMasker_Marshal(t *testing.T) { 6 | type args struct { 7 | s string 8 | i string 9 | } 10 | tests := []struct { 11 | name string 12 | m *PasswordMasker 13 | args args 14 | want string 15 | }{ 16 | { 17 | name: "Empty Input", 18 | args: args{ 19 | s: "*", 20 | i: "", 21 | }, 22 | want: "**************", 23 | }, 24 | { 25 | name: "Happy Pass", 26 | args: args{ 27 | s: "*", 28 | i: "1234567", 29 | }, 30 | want: "**************", 31 | }, 32 | { 33 | name: "Happy Pass", 34 | args: args{ 35 | s: "*", 36 | i: "abcd!@#$%321", 37 | }, 38 | want: "**************", 39 | }, 40 | { 41 | name: "Happy Pass", 42 | args: args{ 43 | s: "@", 44 | i: "abcd!@#$%321", 45 | }, 46 | want: "@@@@@@@@@@@@@@", 47 | }, 48 | } 49 | for _, tt := range tests { 50 | t.Run(tt.name, func(t *testing.T) { 51 | m := &PasswordMasker{} 52 | if got := m.Marshal(tt.args.s, tt.args.i); got != tt.want { 53 | t.Errorf("PasswordMasker.Marshal() = %v, want %v", got, tt.want) 54 | } 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /telephone.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "strings" 4 | 5 | // TelephoneMasker is a masker for telephone 6 | type TelephoneMasker struct{} 7 | 8 | // Marshal masks telephone 9 | // It remove "(", ")", " ", "-" chart, and mask last 4 digits of telephone number, format to "(??)????-????" 10 | // Example: 11 | // 12 | // TelephoneMasker{}.Marshal("*", "0227993078") // returns "(02)2799-****" 13 | func (m *TelephoneMasker) Marshal(s string, i string) string { 14 | l := len([]rune(i)) 15 | if l == 0 { 16 | return "" 17 | } 18 | 19 | i = strings.Replace(i, " ", "", -1) 20 | i = strings.Replace(i, "(", "", -1) 21 | i = strings.Replace(i, ")", "", -1) 22 | i = strings.Replace(i, "-", "", -1) 23 | 24 | l = len([]rune(i)) 25 | 26 | if l != 10 && l != 8 { 27 | return i 28 | } 29 | 30 | ans := "" 31 | 32 | if l == 10 { 33 | ans += "(" 34 | ans += i[:2] 35 | ans += ")" 36 | i = i[2:] 37 | } 38 | 39 | ans += i[:4] 40 | ans += "-" 41 | ans += "****" 42 | 43 | return ans 44 | } 45 | -------------------------------------------------------------------------------- /telephone_test.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "testing" 4 | 5 | func TestTelephoneMasker_Marshal(t *testing.T) { 6 | type args struct { 7 | s string 8 | i string 9 | } 10 | tests := []struct { 11 | name string 12 | m *TelephoneMasker 13 | args args 14 | want string 15 | }{ 16 | { 17 | name: "Empty Input", 18 | args: args{ 19 | s: "*", 20 | i: "", 21 | }, 22 | want: "", 23 | }, 24 | { 25 | name: "With Special Chart", 26 | args: args{ 27 | s: "*", 28 | i: "(02-)27 99-3--078", 29 | }, 30 | want: "(02)2799-****", 31 | }, 32 | { 33 | name: "Happy Pass", 34 | args: args{ 35 | s: "*", 36 | i: "0227993078", 37 | }, 38 | want: "(02)2799-****", 39 | }, 40 | { 41 | name: "Happy Pass", 42 | args: args{ 43 | s: "*", 44 | i: "0788079966", 45 | }, 46 | want: "(07)8807-****", 47 | }, 48 | } 49 | for _, tt := range tests { 50 | t.Run(tt.name, func(t *testing.T) { 51 | m := &TelephoneMasker{} 52 | if got := m.Marshal(tt.args.s, tt.args.i); got != tt.want { 53 | t.Errorf("TelephoneMasker.Marshal() = %v, want %v", got, tt.want) 54 | } 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /url.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "net/url" 4 | 5 | // URLMasker is a masker for URL 6 | type URLMasker struct{} 7 | 8 | // Marshal masks URL 9 | // It mask the password part of the URL if exists 10 | func (m *URLMasker) Marshal(s, i string) string { 11 | u, err := url.Parse(i) 12 | if err != nil { 13 | return i 14 | } 15 | return u.Redacted() 16 | } 17 | -------------------------------------------------------------------------------- /url_test.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import "testing" 4 | 5 | func TestURLMasker_Marshal(t *testing.T) { 6 | type args struct { 7 | s string 8 | i string 9 | } 10 | tests := []struct { 11 | name string 12 | m *URLMasker 13 | args args 14 | want string 15 | }{ 16 | // TODO: Add test cases. 17 | } 18 | for _, tt := range tests { 19 | t.Run(tt.name, func(t *testing.T) { 20 | m := &URLMasker{} 21 | if got := m.Marshal(tt.args.s, tt.args.i); got != tt.want { 22 | t.Errorf("URLMasker.Marshal() = %v, want %v", got, tt.want) 23 | } 24 | }) 25 | } 26 | } 27 | --------------------------------------------------------------------------------