UNPKG

@magic/core

Version:

@magic core. generate static pages and serverless lambdas. ~5kb client boilerplate.

263 lines (220 loc) 7.4 kB
export const CHECK_PROPS = (props, propTypeDecl, name, log) => { const currentPage = app && app.state?.url ? app.state.url : 'Unknown on client.' if (log === false) { log = () => {} } else if (!log) { log = e => console.log(e.code, e.message) } const error = (msg, code = 'E_CHECK_PROPS') => { const err = new Error(msg) err.code = code return err } const is = (e, ...types) => types.some(type => (typeof is[type] === 'function' ? is[type](e) : typeof e === type)) is.number = e => e === +e is.integer = e => e === +e && e === (e | 0) is.float = e => e === +e is.array = e => Array.isArray(e) is.regexp = e => e instanceof RegExp is.date = e => e instanceof Date is.error = e => e instanceof Error is.null = e => e === null is.promise = e => e instanceof Promise if (!propTypeDecl) { const err = error('expected propTypes as second argument', `E_CHECK_PROPS_${currentPage}`) log(err) return false } if (!name) { const err = error('expected Module name as third argument', 'E_CHECK_PROPS') log(`${err.stack} on page ${currentPage}`) return false } let propTypes = propTypeDecl[name] if (!is.array(propTypes)) { const err = error( `expected propTypes to be an array. received: ${propTypes} on page ${currentPage} in component ${name}`, 'E_CHECK_PROPS', ) log.error(err) return false } if (!propTypes[0].key) { let [FALLBACK, ...pT] = propTypes propTypes = pT if (FALLBACK && typeof props === FALLBACK.type) { return true } } const errors = [] propTypes.forEach(propType => { const { key, required, type, oneOf, someOf } = propType let value = props[key] const types = is.array(type) ? type : [type] if (!required && !types.includes('undefined')) { types.push('undefined') } if (oneOf) { if (!value && !required) { value = propType.default } const includes = oneOf.includes(value) if (!includes && value !== propType.default) { const err = error( `${name} needs value to be one of [${oneOf .filter(a => a) .join(', ')}]. received ${value}`, 'E_CHECK_PROPS_ONEOF_MISMATCH', ) errors.push(err) } } if (someOf) { if (!value && !required) { value = propType.default } if (!is.array(value)) { const err = error( `${name} has someOf, someOf needs value to be an array. received ${value}, which is a ${typeof value}`, 'E_CHECK_PROPS_SOMEOF_ARG_NOT_ARRAY', ) errors.push(err) } const includes = !value.some(inc => !someOf.includes(inc)) if (!includes && value !== propType.default) { const err = error( `${name} needs value to be one of [${someOf.join(', ')}]. received ${value}`, 'E_CHECK_PROPS_SOME_OF_ARG_MISMATCH', ) errors.push(err) } } const match = is(value, ...types) if (is.array(required)) { if (!match) { const altExists = required.filter(key => is(props[key], ...types)) if (altExists.length) { value = props[altExists[0]] } } } const typeInfo = types.length > 1 ? 'one of' : 'a' const typeString = types.length > 1 ? `["${types.join(', "')}"]` : types[0] if (!is(value, ...types)) { const err = error( `${name} needs props.${key} to be ${typeInfo} ${typeString}. received ${typeof value}`, 'E_CHECK_PROPS_TYPE_MISMATCH', ) errors.push(err) } else if (required) { if (typeof value === 'object' && !Object.keys(value).length) { let typeString = '' if (types.includes('array')) { typeString += ' array' } if (types.includes('object')) { if (types.includes('array')) { typeString += ' or' } typeString += ' object' } const err = error( `${name} needs props.${key} to be a non empty ${typeString}`, 'E_CHECK_PROPS_PROP_EMPTY', ) errors.push(err) } } if (is(value, 'number')) { // handle range prop here const { max = Number.MAX_SAFE_INTEGER, min = Number.MIN_SAFE_INTEGER } = propType if (max && value > max) { const err = error( `${name} number expected to be <= ${max}, was ${value}`, 'E_CHECK_PROPS_NUMBER_TOO_BIG', ) errors.push(err) } else if (min > 0 && value < min) { const err = error( `${name} number expected to be >= ${min}, was ${value}`, 'E_CHECK_PROPS_NUMBER_TOO_SMALL', ) errors.push(err) } } if (is(value, 'string')) { const { max = 500, min = 0 } = propType if (max && value.length > max) { const err = error( `${name} string length expected to be <= ${max}, length was ${value.length}`, 'E_CHECK_PROPS_STRING_TO_LONG', ) errors.push(err) } else if (min > 0 && value.length < min) { const err = error( `${name} string length expected to be >= ${min}, length was ${value.length}`, 'E_CHECK_PROPS_STRING_TO_SHORT', ) errors.push(err) } } if (type === 'array' && propType.item && value) { const { item } = propType if (!is(value, 'array')) { const err = error( `${name} needs props.${key} to be an array. received ${typeof value}`, 'E_CHECK_PROPS_ARRAY_MISMATCH', ) errors.push(err) } value.forEach(val => { const typeInfo = is(item.type, 'array') && item.type.length > 1 ? 'one of' : 'a' const typeString = is(item.type, 'array') ? item.type.length > 1 ? `["${item.type.join(', "')}"]` : item.type[0] : item.type if (item.type.includes('object') && is(val, 'object')) { item.keys.forEach(iKey => { const v = val[iKey.key] if (!is(v, iKey.type)) { const typeInfo = is(iKey.type, 'array') && iKey.type.length > 1 ? 'one of' : 'a' let typeString = '' if (Array.isArray(iKey.type)) { if (iKey.type.length > 1) { typeString = `["${iKey.type.join(', "')}"]` } else { typeString = iKey.type[0] } } else { typeString = iKey.type } const err = error( `${name} expects item.${ iKey.key } to be ${typeInfo} ${typeString}, received ${typeof v}, on page ${currentPage}`, 'E_CHECK_PROPS_OBJECT_KEY_MISMATCH', ) errors.push(err) } }) } else { const type = is(item.type, 'array') ? item.type : [item.type] if (!is(val, ...type)) { const err = error( `${name} has item that is expected to be ${typeInfo} ${typeString}, received ${typeof val}, on page ${currentPage}`, 'E_CHECK_PROPS_ARRAY_ITEM_MISMATCH', ) errors.push(err) } } }) } }) if (log.error) { errors.forEach(log.error) } return !errors.length }