@jammysunshine/astrology-shared 1.0.1

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.
@@ -0,0 +1,308 @@
1
+ const { validateBirthData, createValidationError } = require('./index');
2
+
3
+ const { getLogger } = require('../logger');
4
+
5
+ /**
6
+ * Base Validator class for astrology services
7
+ * Provides common validation functionality and patterns
8
+ */
9
+ class BaseValidator {
10
+ constructor() {
11
+ this.logger = getLogger('BaseValidator');
12
+ }
13
+
14
+
15
+
16
+
17
+
18
+ /**
19
+ * Validate required fields
20
+ * @param {Object} data - Data object
21
+ * @param {Array<string>} requiredFields - Required field names
22
+ * @returns {Object} Validation result
23
+ */
24
+ validateRequired(data, requiredFields) {
25
+ const errors = [];
26
+
27
+ for (const field of requiredFields) {
28
+ if (!data || !(field in data) || data[field] === null || data[field] === undefined) {
29
+ errors.push({
30
+ field,
31
+ message: `${field} is required`,
32
+ code: 'MISSING_REQUIRED_FIELD'
33
+ });
34
+ }
35
+ }
36
+
37
+ return {
38
+ isValid: errors.length === 0,
39
+ errors
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Validate string fields with length constraints
45
+ * @param {Object} data - Data object
46
+ * @param {Object} fieldRules - Field validation rules {fieldName: {minLength, maxLength, pattern}}
47
+ * @returns {Object} Validation result
48
+ */
49
+ validateStringFields(data, fieldRules) {
50
+ const errors = [];
51
+
52
+ for (const [field, rules] of Object.entries(fieldRules)) {
53
+ const value = data[field];
54
+
55
+ if (value !== undefined && value !== null) {
56
+ if (typeof value !== 'string') {
57
+ errors.push({
58
+ field,
59
+ message: `${field} must be a string`,
60
+ code: 'INVALID_TYPE'
61
+ });
62
+ continue;
63
+ }
64
+
65
+ if (rules.minLength && value.length < rules.minLength) {
66
+ errors.push({
67
+ field,
68
+ message: `${field} must be at least ${rules.minLength} characters`,
69
+ code: 'TOO_SHORT'
70
+ });
71
+ }
72
+
73
+ if (rules.maxLength && value.length > rules.maxLength) {
74
+ errors.push({
75
+ field,
76
+ message: `${field} must be at most ${rules.maxLength} characters`,
77
+ code: 'TOO_LONG'
78
+ });
79
+ }
80
+
81
+ if (rules.pattern && !rules.pattern.test(value)) {
82
+ errors.push({
83
+ field,
84
+ message: `${field} format is invalid`,
85
+ code: 'INVALID_FORMAT'
86
+ });
87
+ }
88
+ }
89
+ }
90
+
91
+ return {
92
+ isValid: errors.length === 0,
93
+ errors
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Validate numeric ranges
99
+ * @param {Object} data - Data object
100
+ * @param {Object} fieldRules - Field validation rules {fieldName: {min, max, integer}}
101
+ * @returns {Object} Validation result
102
+ */
103
+ validateNumericFields(data, fieldRules) {
104
+ const errors = [];
105
+
106
+ for (const [field, rules] of Object.entries(fieldRules)) {
107
+ const value = data[field];
108
+
109
+ if (value !== undefined && value !== null) {
110
+ if (typeof value !== 'number' || isNaN(value)) {
111
+ errors.push({
112
+ field,
113
+ message: `${field} must be a valid number`,
114
+ code: 'INVALID_NUMBER'
115
+ });
116
+ continue;
117
+ }
118
+
119
+ if (rules.integer && !Number.isInteger(value)) {
120
+ errors.push({
121
+ field,
122
+ message: `${field} must be an integer`,
123
+ code: 'NOT_INTEGER'
124
+ });
125
+ }
126
+
127
+ if (rules.min !== undefined && value < rules.min) {
128
+ errors.push({
129
+ field,
130
+ message: `${field} must be at least ${rules.min}`,
131
+ code: 'TOO_SMALL'
132
+ });
133
+ }
134
+
135
+ if (rules.max !== undefined && value > rules.max) {
136
+ errors.push({
137
+ field,
138
+ message: `${field} must be at most ${rules.max}`,
139
+ code: 'TOO_LARGE'
140
+ });
141
+ }
142
+ }
143
+ }
144
+
145
+ return {
146
+ isValid: errors.length === 0,
147
+ errors
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Validate array fields
153
+ * @param {Object} data - Data object
154
+ * @param {Object} fieldRules - Field validation rules {fieldName: {minItems, maxItems, itemValidator}}
155
+ * @returns {Object} Validation result
156
+ */
157
+ validateArrayFields(data, fieldRules) {
158
+ const errors = [];
159
+
160
+ for (const [field, rules] of Object.entries(fieldRules)) {
161
+ const value = data[field];
162
+
163
+ if (value !== undefined && value !== null) {
164
+ if (!Array.isArray(value)) {
165
+ errors.push({
166
+ field,
167
+ message: `${field} must be an array`,
168
+ code: 'NOT_ARRAY'
169
+ });
170
+ continue;
171
+ }
172
+
173
+ if (rules.minItems && value.length < rules.minItems) {
174
+ errors.push({
175
+ field,
176
+ message: `${field} must have at least ${rules.minItems} items`,
177
+ code: 'TOO_FEW_ITEMS'
178
+ });
179
+ }
180
+
181
+ if (rules.maxItems && value.length > rules.maxItems) {
182
+ errors.push({
183
+ field,
184
+ message: `${field} must have at most ${rules.maxItems} items`,
185
+ code: 'TOO_MANY_ITEMS'
186
+ });
187
+ }
188
+
189
+ // Validate individual items if validator provided
190
+ if (rules.itemValidator && typeof rules.itemValidator === 'function') {
191
+ value.forEach((item, index) => {
192
+ try {
193
+ const itemResult = rules.itemValidator(item);
194
+ if (!itemResult.isValid) {
195
+ errors.push(...itemResult.errors.map(error => ({
196
+ ...error,
197
+ field: `${field}[${index}].${error.field}`
198
+ })));
199
+ }
200
+ } catch (error) {
201
+ errors.push({
202
+ field: `${field}[${index}]`,
203
+ message: error.message,
204
+ code: 'ITEM_VALIDATION_ERROR'
205
+ });
206
+ }
207
+ });
208
+ }
209
+ }
210
+ }
211
+
212
+ return {
213
+ isValid: errors.length === 0,
214
+ errors
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Validate coordinates (latitude/longitude)
220
+ * @param {Object} coordinates - Coordinates object {latitude, longitude}
221
+ * @returns {Object} Validation result
222
+ */
223
+ validateCoordinates(coordinates) {
224
+ const errors = [];
225
+
226
+ if (!coordinates || typeof coordinates !== 'object') {
227
+ return {
228
+ isValid: false,
229
+ errors: [{
230
+ field: 'coordinates',
231
+ message: 'Coordinates object is required',
232
+ code: 'MISSING_COORDINATES'
233
+ }]
234
+ };
235
+ }
236
+
237
+ const { latitude, longitude } = coordinates;
238
+
239
+ if (latitude === undefined || latitude === null) {
240
+ errors.push({
241
+ field: 'coordinates.latitude',
242
+ message: 'Latitude is required',
243
+ code: 'MISSING_LATITUDE'
244
+ });
245
+ } else if (typeof latitude !== 'number' || isNaN(latitude)) {
246
+ errors.push({
247
+ field: 'coordinates.latitude',
248
+ message: 'Latitude must be a valid number',
249
+ code: 'INVALID_LATITUDE'
250
+ });
251
+ } else if (latitude < -90 || latitude > 90) {
252
+ errors.push({
253
+ field: 'coordinates.latitude',
254
+ message: 'Latitude must be between -90 and 90',
255
+ code: 'LATITUDE_OUT_OF_RANGE'
256
+ });
257
+ }
258
+
259
+ if (longitude === undefined || longitude === null) {
260
+ errors.push({
261
+ field: 'coordinates.longitude',
262
+ message: 'Longitude is required',
263
+ code: 'MISSING_LONGITUDE'
264
+ });
265
+ } else if (typeof longitude !== 'number' || isNaN(longitude)) {
266
+ errors.push({
267
+ field: 'coordinates.longitude',
268
+ message: 'Longitude must be a valid number',
269
+ code: 'INVALID_LONGITUDE'
270
+ });
271
+ } else if (longitude < -180 || longitude > 180) {
272
+ errors.push({
273
+ field: 'coordinates.longitude',
274
+ message: 'Longitude must be between -180 and 180',
275
+ code: 'LONGITUDE_OUT_OF_RANGE'
276
+ });
277
+ }
278
+
279
+ return {
280
+ isValid: errors.length === 0,
281
+ errors
282
+ };
283
+ }
284
+
285
+
286
+
287
+ /**
288
+ * Combine multiple validation results
289
+ * @param {Array<Object>} validationResults - Array of validation result objects
290
+ * @returns {Object} Combined validation result
291
+ */
292
+ combineValidations(validationResults) {
293
+ const allErrors = [];
294
+
295
+ for (const result of validationResults) {
296
+ if (!result.isValid && result.errors) {
297
+ allErrors.push(...result.errors);
298
+ }
299
+ }
300
+
301
+ return {
302
+ isValid: allErrors.length === 0,
303
+ errors: allErrors
304
+ };
305
+ }
306
+ }
307
+
308
+ module.exports = BaseValidator;
@@ -0,0 +1,88 @@
1
+ // This file is now refactored to use the AJV/JSON Schema system.
2
+
3
+ const { isValid, parse } = require('date-fns'); // Keep date-fns for now if AJV patterns are not strict enough for parsing.
4
+ const { validateInput, loadSchema } = require('../schemas'); // Import validateInput and loadSchema from central schema management
5
+
6
+ /**
7
+ * Validate birth data with strict DD/MM/YYYY format using AJV.
8
+ * @param {Object} data - Input data to validate
9
+ * @returns {Object} Validated and sanitized data
10
+ * @throws {Error} If validation fails
11
+ */
12
+ function validateBirthData(data) {
13
+ // Use the birthData component schema for validating just the birthData object
14
+ const schemaName = 'astrology/birthData';
15
+
16
+ const result = validateInput(schemaName, data); // Validate birthData directly
17
+
18
+ if (!result.isValid) {
19
+ const errorDetails = result.errors.map(d => d.message).join(', ');
20
+ throw createValidationError('VALIDATION_ERROR', `Invalid birth data: ${errorDetails}`, result.errors);
21
+ }
22
+
23
+ // If validation is successful, return the validated data (which may have defaults applied by AJV)
24
+ return result.data;
25
+ }
26
+
27
+ /**
28
+ * Create a standardized validation error
29
+ * @param {string} code - Error code
30
+ * @param {string} message - Error message
31
+ * @param {Object} details - Additional error details
32
+ * @returns {Error} Validation error
33
+ */
34
+ function createValidationError(code, message, details = {}) {
35
+ const error = new Error(message);
36
+ error.code = code;
37
+ error.details = details;
38
+ error.type = 'VALIDATION_ERROR';
39
+ return error;
40
+ }
41
+
42
+ /**
43
+ * Validate client context for multi-frontend support using AJV.
44
+ * @param {Object} context - Client context
45
+ * @returns {Object} Validated context
46
+ */
47
+ function validateClientContext(context) {
48
+ if (!context) return {};
49
+
50
+ const result = validateInput('client-context', context); // Validate against client-context schema
51
+
52
+ if (!result.isValid) {
53
+ const errorDetails = result.errors.map(d => d.message).join(', ');
54
+ throw createValidationError('INVALID_CLIENT_CONTEXT', `Invalid client context: ${errorDetails}`, result.errors);
55
+ }
56
+
57
+ // If validated, AJV might have applied defaults/coerced types.
58
+ // We still need to validate preferences separately if they are nested.
59
+ if (result.data.preferences) {
60
+ result.data.preferences = validatePreferences(result.data.preferences);
61
+ }
62
+
63
+ return result.data;
64
+ }
65
+
66
+ /**
67
+ * Validate user preferences using AJV.
68
+ * @param {Object} preferences - User preferences
69
+ * @returns {Object} Validated preferences
70
+ */
71
+ function validatePreferences(preferences) {
72
+ const result = validateInput('preferences', preferences); // Validate against preferences schema
73
+
74
+ if (!result.isValid) {
75
+ const errorDetails = result.errors.map(d => d.message).join(', ');
76
+ throw createValidationError('INVALID_PREFERENCES', `Invalid user preferences: ${errorDetails}`, result.errors);
77
+ }
78
+
79
+ // If validated, AJV might have applied defaults/coerced types.
80
+ return result.data;
81
+ }
82
+
83
+ module.exports = {
84
+ validateBirthData,
85
+ validateClientContext,
86
+ validatePreferences,
87
+ createValidationError
88
+ };