validators.init   F
last analyzed

Complexity

Conditions 29

Size

Total Lines 116
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 29
eloc 66
dl 0
loc 116
rs 0
c 0
b 0
f 0
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like validators.init often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package validators
2
3
import (
4
	"fmt"
5
	"net/url"
6
	"regexp"
7
	"strings"
8
9
	"github.com/NdoleStudio/httpsms/pkg/events"
10
11
	"github.com/nyaruka/phonenumbers"
12
	"github.com/thedevsaddam/govalidator"
13
)
14
15
type validator struct{}
16
17
const (
18
	phoneNumberRule                = "phoneNumber"
19
	multiplePhoneNumberRule        = "multiplePhoneNumber"
20
	contactPhoneNumberRule         = "contactPhoneNumber"
21
	multipleContactPhoneNumberRule = "multipleContactPhoneNumber"
22
	multipleInRule                 = "multipleIn"
23
	webhookEventsRule              = "webhookEvents"
24
)
25
26
func init() {
27
	// custom rules to take fixed length word.
28
	// e.g: max_word:5 will throw error if the field contains more than 5 words
29
	govalidator.AddCustomRule(phoneNumberRule, func(field string, rule string, message string, value interface{}) error {
30
		phoneNumber, ok := value.(string)
31
		if !ok {
32
			return fmt.Errorf("The %s field must be a valid E.164 phone number in the international format e.g +18005550100", field)
33
		}
34
35
		_, err := phonenumbers.Parse(phoneNumber, phonenumbers.UNKNOWN_REGION)
36
		if err != nil {
37
			return fmt.Errorf("The %s field must be a valid E.164 phone number in the international format e.g +18005550100", field)
38
		}
39
40
		return nil
41
	})
42
43
	govalidator.AddCustomRule(multiplePhoneNumberRule, func(field string, rule string, message string, value interface{}) error {
44
		phoneNumbers, ok := value.([]string)
45
		if !ok {
46
			return fmt.Errorf("The %s field must be an array of valid phone numbers", field)
47
		}
48
49
		for index, number := range phoneNumbers {
50
			_, err := phonenumbers.Parse(number, phonenumbers.UNKNOWN_REGION)
51
			if err != nil {
52
				return fmt.Errorf("The %s field in index [%d] must be a valid E.164 phone number in the international format e.g +18005550100", field, index)
53
			}
54
		}
55
56
		return nil
57
	})
58
59
	// custom rules to take fixed length word.
60
	// e.g: max_word:5 will throw error if the field contains more than 5 words
61
	govalidator.AddCustomRule(contactPhoneNumberRule, func(field string, rule string, message string, value interface{}) error {
62
		phoneNumber, ok := value.(string)
63
		if !ok {
64
			return fmt.Errorf("The %s field must contain only digits and must be less than 14 characters", field)
65
		}
66
67
		if match, err := regexp.MatchString("^\\+?[0-9]\\d{1,14}$", phoneNumber); err != nil || !match {
68
			return fmt.Errorf("The %s field must contain only digits and must be less than 14 characters", field)
69
		}
70
71
		return nil
72
	})
73
74
	govalidator.AddCustomRule(multipleContactPhoneNumberRule, func(field string, rule string, message string, value interface{}) error {
75
		phoneNumbers, ok := value.([]string)
76
		if !ok {
77
			return fmt.Errorf("The %s field must be an array of valid phone numbers", field)
78
		}
79
80
		for index, number := range phoneNumbers {
81
			if match, err := regexp.MatchString("^\\+?[0-9]\\d{1,14}$", number); err != nil || !match {
82
				return fmt.Errorf("The %s field in index [%d] must contain only digits and must be less than 14 characters", field, index)
83
			}
84
		}
85
86
		return nil
87
	})
88
89
	govalidator.AddCustomRule(multipleInRule, func(field string, rule string, message string, value interface{}) error {
90
		values, ok := value.([]string)
91
		if !ok {
92
			return fmt.Errorf("the %s field must be a string array", field)
93
		}
94
95
		allowlist := strings.Split(strings.TrimPrefix(rule, multipleInRule+":"), ",")
96
		contains := func(str string) bool {
97
			for _, a := range allowlist {
98
				if a == str {
99
					return true
100
				}
101
			}
102
			return false
103
		}
104
105
		for index, item := range values {
106
			if !contains(item) {
107
				return fmt.Errorf("the %s field in contains an invalid value [%s] at index [%d] ", field, item, index)
108
			}
109
		}
110
111
		return nil
112
	})
113
114
	govalidator.AddCustomRule(webhookEventsRule, func(field string, rule string, message string, value interface{}) error {
115
		input, ok := value.([]string)
116
		if !ok {
117
			return fmt.Errorf("The %s field must be a string array", field)
118
		}
119
120
		if len(input) == 0 {
121
			return fmt.Errorf("The %s field is an empty array", field)
122
		}
123
124
		validEvents := map[string]bool{
125
			events.EventTypeMessagePhoneReceived:  true,
126
			events.EventTypeMessagePhoneSent:      true,
127
			events.EventTypeMessagePhoneDelivered: true,
128
			events.EventTypeMessageSendFailed:     true,
129
			events.EventTypeMessageSendExpired:    true,
130
			events.EventTypePhoneHeartbeatOnline:  true,
131
			events.EventTypePhoneHeartbeatOffline: true,
132
			events.MessageCallMissed:              true,
133
		}
134
135
		for _, event := range input {
136
			if _, ok := validEvents[event]; !ok {
137
				return fmt.Errorf("The %s field has an invalid event with name [%s]", field, event)
138
			}
139
		}
140
141
		return nil
142
	})
143
}
144
145
// ValidateUUID that the payload is a UUID
146
func (validator *validator) ValidateUUID(ID string, name string) url.Values {
147
	request := map[string]string{
148
		name: ID,
149
	}
150
151
	v := govalidator.New(govalidator.Options{
152
		Data: &request,
153
		Rules: govalidator.MapData{
154
			name: []string{
155
				"required",
156
				"uuid",
157
			},
158
		},
159
	})
160
161
	return v.ValidateStruct()
162
}
163