@rokkit/forms 1.0.0-next.136 → 1.0.0-next.138

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.
@@ -19,103 +19,117 @@ function isEmpty(value) {
19
19
  return value === undefined || value === null || value === ''
20
20
  }
21
21
 
22
+ /** @returns {ValidationMessage} */
23
+ function errorMsg(text) {
24
+ return { state: 'error', text }
25
+ }
26
+
22
27
  /**
23
- * Validate string field
28
+ * Validate string pattern constraint
24
29
  * @private
25
- * @param {string} value - String value
26
- * @param {Object} schema - Field schema
27
- * @param {string} label - Field label
28
- * @returns {ValidationMessage|null}
29
30
  */
30
- function validateString(value, schema, label) {
31
- // Convert to string if not already
32
- const stringValue = String(value)
33
-
34
- // Pattern validation
35
- if (schema.pattern) {
36
- const regex = new RegExp(schema.pattern)
37
- if (!regex.test(stringValue)) {
38
- return {
39
- state: 'error',
40
- text: `${label} format is invalid`
41
- }
42
- }
43
- }
31
+ function validateStringPattern(stringValue, schema, label) {
32
+ if (!schema.pattern) return null
33
+ const regex = new RegExp(schema.pattern)
34
+ if (!regex.test(stringValue)) return errorMsg(`${label} format is invalid`)
35
+ return null
36
+ }
44
37
 
45
- // Length validations
38
+ /**
39
+ * Validate string length constraints
40
+ * @private
41
+ */
42
+ function validateStringLength(stringValue, schema, label) {
46
43
  if (schema.minLength !== undefined && stringValue.length < schema.minLength) {
47
- return {
48
- state: 'error',
49
- text: `${label} must be at least ${schema.minLength} characters`
50
- }
44
+ return errorMsg(`${label} must be at least ${schema.minLength} characters`)
51
45
  }
52
-
53
46
  if (schema.maxLength !== undefined && stringValue.length > schema.maxLength) {
54
- return {
55
- state: 'error',
56
- text: `${label} must be no more than ${schema.maxLength} characters`
57
- }
47
+ return errorMsg(`${label} must be no more than ${schema.maxLength} characters`)
58
48
  }
49
+ return null
50
+ }
59
51
 
60
- // Enum validation
52
+ /**
53
+ * Validate string enum constraint
54
+ * @private
55
+ */
56
+ function validateStringEnum(stringValue, schema, label) {
61
57
  if (schema.enum && !schema.enum.includes(stringValue)) {
62
- return {
63
- state: 'error',
64
- text: `${label} must be one of: ${schema.enum.join(', ')}`
65
- }
58
+ return errorMsg(`${label} must be one of: ${schema.enum.join(', ')}`)
66
59
  }
67
-
68
60
  return null
69
61
  }
70
62
 
71
63
  /**
72
- * Validate number field
64
+ * Validate string field
73
65
  * @private
74
- * @param {number} value - Number value
66
+ * @param {string} value - String value
75
67
  * @param {Object} schema - Field schema
76
68
  * @param {string} label - Field label
77
69
  * @returns {ValidationMessage|null}
78
70
  */
79
- function validateNumber(value, schema, label) {
80
- const numValue = Number(value)
81
-
82
- // Check if it's a valid number
83
- if (isNaN(numValue)) {
84
- return {
85
- state: 'error',
86
- text: `${label} must be a valid number`
87
- }
88
- }
71
+ function validateString(value, schema, label) {
72
+ const stringValue = String(value)
73
+ return (
74
+ validateStringPattern(stringValue, schema, label) ||
75
+ validateStringLength(stringValue, schema, label) ||
76
+ validateStringEnum(stringValue, schema, label)
77
+ )
78
+ }
89
79
 
90
- // Integer validation
80
+ /**
81
+ * Validate number integer constraint
82
+ * @private
83
+ */
84
+ function validateNumberInteger(numValue, schema, label) {
91
85
  if (schema.type === 'integer' && !Number.isInteger(numValue)) {
92
- return {
93
- state: 'error',
94
- text: `${label} must be a whole number`
95
- }
86
+ return errorMsg(`${label} must be a whole number`)
96
87
  }
88
+ return null
89
+ }
97
90
 
98
- // Minimum validation (support both min and minimum)
91
+ /**
92
+ * Validate number minimum constraint
93
+ * @private
94
+ */
95
+ function validateNumberMin(numValue, schema, label) {
99
96
  const minimum = schema.min !== undefined ? schema.min : schema.minimum
100
97
  if (minimum !== undefined && numValue < minimum) {
101
- return {
102
- state: 'error',
103
- text: `${label} must be at least ${minimum}`
104
- }
98
+ return errorMsg(`${label} must be at least ${minimum}`)
105
99
  }
100
+ return null
101
+ }
106
102
 
107
- // Maximum validation (support both max and maximum)
103
+ /**
104
+ * Validate number maximum constraint
105
+ * @private
106
+ */
107
+ function validateNumberMax(numValue, schema, label) {
108
108
  const maximum = schema.max !== undefined ? schema.max : schema.maximum
109
109
  if (maximum !== undefined && numValue > maximum) {
110
- return {
111
- state: 'error',
112
- text: `${label} must be no more than ${maximum}`
113
- }
110
+ return errorMsg(`${label} must be no more than ${maximum}`)
114
111
  }
115
-
116
112
  return null
117
113
  }
118
114
 
115
+ /**
116
+ * Validate number field
117
+ * @private
118
+ * @param {number} value - Number value
119
+ * @param {Object} schema - Field schema
120
+ * @param {string} label - Field label
121
+ * @returns {ValidationMessage|null}
122
+ */
123
+ function validateNumber(value, schema, label) {
124
+ const numValue = Number(value)
125
+ if (isNaN(numValue)) return errorMsg(`${label} must be a valid number`)
126
+ return (
127
+ validateNumberInteger(numValue, schema, label) ||
128
+ validateNumberMin(numValue, schema, label) ||
129
+ validateNumberMax(numValue, schema, label)
130
+ )
131
+ }
132
+
119
133
  /**
120
134
  * Validate boolean field
121
135
  * @private
@@ -130,10 +144,7 @@ function validateBoolean(value, schema, label) {
130
144
 
131
145
  // For required boolean fields, we might want to ensure it's explicitly true
132
146
  if (schema.required && schema.mustBeTrue && !boolValue) {
133
- return {
134
- state: 'error',
135
- text: `${label} must be accepted`
136
- }
147
+ return errorMsg(`${label} must be accepted`)
137
148
  }
138
149
 
139
150
  return null
@@ -210,27 +221,10 @@ function getValueByPath(data, path) {
210
221
  }
211
222
 
212
223
  /**
213
- * Validate a single field value against its schema
214
- * @param {any} value - Field value to validate
215
- * @param {Object} fieldSchema - Field schema definition
216
- * @param {string} [fieldLabel] - Field label for error messages
217
- * @returns {ValidationMessage|null} Validation message or null if valid
224
+ * Dispatch to the type-specific validator
225
+ * @private
218
226
  */
219
- export function validateField(value, fieldSchema, fieldLabel = 'Field') {
220
- if (!fieldSchema) return null
221
-
222
- // Required field validation
223
- if (fieldSchema.required && isEmpty(value)) {
224
- return {
225
- state: 'error',
226
- text: `${fieldLabel} is required`
227
- }
228
- }
229
-
230
- // Skip other validations if field is empty and not required
231
- if (isEmpty(value)) return null
232
-
233
- // Type-specific validations
227
+ function validateByType(value, fieldSchema, fieldLabel) {
234
228
  switch (fieldSchema.type) {
235
229
  case 'string':
236
230
  return validateString(value, fieldSchema, fieldLabel)
@@ -243,6 +237,52 @@ export function validateField(value, fieldSchema, fieldLabel = 'Field') {
243
237
  return null
244
238
  }
245
239
  }
240
+
241
+ /**
242
+ * Check presence (required + empty) before type validation.
243
+ * Returns an error message, null if valid/skippable, or 'skip' sentinel to skip type checks.
244
+ * @private
245
+ * @returns {ValidationMessage|null|'skip'}
246
+ */
247
+ function checkPresence(value, fieldSchema, fieldLabel) {
248
+ if (fieldSchema.required && isEmpty(value)) return errorMsg(`${fieldLabel} is required`)
249
+ if (isEmpty(value)) return 'skip'
250
+ return null
251
+ }
252
+
253
+ /**
254
+ * Validate a single field value against its schema
255
+ * @param {any} value - Field value to validate
256
+ * @param {Object} fieldSchema - Field schema definition
257
+ * @param {string} [fieldLabel] - Field label for error messages
258
+ * @returns {ValidationMessage|null} Validation message or null if valid
259
+ */
260
+ export function validateField(value, fieldSchema, fieldLabel = 'Field') {
261
+ if (!fieldSchema) return null
262
+ const presenceResult = checkPresence(value, fieldSchema, fieldLabel)
263
+ if (presenceResult === 'skip') return null
264
+ if (presenceResult) return presenceResult
265
+ return validateByType(value, fieldSchema, fieldLabel)
266
+ }
267
+
268
+ /**
269
+ * Validate one element from the layout
270
+ * @private
271
+ */
272
+ function validateElement(element, data, schema, results) {
273
+ if (!element.scope) return
274
+
275
+ const fieldPath = element.scope.replace(/^#\//, '')
276
+ const fieldSchema = getFieldSchema(fieldPath, schema)
277
+ const fieldLabel = element.label || element.title || fieldPath
278
+ const value = getValueByPath(data, fieldPath)
279
+
280
+ const result = validateField(value, fieldSchema, fieldLabel)
281
+ if (result) {
282
+ results[fieldPath] = result
283
+ }
284
+ }
285
+
246
286
  /**
247
287
  * Validate all fields in a data object
248
288
  * @param {Object} data - Data object to validate
@@ -256,17 +296,7 @@ export function validateAll(data, schema, layout) {
256
296
  if (!layout.elements || !schema.properties) return validationResults
257
297
 
258
298
  for (const element of layout.elements) {
259
- if (!element.scope) continue
260
-
261
- const fieldPath = element.scope.replace(/^#\//, '')
262
- const fieldSchema = getFieldSchema(fieldPath, schema)
263
- const fieldLabel = element.label || element.title || fieldPath
264
- const value = getValueByPath(data, fieldPath)
265
-
266
- const result = validateField(value, fieldSchema, fieldLabel)
267
- if (result) {
268
- validationResults[fieldPath] = result
269
- }
299
+ validateElement(element, data, schema, validationResults)
270
300
  }
271
301
 
272
302
  return validationResults