-
Notifications
You must be signed in to change notification settings - Fork 8
/
validate.go
278 lines (248 loc) · 10.2 KB
/
validate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
package validate
import (
"fmt"
"io"
"net/http"
"reflect"
"regexp"
"strings"
"time"
"github.com/google/uuid"
parse "github.com/karrick/tparse/v2"
"github.com/oleiade/reflections"
"github.com/pkg/errors"
)
// Response validates a response interface and error
// if requestError has an error, it is returned
// if resp.Error is defined and not nil, it is returned
// if one of the field namess provided in []checkNullFields are nil, an error is returned
func Response(resp interface{}, requestError error, checkNullFields ...string) error {
// check request error
if requestError != nil {
return requestError
}
if err := ResponseObject(resp); err != nil {
return err
}
return Fields(resp, checkNullFields...)
}
// Field validates that given field in the response object is set (not nil)
func Field(resp interface{}, field string) error {
sl := strings.Split(field, ".")
res := resp
for _, f := range sl {
a, err := reflections.GetField(res, f)
if err != nil {
return err
}
if a == nil || (reflect.ValueOf(a).Kind() == reflect.Ptr && reflect.ValueOf(a).IsNil()) {
return fmt.Errorf("field %s in response is nil", field)
}
res = a
}
return nil
}
// Fields validates that given fields in the response object are set (not nil)
func Fields(resp interface{}, fields ...string) error {
for _, field := range fields {
if err := Field(resp, field); err != nil {
return err
}
}
return nil
}
// ResponseObject validates the response response and checks if it has an Error field
// that's set
func ResponseObject(resp interface{}) error {
if resp == nil {
return errors.New("response interface is nil")
}
// check Error field exists
// if not return err (unless the resp is a non-struct, err will be nil)
if ok, err := reflections.HasField(resp, "Error"); !ok {
return err
}
value, _ := reflections.GetField(resp, "Error")
if v, ok := value.(error); ok {
if v != nil {
return v
}
}
return nil
}
type ResponseInterface interface {
StatusCode() int
}
// StatusEquals returns true if interface.StatusCode() equals a given http code
// if more than one status code is provided, the function will return true if one of them matches
func StatusEquals(a ResponseInterface, statusCode ...int) bool {
if a == nil || (reflect.ValueOf(a).Kind() == reflect.Ptr && reflect.ValueOf(a).IsNil()) {
return false
}
for _, code := range statusCode {
if a.StatusCode() == code {
return true
}
}
return false
}
// UUID validates a given UUID
func UUID(id string) error {
if _, err := uuid.Parse(id); err != nil {
return err
}
return nil
}
// ProjectID validates a given project ID
func ProjectID(projectID string) error {
if err := UUID(projectID); err != nil {
return errors.Wrap(err, "invalid UUID for project")
}
return nil
}
// ProjectName validates a given project name
func ProjectName(name string) error {
exp := `^[a-zA-Z][ a-zA-Z0-9_-]{1,39}$`
r := regexp.MustCompile(exp)
if !r.MatchString(name) {
return fmt.Errorf("invalid project name. valid name is of: %s", exp)
}
return nil
}
// BillingRef validates a given billing reference
func BillingRef(billingRef string) error {
exp := `^[a-zA-Z][a-zA-Z0-9_-]{1,29}$`
r := regexp.MustCompile(exp)
if !r.MatchString(billingRef) {
return fmt.Errorf("invalid billing reference. valid reference is of: %s", exp)
}
return nil
}
// SemVer validates a given version
func SemVer(version string) error {
if version == "" {
return errors.New("version is empty")
}
exp := `^\d+\.\d+(?:\.\d+)?$`
r := regexp.MustCompile(exp)
if !r.MatchString(version) {
return fmt.Errorf("invalid version. valid version is of: %s", exp)
}
return nil
}
// DefaultResponseErrorHandler is the default error handler used to check
// if a giving STACKIT API response returned an error
func DefaultResponseErrorHandler(resp *http.Response) error {
if resp.StatusCode < 400 {
return nil
}
var b []byte
if resp.Body != nil {
b, _ = io.ReadAll(resp.Body)
}
return fmt.Errorf(
"call error:\nHTTP status code: %d\nHTTP status message: %s\nServer response: %s\nURL: %s\nTrace: %s\n",
resp.StatusCode,
http.StatusText(resp.StatusCode),
string(b),
resp.Request.URL.String(),
resp.Request.Header.Get("Traceparent"),
)
}
// ISO8601 Validates that given time is formatted as ISO 8601
func ISO8601(t string) error {
isoFmt := "2006-01-02T15:04:05.999Z"
_, err := time.Parse(isoFmt, t)
if err != nil {
return errors.Wrap(err, "couldn't parse given time as ISO8601")
}
return nil
}
// RFC3339 Validates that given time is formatted as RFC3339
func RFC3339(t string) error {
_, err := time.Parse(time.RFC3339, t)
if err != nil {
return errors.Wrap(err, "couldn't parse given time as RFC3339")
}
return nil
}
// Duration validates that a given string can be parsed as duration
// i.e. 5m, 60s, 1h
func Duration(s string) (time.Duration, error) {
if s == "" {
return 0, errors.New("can't parse empty string as duration")
}
return parse.AbsoluteDuration(time.Now(), s)
}
// ErrorIsOneOf checks checks if a given error message
// has one of the provided sub strings
func ErrorIsOneOf(err error, msgs ...string) bool {
if err == nil {
return false
}
for _, m := range msgs {
if strings.Contains(err.Error(), m) {
return true
}
}
return false
}
// NetworkName validates a given network name
func NetworkName(name string) error {
exp := `^[A-Za-z0-9]+((-|_|\s|\.)[A-Za-z0-9]+)*$`
r := regexp.MustCompile(exp)
if !r.MatchString(name) {
return fmt.Errorf("invalid network name: %s. valid name must patch the expression: %s", name, exp)
}
if len(name) > 63 {
return fmt.Errorf("invalid network name. The length of the name must contains maximum 63 characters")
}
return nil
}
// NameServer validates a given server name
func NameServer(name string) error {
name = strings.Trim(name, "\"")
exp := "((^\\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\\s*$)|(^\\s*((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$))"
r := regexp.MustCompile(exp)
if !r.MatchString(name) {
return fmt.Errorf("invalid server name: %s. valid name must match the expression: %s", name, exp)
}
return nil
}
// PrefixLengthV4 validates a given server name.
func PrefixLengthV4(prefix int64) error {
if prefix < 22 || prefix > 29 {
return fmt.Errorf("invalid prefix length: %d. The length must be between 22 and 29", prefix)
}
return nil
}
// Prefix validates a given prefix
func Prefix(pr string) error {
pr = strings.Trim(pr, "\"")
exp := "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\/(3[0-2]|2[0-9]|1[0-9]|[0-9]))$|^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(\\/((1(1[0-9]|2[0-8]))|([0-9][0-9])|([0-9])))?$"
r := regexp.MustCompile(exp)
if !r.MatchString(pr) {
return fmt.Errorf("invalid prefix: %s. valid prefix shuld match expression: %s", pr, exp)
}
return nil
}
// PublicIP validates a given ID address
func PublicIP(ip string) error {
exp := "((^\\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\\s*$)|(^\\s*((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$))"
r := regexp.MustCompile(exp)
if !r.MatchString(ip) {
return fmt.Errorf("invalid public IP: %s. valid IP must match the expression: %s", ip, exp)
}
return nil
}
func NetworkID(id string) error {
exp := "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
r := regexp.MustCompile(exp)
if !r.MatchString(id) {
return fmt.Errorf("invalid networkID: %s. valid ID must match the expression: %s", id, exp)
}
if len(id) != 36 {
return fmt.Errorf("invalid networkID: %s. Valid ID must be exactly 36 characters", id)
}
return nil
}