api/pkg/validators/validator.go   A
last analyzed

Size/Duplication

Total Lines 161
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 30
eloc 92
dl 0
loc 161
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
F validators.init 0 116 29
A validators.*validator.ValidateUUID 0 16 1
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