|
| 1 | +/** |
| 2 | + * Validates that a language map object (per IIIF spec) has at least one language key |
| 3 | + * with an array of string values. |
| 4 | + * |
| 5 | + * @param {Object} langMap - Object keyed by IETF language tag (e.g., { "en": ["value"], "fr": ["valeur"] }). |
| 6 | + * @returns {boolean} - True if the language map is valid. |
| 7 | + */ |
| 8 | +function validateLanguageMap(langMap) { |
| 9 | + if (typeof langMap !== 'object' || langMap === null) |
| 10 | + return false |
| 11 | + |
| 12 | + const keys = Object.keys(langMap) |
| 13 | + if (keys.length === 0) |
| 14 | + return false |
| 15 | + |
| 16 | + return keys.some(key => { |
| 17 | + const values = langMap[key] |
| 18 | + return Array.isArray(values) && values.length > 0 && values.every(v => typeof v === 'string') |
| 19 | + }) |
| 20 | +} |
| 21 | + |
| 22 | +/** |
| 23 | + * Validates a metadata label or value field that can be a plain string or a language map. |
| 24 | + * |
| 25 | + * @param {string} fieldName - The field name for error messages ("label" or "value"). |
| 26 | + * @param {*} fieldValue - The value to validate. |
| 27 | + * @param {boolean} allowEmpty - Whether empty strings are permitted (true for values, false for labels). |
| 28 | + * @returns {Object|null} - Returns { isValid: false, errors: string } on failure, or null on success. |
| 29 | + */ |
| 30 | +function validateMetadataField(fieldName, fieldValue, allowEmpty) { |
| 31 | + const isString = typeof fieldValue === 'string' |
| 32 | + const isLangMap = typeof fieldValue === 'object' && fieldValue !== null |
| 33 | + |
| 34 | + // Invalid type entirely |
| 35 | + if (!isString && !isLangMap) { |
| 36 | + const message = fieldName === 'label' |
| 37 | + ? `metadata item label must be a string or language map object` |
| 38 | + : 'metadata item value cannot be processed' |
| 39 | + return { isValid: false, errors: message } |
| 40 | + } |
| 41 | + |
| 42 | + // Plain string |
| 43 | + if (isString) { |
| 44 | + if (!allowEmpty && fieldValue.trim() === '') { |
| 45 | + return { isValid: false, errors: `metadata item ${fieldName} must be a non-empty string` } |
| 46 | + } |
| 47 | + return null |
| 48 | + } |
| 49 | + |
| 50 | + // Language map |
| 51 | + if (!validateLanguageMap(fieldValue)) { |
| 52 | + const message = allowEmpty |
| 53 | + ? `metadata item ${fieldName} must be a valid language map with string values` |
| 54 | + : `metadata item ${fieldName} must be a valid language map with non-empty string values` |
| 55 | + return { isValid: false, errors: message } |
| 56 | + } |
| 57 | + |
| 58 | + return null |
| 59 | +} |
| 60 | + |
1 | 61 | /** |
2 | 62 | * Validate that the provided data payload is a valid Project object. |
3 | 63 | * This function validates both the presence and the data types of required project properties. |
@@ -49,15 +109,12 @@ export function validateProjectPayload(payload) { |
49 | 109 | return { isValid: false, errors: 'metadata item must have label and value properties' } |
50 | 110 | } |
51 | 111 |
|
52 | | - // Validate label and value are non-empty strings |
53 | | - if (typeof metadataItem.label?.none?.[0] === 'string' && metadataItem.label?.none?.[0].trim() === '') |
54 | | - return { isValid: false, errors: 'metadata item label must be a non-empty language map' } |
55 | | - |
56 | | - if (typeof metadataItem.label === 'string' && metadataItem.label.trim() === '') |
57 | | - return { isValid: false, errors: 'metadata item label must be a non-empty string' } |
| 112 | + // Validate label and value - accept plain string or language-mapped object (any IETF language tag) |
| 113 | + const labelError = validateMetadataField('label', metadataItem.label, false) |
| 114 | + if (labelError) return labelError |
58 | 115 |
|
59 | | - if (typeof metadataItem.value?.none?.[0] !== 'string' && typeof metadataItem.value !== 'string') |
60 | | - return { isValid: false, errors: 'metadata item value cannot be processed' } |
| 116 | + const valueError = validateMetadataField('value', metadataItem.value, true) |
| 117 | + if (valueError) return valueError |
61 | 118 |
|
62 | 119 | // Ensure no extra properties beyond label and value |
63 | 120 | const allowedProps = ['label', 'value'] |
|
0 commit comments