validation.ts•3.45 kB
import { DataRule, ValidationError } from '../query-plan'
import { UserFacingError } from '../user-facing-error'
import { assertNever } from '../utils'
export function performValidation(data: unknown, rules: DataRule[], error: ValidationError) {
if (!rules.every((rule) => doesSatisfyRule(data, rule))) {
const message = renderMessage(data, error)
const code = getErrorCode(error)
throw new UserFacingError(message, code, error.context)
}
}
export function doesSatisfyRule(data: unknown, rule: DataRule): boolean {
switch (rule.type) {
case 'rowCountEq':
if (Array.isArray(data)) {
return data.length === rule.args
}
if (data === null) {
return rule.args === 0
}
return rule.args === 1
case 'rowCountNeq':
if (Array.isArray(data)) {
return data.length !== rule.args
}
if (data === null) {
return rule.args !== 0
}
return rule.args !== 1
case 'affectedRowCountEq':
return data === rule.args
case 'never':
return false
default:
assertNever(rule, `Unknown rule type: ${(rule as DataRule).type}`)
}
}
function renderMessage(data: unknown, error: ValidationError): string {
switch (error.error_identifier) {
case 'RELATION_VIOLATION':
return `The change you are trying to make would violate the required relation '${error.context.relation}' between the \`${error.context.modelA}\` and \`${error.context.modelB}\` models.`
case 'MISSING_RECORD':
return `An operation failed because it depends on one or more records that were required but not found. No record was found for ${error.context.operation}.`
case 'MISSING_RELATED_RECORD': {
const hint = error.context.neededFor ? ` (needed to ${error.context.neededFor})` : ''
return `An operation failed because it depends on one or more records that were required but not found. No '${error.context.model}' record${hint} was found for ${error.context.operation} on ${error.context.relationType} relation '${error.context.relation}'.`
}
case 'INCOMPLETE_CONNECT_INPUT':
return `An operation failed because it depends on one or more records that were required but not found. Expected ${
error.context.expectedRows
} records to be connected, found only ${Array.isArray(data) ? data.length : data}.`
case 'INCOMPLETE_CONNECT_OUTPUT':
return `The required connected records were not found. Expected ${
error.context.expectedRows
} records to be connected after connect operation on ${error.context.relationType} relation '${
error.context.relation
}', found ${Array.isArray(data) ? data.length : data}.`
case 'RECORDS_NOT_CONNECTED':
return `The records for relation \`${error.context.relation}\` between the \`${error.context.parent}\` and \`${error.context.child}\` models are not connected.`
default:
assertNever(error, `Unknown error identifier: ${error}`)
}
}
function getErrorCode(error: ValidationError): string {
switch (error.error_identifier) {
case 'RELATION_VIOLATION':
return 'P2014'
case 'RECORDS_NOT_CONNECTED':
return 'P2017'
case 'INCOMPLETE_CONNECT_OUTPUT':
return 'P2018'
case 'MISSING_RECORD':
case 'MISSING_RELATED_RECORD':
case 'INCOMPLETE_CONNECT_INPUT':
return 'P2025'
default:
assertNever(error, `Unknown error identifier: ${error}`)
}
}