@umituz/react-native-validation 1.4.4 → 1.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/src/index.ts +21 -7
- package/src/infrastructure/utils/sanitization.ts +61 -54
- package/src/infrastructure/utils/validators.ts +264 -23
- package/README.md +0 -245
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-validation",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.6",
|
|
4
4
|
"description": "Comprehensive validation and sanitization utilities for React Native forms",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
"react-native": ">=0.74.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
+
"@types/node": "^25.0.3",
|
|
32
33
|
"@types/react": "~19.1.10",
|
|
34
|
+
"@types/react-native": "^0.72.8",
|
|
33
35
|
"react": "19.1.0",
|
|
34
36
|
"react-native": "0.81.5",
|
|
35
37
|
"typescript": "~5.9.2"
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
// Domain Layer - Entities
|
|
11
|
-
export type { ValidationResult } from
|
|
11
|
+
export type { ValidationResult } from "./domain/entities/ValidationResult";
|
|
12
12
|
|
|
13
13
|
// Infrastructure Layer - Validators
|
|
14
14
|
export {
|
|
@@ -24,7 +24,19 @@ export {
|
|
|
24
24
|
validateDateOfBirth,
|
|
25
25
|
validateAge,
|
|
26
26
|
batchValidate,
|
|
27
|
-
} from
|
|
27
|
+
} from "./infrastructure/utils/validators";
|
|
28
|
+
|
|
29
|
+
// Dream-specific validators
|
|
30
|
+
export {
|
|
31
|
+
validateDreamTitle,
|
|
32
|
+
validateDreamDescription,
|
|
33
|
+
validateDreamMood,
|
|
34
|
+
validateDreamCategory,
|
|
35
|
+
validateDreamDate,
|
|
36
|
+
validateDreamTags,
|
|
37
|
+
validateDreamPrivacy,
|
|
38
|
+
validateDreamForm,
|
|
39
|
+
} from "./infrastructure/utils/validators";
|
|
28
40
|
|
|
29
41
|
// Infrastructure Layer - Sanitization
|
|
30
42
|
export {
|
|
@@ -36,7 +48,7 @@ export {
|
|
|
36
48
|
sanitizeText,
|
|
37
49
|
containsDangerousChars,
|
|
38
50
|
isWithinLengthLimit,
|
|
39
|
-
} from
|
|
51
|
+
} from "./infrastructure/utils/sanitization";
|
|
40
52
|
|
|
41
53
|
// Infrastructure Layer - MIME Type Validation
|
|
42
54
|
export {
|
|
@@ -44,7 +56,7 @@ export {
|
|
|
44
56
|
SUPPORTED_IMAGE_MIME_TYPES,
|
|
45
57
|
EXTENSION_TO_MIME_TYPE,
|
|
46
58
|
MIME_TYPE_TO_EXTENSION,
|
|
47
|
-
} from
|
|
59
|
+
} from "./infrastructure/utils/mime-types.constants";
|
|
48
60
|
|
|
49
61
|
export {
|
|
50
62
|
getFileExtension,
|
|
@@ -53,8 +65,10 @@ export {
|
|
|
53
65
|
validateImageMimeType,
|
|
54
66
|
validateImageExtension,
|
|
55
67
|
validateImageDataUrl,
|
|
56
|
-
} from
|
|
68
|
+
} from "./infrastructure/utils/mime-type-validator";
|
|
57
69
|
|
|
58
70
|
// Infrastructure Layer - Image Validation
|
|
59
|
-
export {
|
|
60
|
-
|
|
71
|
+
export {
|
|
72
|
+
validateImageUri,
|
|
73
|
+
getImageMimeType,
|
|
74
|
+
} from "./infrastructure/utils/image-validator";
|
|
@@ -1,92 +1,99 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sanitization Utilities
|
|
3
|
-
*
|
|
3
|
+
* Secure input cleaning for user data
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Security constants for input validation
|
|
8
|
+
*/
|
|
6
9
|
export const SECURITY_LIMITS = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
};
|
|
10
|
+
EMAIL_MAX_LENGTH: 254, // RFC 5321
|
|
11
|
+
PASSWORD_MIN_LENGTH: 6,
|
|
12
|
+
PASSWORD_MAX_LENGTH: 128,
|
|
13
|
+
NAME_MAX_LENGTH: 100,
|
|
14
|
+
GENERAL_TEXT_MAX_LENGTH: 500,
|
|
15
|
+
} as const;
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
|
-
*
|
|
16
|
-
* Removes leading/trailing whitespace and replaces multiple spaces with single space
|
|
18
|
+
* Trim and normalize whitespace
|
|
17
19
|
*/
|
|
18
|
-
export const sanitizeWhitespace = (
|
|
19
|
-
|
|
20
|
-
return value.trim().replace(/\s+/g, ' ');
|
|
20
|
+
export const sanitizeWhitespace = (input: string): string => {
|
|
21
|
+
return input.trim().replace(/\s+/g, ' ');
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* Sanitize email
|
|
25
|
-
*
|
|
25
|
+
* Sanitize email address
|
|
26
|
+
* - Trim whitespace
|
|
27
|
+
* - Convert to lowercase
|
|
28
|
+
* - Limit length
|
|
26
29
|
*/
|
|
27
30
|
export const sanitizeEmail = (email: string): string => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
let sanitized = email.trim().toLowerCase();
|
|
31
|
-
// Remove potential dangerous characters that shouldn't be in an email
|
|
32
|
-
// < > " ' `
|
|
33
|
-
sanitized = sanitized.replace(/[<>"'`]/g, '');
|
|
34
|
-
return sanitized;
|
|
31
|
+
const trimmed = email.trim().toLowerCase();
|
|
32
|
+
return trimmed.substring(0, SECURITY_LIMITS.EMAIL_MAX_LENGTH);
|
|
35
33
|
};
|
|
36
34
|
|
|
37
35
|
/**
|
|
38
36
|
* Sanitize password
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* but for this utility we will assume standard string behavior.
|
|
42
|
-
* actually it's safer NOT to modify password input other than ensuring it is a string.
|
|
37
|
+
* - Only trim (preserve case and special chars)
|
|
38
|
+
* - Limit length to prevent DoS
|
|
43
39
|
*/
|
|
44
40
|
export const sanitizePassword = (password: string): string => {
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
// Don't trim password to preserve intentional spaces
|
|
42
|
+
// Only limit length
|
|
43
|
+
return password.substring(0, SECURITY_LIMITS.PASSWORD_MAX_LENGTH);
|
|
47
44
|
};
|
|
48
45
|
|
|
49
46
|
/**
|
|
50
|
-
* Sanitize name
|
|
51
|
-
*
|
|
47
|
+
* Sanitize display name
|
|
48
|
+
* - Trim whitespace
|
|
49
|
+
* - Normalize multiple spaces
|
|
50
|
+
* - Remove potential XSS characters
|
|
51
|
+
* - Limit length
|
|
52
52
|
*/
|
|
53
53
|
export const sanitizeName = (name: string): string => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
const trimmed = sanitizeWhitespace(name);
|
|
55
|
+
// Remove HTML tags and script content
|
|
56
|
+
const noTags = trimmed.replace(/<[^>]*>/g, '');
|
|
57
|
+
return noTags.substring(0, SECURITY_LIMITS.NAME_MAX_LENGTH);
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
|
-
* Sanitize
|
|
61
|
-
*
|
|
62
|
-
*
|
|
61
|
+
* Sanitize general text input
|
|
62
|
+
* - Trim whitespace
|
|
63
|
+
* - Remove HTML/script tags
|
|
64
|
+
* - Limit length
|
|
63
65
|
*/
|
|
64
66
|
export const sanitizeText = (text: string): string => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
.replace(/&/g, '&')
|
|
69
|
-
.replace(/</g, '<')
|
|
70
|
-
.replace(/>/g, '>')
|
|
71
|
-
.replace(/"/g, '"')
|
|
72
|
-
.replace(/'/g, ''');
|
|
67
|
+
const trimmed = sanitizeWhitespace(text);
|
|
68
|
+
const noTags = trimmed.replace(/<[^>]*>/g, '');
|
|
69
|
+
return noTags.substring(0, SECURITY_LIMITS.GENERAL_TEXT_MAX_LENGTH);
|
|
73
70
|
};
|
|
74
71
|
|
|
75
72
|
/**
|
|
76
|
-
* Check
|
|
77
|
-
* Returns true if string contains characters often used in attacks (<, >, etc)
|
|
73
|
+
* Check if string contains potentially dangerous characters
|
|
78
74
|
*/
|
|
79
|
-
export const containsDangerousChars = (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
export const containsDangerousChars = (input: string): boolean => {
|
|
76
|
+
// Check for common XSS patterns
|
|
77
|
+
const dangerousPatterns = [
|
|
78
|
+
/<script/i,
|
|
79
|
+
/javascript:/i,
|
|
80
|
+
/on\w+\s*=/i, // onclick, onload, etc.
|
|
81
|
+
/<iframe/i,
|
|
82
|
+
/eval\(/i,
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
return dangerousPatterns.some(pattern => pattern.test(input));
|
|
84
86
|
};
|
|
85
87
|
|
|
86
88
|
/**
|
|
87
|
-
*
|
|
89
|
+
* Validate string length is within bounds
|
|
88
90
|
*/
|
|
89
|
-
export const isWithinLengthLimit = (
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
export const isWithinLengthLimit = (
|
|
92
|
+
input: string,
|
|
93
|
+
maxLength: number,
|
|
94
|
+
minLength = 0
|
|
95
|
+
): boolean => {
|
|
96
|
+
const length = input.trim().length;
|
|
97
|
+
return length >= minLength && length <= maxLength;
|
|
92
98
|
};
|
|
99
|
+
|
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
* Comprehensive validation functions for React Native forms
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { ValidationResult } from
|
|
6
|
+
import type { ValidationResult } from "../../domain/entities/ValidationResult";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Validate email format
|
|
10
10
|
*/
|
|
11
11
|
export const validateEmail = (email: string): ValidationResult => {
|
|
12
|
-
if (!email || email.trim() ===
|
|
13
|
-
return { isValid: false, error:
|
|
12
|
+
if (!email || email.trim() === "") {
|
|
13
|
+
return { isValid: false, error: "Email is required" };
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
17
17
|
if (!emailRegex.test(email)) {
|
|
18
|
-
return { isValid: false, error:
|
|
18
|
+
return { isValid: false, error: "Please enter a valid email address" };
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
return { isValid: true };
|
|
@@ -26,9 +26,9 @@ export const validateEmail = (email: string): ValidationResult => {
|
|
|
26
26
|
*/
|
|
27
27
|
export const validateRequired = (
|
|
28
28
|
value: string,
|
|
29
|
-
fieldName: string =
|
|
29
|
+
fieldName: string = "This field",
|
|
30
30
|
): ValidationResult => {
|
|
31
|
-
if (!value || value.trim() ===
|
|
31
|
+
if (!value || value.trim() === "") {
|
|
32
32
|
return { isValid: false, error: `${fieldName} is required` };
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -40,10 +40,10 @@ export const validateRequired = (
|
|
|
40
40
|
*/
|
|
41
41
|
export const validateName = (
|
|
42
42
|
name: string,
|
|
43
|
-
fieldName: string =
|
|
44
|
-
minLength: number = 2
|
|
43
|
+
fieldName: string = "Name",
|
|
44
|
+
minLength: number = 2,
|
|
45
45
|
): ValidationResult => {
|
|
46
|
-
if (!name || name.trim() ===
|
|
46
|
+
if (!name || name.trim() === "") {
|
|
47
47
|
return { isValid: false, error: `${fieldName} is required` };
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -61,13 +61,13 @@ export const validateName = (
|
|
|
61
61
|
* Validate phone number (E.164 format)
|
|
62
62
|
*/
|
|
63
63
|
export const validatePhone = (phone: string): ValidationResult => {
|
|
64
|
-
if (!phone || phone.trim() ===
|
|
65
|
-
return { isValid: false, error:
|
|
64
|
+
if (!phone || phone.trim() === "") {
|
|
65
|
+
return { isValid: false, error: "Phone number is required" };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const phoneRegex = /^\+[1-9]\d{1,14}$/;
|
|
69
69
|
if (!phoneRegex.test(phone)) {
|
|
70
|
-
return { isValid: false, error:
|
|
70
|
+
return { isValid: false, error: "Please enter a valid phone number" };
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
return { isValid: true };
|
|
@@ -80,7 +80,7 @@ export const validateNumberRange = (
|
|
|
80
80
|
value: number,
|
|
81
81
|
min: number,
|
|
82
82
|
max: number,
|
|
83
|
-
fieldName: string =
|
|
83
|
+
fieldName: string = "Value",
|
|
84
84
|
): ValidationResult => {
|
|
85
85
|
if (isNaN(value)) {
|
|
86
86
|
return { isValid: false, error: `${fieldName} must be a number` };
|
|
@@ -101,7 +101,7 @@ export const validateNumberRange = (
|
|
|
101
101
|
*/
|
|
102
102
|
export const validatePositiveNumber = (
|
|
103
103
|
value: number,
|
|
104
|
-
fieldName: string =
|
|
104
|
+
fieldName: string = "Value",
|
|
105
105
|
): ValidationResult => {
|
|
106
106
|
if (isNaN(value)) {
|
|
107
107
|
return { isValid: false, error: `${fieldName} must be a number` };
|
|
@@ -120,7 +120,7 @@ export const validatePositiveNumber = (
|
|
|
120
120
|
export const validateMinLength = (
|
|
121
121
|
value: string,
|
|
122
122
|
minLength: number,
|
|
123
|
-
fieldName: string =
|
|
123
|
+
fieldName: string = "Field",
|
|
124
124
|
): ValidationResult => {
|
|
125
125
|
if (!value || value.trim().length < minLength) {
|
|
126
126
|
return {
|
|
@@ -138,7 +138,7 @@ export const validateMinLength = (
|
|
|
138
138
|
export const validateMaxLength = (
|
|
139
139
|
value: string,
|
|
140
140
|
maxLength: number,
|
|
141
|
-
fieldName: string =
|
|
141
|
+
fieldName: string = "Field",
|
|
142
142
|
): ValidationResult => {
|
|
143
143
|
if (value && value.trim().length > maxLength) {
|
|
144
144
|
return {
|
|
@@ -156,8 +156,8 @@ export const validateMaxLength = (
|
|
|
156
156
|
export const validatePattern = (
|
|
157
157
|
value: string,
|
|
158
158
|
pattern: RegExp,
|
|
159
|
-
fieldName: string =
|
|
160
|
-
errorMessage?: string
|
|
159
|
+
fieldName: string = "Field",
|
|
160
|
+
errorMessage?: string,
|
|
161
161
|
): ValidationResult => {
|
|
162
162
|
if (!value) {
|
|
163
163
|
return { isValid: false, error: `${fieldName} is required` };
|
|
@@ -178,18 +178,18 @@ export const validatePattern = (
|
|
|
178
178
|
*/
|
|
179
179
|
export const validateDateOfBirth = (date: Date): ValidationResult => {
|
|
180
180
|
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
|
181
|
-
return { isValid: false, error:
|
|
181
|
+
return { isValid: false, error: "Please enter a valid date" };
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
const today = new Date();
|
|
185
185
|
const age = today.getFullYear() - date.getFullYear();
|
|
186
186
|
|
|
187
187
|
if (age < 13) {
|
|
188
|
-
return { isValid: false, error:
|
|
188
|
+
return { isValid: false, error: "You must be at least 13 years old" };
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
if (age > 120) {
|
|
192
|
-
return { isValid: false, error:
|
|
192
|
+
return { isValid: false, error: "Please enter a valid date of birth" };
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
return { isValid: true };
|
|
@@ -199,7 +199,7 @@ export const validateDateOfBirth = (date: Date): ValidationResult => {
|
|
|
199
199
|
* Validate age
|
|
200
200
|
*/
|
|
201
201
|
export const validateAge = (age: number): ValidationResult => {
|
|
202
|
-
return validateNumberRange(age, 13, 120,
|
|
202
|
+
return validateNumberRange(age, 13, 120, "Age");
|
|
203
203
|
};
|
|
204
204
|
|
|
205
205
|
/**
|
|
@@ -207,7 +207,7 @@ export const validateAge = (age: number): ValidationResult => {
|
|
|
207
207
|
* Validates multiple fields and returns all errors
|
|
208
208
|
*/
|
|
209
209
|
export const batchValidate = (
|
|
210
|
-
validations: Array<{ field: string; validator: () => ValidationResult }
|
|
210
|
+
validations: Array<{ field: string; validator: () => ValidationResult }>,
|
|
211
211
|
): { isValid: boolean; errors: Record<string, string> } => {
|
|
212
212
|
const errors: Record<string, string> = {};
|
|
213
213
|
let isValid = true;
|
|
@@ -223,3 +223,244 @@ export const batchValidate = (
|
|
|
223
223
|
return { isValid, errors };
|
|
224
224
|
};
|
|
225
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Dream-specific validation functions
|
|
228
|
+
*/
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Validate dream title
|
|
232
|
+
*/
|
|
233
|
+
export const validateDreamTitle = (
|
|
234
|
+
title: string,
|
|
235
|
+
minLength: number = 3,
|
|
236
|
+
maxLength: number = 100,
|
|
237
|
+
): ValidationResult => {
|
|
238
|
+
if (!title || title.trim() === "") {
|
|
239
|
+
return { isValid: false, error: "Dream title is required" };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (title.trim().length < minLength) {
|
|
243
|
+
return {
|
|
244
|
+
isValid: false,
|
|
245
|
+
error: `Dream title must be at least ${minLength} characters`,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (title.trim().length > maxLength) {
|
|
250
|
+
return {
|
|
251
|
+
isValid: false,
|
|
252
|
+
error: `Dream title must be at most ${maxLength} characters`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { isValid: true };
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Validate dream description
|
|
261
|
+
*/
|
|
262
|
+
export const validateDreamDescription = (
|
|
263
|
+
description: string,
|
|
264
|
+
minLength: number = 10,
|
|
265
|
+
maxLength: number = 2000,
|
|
266
|
+
): ValidationResult => {
|
|
267
|
+
if (!description || description.trim() === "") {
|
|
268
|
+
return { isValid: false, error: "Dream description is required" };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (description.trim().length < minLength) {
|
|
272
|
+
return {
|
|
273
|
+
isValid: false,
|
|
274
|
+
error: `Dream description must be at least ${minLength} characters`,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (description.trim().length > maxLength) {
|
|
279
|
+
return {
|
|
280
|
+
isValid: false,
|
|
281
|
+
error: `Dream description must be at most ${maxLength} characters`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return { isValid: true };
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Validate dream mood
|
|
290
|
+
*/
|
|
291
|
+
export const validateDreamMood = (mood: string): ValidationResult => {
|
|
292
|
+
if (!mood || mood.trim() === "") {
|
|
293
|
+
return { isValid: false, error: "Dream mood is required" };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const validMoods = [
|
|
297
|
+
"happy",
|
|
298
|
+
"sad",
|
|
299
|
+
"scared",
|
|
300
|
+
"excited",
|
|
301
|
+
"peaceful",
|
|
302
|
+
"anxious",
|
|
303
|
+
"confused",
|
|
304
|
+
"angry",
|
|
305
|
+
"surprised",
|
|
306
|
+
"neutral",
|
|
307
|
+
];
|
|
308
|
+
if (!validMoods.includes(mood.toLowerCase())) {
|
|
309
|
+
return {
|
|
310
|
+
isValid: false,
|
|
311
|
+
error: "Please select a valid mood",
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return { isValid: true };
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Validate dream category
|
|
320
|
+
*/
|
|
321
|
+
export const validateDreamCategory = (category: string): ValidationResult => {
|
|
322
|
+
if (!category || category.trim() === "") {
|
|
323
|
+
return { isValid: false, error: "Dream category is required" };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const validCategories = [
|
|
327
|
+
"lucid",
|
|
328
|
+
"nightmare",
|
|
329
|
+
"recurring",
|
|
330
|
+
"prophetic",
|
|
331
|
+
"healing",
|
|
332
|
+
"creative",
|
|
333
|
+
"adventure",
|
|
334
|
+
"relationship",
|
|
335
|
+
"work",
|
|
336
|
+
"childhood",
|
|
337
|
+
"flying",
|
|
338
|
+
"falling",
|
|
339
|
+
"chase",
|
|
340
|
+
"exam",
|
|
341
|
+
"tooth",
|
|
342
|
+
"other",
|
|
343
|
+
];
|
|
344
|
+
if (!validCategories.includes(category.toLowerCase())) {
|
|
345
|
+
return {
|
|
346
|
+
isValid: false,
|
|
347
|
+
error: "Please select a valid category",
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return { isValid: true };
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Validate dream date
|
|
356
|
+
*/
|
|
357
|
+
export const validateDreamDate = (date: Date): ValidationResult => {
|
|
358
|
+
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
|
359
|
+
return { isValid: false, error: "Please enter a valid dream date" };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const today = new Date();
|
|
363
|
+
if (date > today) {
|
|
364
|
+
return { isValid: false, error: "Dream date cannot be in the future" };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Don't allow dreams older than 1 year
|
|
368
|
+
const oneYearAgo = new Date();
|
|
369
|
+
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
|
370
|
+
if (date < oneYearAgo) {
|
|
371
|
+
return {
|
|
372
|
+
isValid: false,
|
|
373
|
+
error: "Dream date cannot be more than 1 year old",
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return { isValid: true };
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Validate dream tags
|
|
382
|
+
*/
|
|
383
|
+
export const validateDreamTags = (tags: string[]): ValidationResult => {
|
|
384
|
+
if (!Array.isArray(tags)) {
|
|
385
|
+
return { isValid: false, error: "Dream tags must be an array" };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (tags.length > 10) {
|
|
389
|
+
return { isValid: false, error: "Maximum 10 tags allowed" };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
for (const tag of tags) {
|
|
393
|
+
if (typeof tag !== "string" || tag.trim().length === 0) {
|
|
394
|
+
return { isValid: false, error: "All tags must be non-empty strings" };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (tag.trim().length > 20) {
|
|
398
|
+
return {
|
|
399
|
+
isValid: false,
|
|
400
|
+
error: "Each tag must be at most 20 characters",
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return { isValid: true };
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Validate dream privacy setting
|
|
410
|
+
*/
|
|
411
|
+
export const validateDreamPrivacy = (privacy: string): ValidationResult => {
|
|
412
|
+
if (!privacy || privacy.trim() === "") {
|
|
413
|
+
return { isValid: false, error: "Privacy setting is required" };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const validPrivacyOptions = ["private", "friends", "public"];
|
|
417
|
+
if (!validPrivacyOptions.includes(privacy.toLowerCase())) {
|
|
418
|
+
return {
|
|
419
|
+
isValid: false,
|
|
420
|
+
error: "Please select a valid privacy setting",
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return { isValid: true };
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Complete dream form validation
|
|
429
|
+
* Validates all dream fields at once
|
|
430
|
+
*/
|
|
431
|
+
export const validateDreamForm = (formData: {
|
|
432
|
+
title?: string;
|
|
433
|
+
description?: string;
|
|
434
|
+
mood?: string;
|
|
435
|
+
category?: string;
|
|
436
|
+
date?: Date;
|
|
437
|
+
tags?: string[];
|
|
438
|
+
privacy?: string;
|
|
439
|
+
}): { isValid: boolean; errors: Record<string, string> } => {
|
|
440
|
+
const validations = [
|
|
441
|
+
{
|
|
442
|
+
field: "title",
|
|
443
|
+
validator: () => validateDreamTitle(formData.title || ""),
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
field: "description",
|
|
447
|
+
validator: () => validateDreamDescription(formData.description || ""),
|
|
448
|
+
},
|
|
449
|
+
{ field: "mood", validator: () => validateDreamMood(formData.mood || "") },
|
|
450
|
+
{
|
|
451
|
+
field: "category",
|
|
452
|
+
validator: () => validateDreamCategory(formData.category || ""),
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
field: "date",
|
|
456
|
+
validator: () => validateDreamDate(formData.date || new Date()),
|
|
457
|
+
},
|
|
458
|
+
{ field: "tags", validator: () => validateDreamTags(formData.tags || []) },
|
|
459
|
+
{
|
|
460
|
+
field: "privacy",
|
|
461
|
+
validator: () => validateDreamPrivacy(formData.privacy || ""),
|
|
462
|
+
},
|
|
463
|
+
];
|
|
464
|
+
|
|
465
|
+
return batchValidate(validations);
|
|
466
|
+
};
|
package/README.md
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
# @umituz/react-native-validation
|
|
2
|
-
|
|
3
|
-
Comprehensive validation and sanitization utilities for React Native forms. Provides reusable validation and sanitization functions for common input types.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @umituz/react-native-validation
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Peer Dependencies
|
|
12
|
-
|
|
13
|
-
- `react` >= 18.2.0
|
|
14
|
-
- `react-native` >= 0.74.0
|
|
15
|
-
|
|
16
|
-
## Features
|
|
17
|
-
|
|
18
|
-
### Validation
|
|
19
|
-
- ✅ Email validation
|
|
20
|
-
- ✅ Password validation (with strength requirements)
|
|
21
|
-
- ✅ Phone number validation (E.164 format)
|
|
22
|
-
- ✅ Required field validation
|
|
23
|
-
- ✅ Name validation
|
|
24
|
-
- ✅ Number range validation
|
|
25
|
-
- ✅ Min/Max length validation
|
|
26
|
-
- ✅ Pattern (regex) validation
|
|
27
|
-
- ✅ Date of birth validation
|
|
28
|
-
- ✅ Age validation
|
|
29
|
-
- ✅ Batch validation
|
|
30
|
-
|
|
31
|
-
### Sanitization
|
|
32
|
-
- ✅ Email sanitization
|
|
33
|
-
- ✅ Password sanitization
|
|
34
|
-
- ✅ Name sanitization
|
|
35
|
-
- ✅ Text sanitization
|
|
36
|
-
- ✅ XSS protection
|
|
37
|
-
- ✅ Length limit validation
|
|
38
|
-
- ✅ Whitespace normalization
|
|
39
|
-
|
|
40
|
-
### General
|
|
41
|
-
- ✅ TypeScript type safety
|
|
42
|
-
- ✅ Image file validation (prevents zip, exe, etc.)
|
|
43
|
-
- ✅ MIME type validation
|
|
44
|
-
|
|
45
|
-
## Usage
|
|
46
|
-
|
|
47
|
-
### Basic Example
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
import { validateEmail, validatePassword, validateRequired } from '@umituz/react-native-validation';
|
|
51
|
-
|
|
52
|
-
// Validate email
|
|
53
|
-
const emailResult = validateEmail('user@example.com');
|
|
54
|
-
if (!emailResult.isValid) {
|
|
55
|
-
console.error(emailResult.error);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Validate password
|
|
59
|
-
const passwordResult = validatePassword('MySecure123', {
|
|
60
|
-
minLength: 8,
|
|
61
|
-
requireUppercase: true,
|
|
62
|
-
requireLowercase: true,
|
|
63
|
-
requireNumber: true,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Validate required field
|
|
67
|
-
const requiredResult = validateRequired('value', 'Field Name');
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Form Validation Example
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
import {
|
|
74
|
-
validateEmail,
|
|
75
|
-
validatePassword,
|
|
76
|
-
validatePasswordConfirmation,
|
|
77
|
-
validateRequired,
|
|
78
|
-
batchValidate,
|
|
79
|
-
} from '@umituz/react-native-validation';
|
|
80
|
-
|
|
81
|
-
const validateForm = (email: string, password: string, confirmPassword: string) => {
|
|
82
|
-
return batchValidate([
|
|
83
|
-
{ field: 'email', validator: () => validateEmail(email) },
|
|
84
|
-
{ field: 'password', validator: () => validatePassword(password) },
|
|
85
|
-
{
|
|
86
|
-
field: 'confirmPassword',
|
|
87
|
-
validator: () => validatePasswordConfirmation(password, confirmPassword),
|
|
88
|
-
},
|
|
89
|
-
]);
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const result = validateForm('user@example.com', 'MySecure123', 'MySecure123');
|
|
93
|
-
if (!result.isValid) {
|
|
94
|
-
console.log('Validation errors:', result.errors);
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Custom Validation
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
import { validatePattern, validateNumberRange } from '@umituz/react-native-validation';
|
|
102
|
-
|
|
103
|
-
// Validate with custom pattern
|
|
104
|
-
const patternResult = validatePattern(
|
|
105
|
-
'ABC123',
|
|
106
|
-
/^[A-Z]{3}\d{3}$/,
|
|
107
|
-
'Code',
|
|
108
|
-
'Code must be 3 uppercase letters followed by 3 numbers'
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
// Validate number range
|
|
112
|
-
const rangeResult = validateNumberRange(50, 0, 100, 'Percentage');
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Sanitization Example
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
import {
|
|
119
|
-
sanitizeEmail,
|
|
120
|
-
sanitizeName,
|
|
121
|
-
sanitizeText,
|
|
122
|
-
containsDangerousChars,
|
|
123
|
-
SECURITY_LIMITS,
|
|
124
|
-
} from '@umituz/react-native-validation';
|
|
125
|
-
|
|
126
|
-
// Sanitize email
|
|
127
|
-
const cleanEmail = sanitizeEmail(' User@Example.COM ');
|
|
128
|
-
// Result: 'user@example.com'
|
|
129
|
-
|
|
130
|
-
// Sanitize name
|
|
131
|
-
const cleanName = sanitizeName(' John Doe ');
|
|
132
|
-
// Result: 'John Doe'
|
|
133
|
-
|
|
134
|
-
// Sanitize text (removes HTML tags)
|
|
135
|
-
const cleanText = sanitizeText('<script>alert("xss")</script>Hello');
|
|
136
|
-
// Result: 'Hello'
|
|
137
|
-
|
|
138
|
-
// Check for dangerous characters
|
|
139
|
-
const isDangerous = containsDangerousChars('<script>alert("xss")</script>');
|
|
140
|
-
// Result: true
|
|
141
|
-
|
|
142
|
-
// Check length limits
|
|
143
|
-
const isValidLength = isWithinLengthLimit('text', SECURITY_LIMITS.NAME_MAX_LENGTH);
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Image Validation Example
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
import {
|
|
150
|
-
validateImageUri,
|
|
151
|
-
getImageMimeType,
|
|
152
|
-
validateImageExtension,
|
|
153
|
-
IMAGE_MIME_TYPES,
|
|
154
|
-
} from '@umituz/react-native-validation';
|
|
155
|
-
|
|
156
|
-
// Validate image URI (supports file://, http://, https://, and data: URLs)
|
|
157
|
-
const result = validateImageUri('file:///path/to/image.jpg');
|
|
158
|
-
if (!result.isValid) {
|
|
159
|
-
console.error(result.error);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Get MIME type from URI
|
|
163
|
-
const mimeType = getImageMimeType('file:///path/to/image.png');
|
|
164
|
-
// Result: 'image/png'
|
|
165
|
-
|
|
166
|
-
// Validate file extension
|
|
167
|
-
const extensionResult = validateImageExtension('image.jpg');
|
|
168
|
-
if (!extensionResult.isValid) {
|
|
169
|
-
console.error(extensionResult.error);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Use MIME type constants
|
|
173
|
-
if (mimeType === IMAGE_MIME_TYPES.PNG) {
|
|
174
|
-
console.log('PNG image');
|
|
175
|
-
}
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
## API Reference
|
|
179
|
-
|
|
180
|
-
### Core Validators
|
|
181
|
-
|
|
182
|
-
- `validateEmail(email: string): ValidationResult`
|
|
183
|
-
- `validatePassword(password: string, options?: PasswordOptions): ValidationResult`
|
|
184
|
-
- `validatePasswordConfirmation(password: string, confirmPassword: string): ValidationResult`
|
|
185
|
-
- `validateRequired(value: string, fieldName?: string): ValidationResult`
|
|
186
|
-
- `validateName(name: string, fieldName?: string, minLength?: number): ValidationResult`
|
|
187
|
-
- `validatePhone(phone: string): ValidationResult`
|
|
188
|
-
|
|
189
|
-
### Number Validators
|
|
190
|
-
|
|
191
|
-
- `validateNumberRange(value: number, min: number, max: number, fieldName?: string): ValidationResult`
|
|
192
|
-
- `validatePositiveNumber(value: number, fieldName?: string): ValidationResult`
|
|
193
|
-
|
|
194
|
-
### String Validators
|
|
195
|
-
|
|
196
|
-
- `validateMinLength(value: string, minLength: number, fieldName?: string): ValidationResult`
|
|
197
|
-
- `validateMaxLength(value: string, maxLength: number, fieldName?: string): ValidationResult`
|
|
198
|
-
- `validatePattern(value: string, pattern: RegExp, fieldName?: string, errorMessage?: string): ValidationResult`
|
|
199
|
-
|
|
200
|
-
### Date Validators
|
|
201
|
-
|
|
202
|
-
- `validateDateOfBirth(date: Date): ValidationResult`
|
|
203
|
-
- `validateAge(age: number): ValidationResult` (13-120 years)
|
|
204
|
-
|
|
205
|
-
### Image Validators
|
|
206
|
-
|
|
207
|
-
- `validateImageUri(uri: string, fieldName?: string): ValidationResult` - Validates image URI (supports file://, http://, https://, and data: URLs)
|
|
208
|
-
- `validateImageMimeType(mimeType: string, fieldName?: string): ValidationResult` - Validates MIME type is supported image type
|
|
209
|
-
- `validateImageExtension(uri: string, fieldName?: string): ValidationResult` - Validates file extension is supported image type
|
|
210
|
-
- `validateImageDataUrl(dataUrl: string, fieldName?: string): ValidationResult` - Validates data URL is image type
|
|
211
|
-
- `getImageMimeType(uri: string): string | null` - Get MIME type from URI (supports all URI types)
|
|
212
|
-
|
|
213
|
-
### MIME Type Utilities
|
|
214
|
-
|
|
215
|
-
- `getMimeTypeFromExtension(uri: string): string | null` - Get MIME type from file extension
|
|
216
|
-
- `getMimeTypeFromDataUrl(dataUrl: string): string | null` - Get MIME type from data URL
|
|
217
|
-
- `IMAGE_MIME_TYPES` - Supported image MIME types constants
|
|
218
|
-
- `SUPPORTED_IMAGE_MIME_TYPES` - Array of supported image MIME types
|
|
219
|
-
- `EXTENSION_TO_MIME_TYPE` - File extension to MIME type mapping
|
|
220
|
-
- `MIME_TYPE_TO_EXTENSION` - MIME type to file extension mapping
|
|
221
|
-
|
|
222
|
-
### Batch Validation
|
|
223
|
-
|
|
224
|
-
- `batchValidate(validations: Array<{ field: string; validator: () => ValidationResult }>): { isValid: boolean; errors: Record<string, string> }`
|
|
225
|
-
|
|
226
|
-
## Types
|
|
227
|
-
|
|
228
|
-
```typescript
|
|
229
|
-
interface ValidationResult {
|
|
230
|
-
isValid: boolean;
|
|
231
|
-
error?: string;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
interface PasswordOptions {
|
|
235
|
-
minLength?: number;
|
|
236
|
-
requireUppercase?: boolean;
|
|
237
|
-
requireLowercase?: boolean;
|
|
238
|
-
requireNumber?: boolean;
|
|
239
|
-
}
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
## License
|
|
243
|
-
|
|
244
|
-
MIT
|
|
245
|
-
|