mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 22:58:08 +00:00
Refine validation error catalog messages
This commit is contained in:
@@ -109,7 +109,9 @@ describe('TabErrors.vue', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(screen.getByText('Server Error: No outputs')).toBeInTheDocument()
|
||||
expect(screen.getAllByText('Prompt has no outputs').length).toBeGreaterThan(
|
||||
0
|
||||
)
|
||||
expect(
|
||||
screen.getByText(
|
||||
'The workflow does not contain any output nodes (e.g. Save Image, Preview Image) to produce a result.'
|
||||
|
||||
@@ -29,17 +29,47 @@ vi.mock('@/platform/distribution/types', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/i18n', () => ({
|
||||
te: vi.fn(() => false),
|
||||
st: vi.fn((_key: string, fallback: string) => fallback),
|
||||
t: vi.fn((key: string, params?: { count?: number }) => {
|
||||
if (key === 'errorOverlay.missingModels') {
|
||||
const count = params?.count ?? 0
|
||||
return `${count} required ${count === 1 ? 'model is' : 'models are'} missing`
|
||||
}
|
||||
return key
|
||||
})
|
||||
}))
|
||||
vi.mock('@/i18n', () => {
|
||||
const messages: Record<string, string> = {
|
||||
'errorCatalog.validationErrors.required_input_missing.title':
|
||||
'Missing connection',
|
||||
'errorCatalog.validationErrors.required_input_missing.message':
|
||||
'Required input slots have no connection feeding them.',
|
||||
'errorCatalog.validationErrors.required_input_missing.details':
|
||||
'{nodeName} is missing a required input: {inputName}',
|
||||
'errorCatalog.validationErrors.required_input_missing.itemLabel':
|
||||
'{nodeName} - {inputName}',
|
||||
'errorCatalog.validationErrors.required_input_missing.toastTitle':
|
||||
'Required input missing',
|
||||
'errorCatalog.validationErrors.required_input_missing.toastMessage':
|
||||
'{nodeName} is missing a required input: {inputName}',
|
||||
'errorCatalog.promptErrors.prompt_no_outputs.title':
|
||||
'Prompt has no outputs',
|
||||
'errorCatalog.promptErrors.prompt_no_outputs.desc':
|
||||
'The workflow does not contain any output nodes (e.g. Save Image, Preview Image) to produce a result.'
|
||||
}
|
||||
|
||||
const interpolate = (
|
||||
message: string,
|
||||
params?: Record<string, string | number>
|
||||
) =>
|
||||
message.replace(/\{(\w+)\}/g, (match, paramName) =>
|
||||
params?.[paramName] === undefined ? match : String(params[paramName])
|
||||
)
|
||||
|
||||
return {
|
||||
te: vi.fn((key: string) => key in messages),
|
||||
st: vi.fn((key: string, fallback: string) => messages[key] ?? fallback),
|
||||
t: vi.fn((key: string, params?: Record<string, string | number>) => {
|
||||
if (key === 'errorOverlay.missingModels') {
|
||||
const count = Number(params?.count ?? 0)
|
||||
return `${count} required ${count === 1 ? 'model is' : 'models are'} missing`
|
||||
}
|
||||
|
||||
return interpolate(messages[key] ?? key, params)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/stores/comfyRegistryStore', () => ({
|
||||
useComfyRegistryStore: () => ({
|
||||
@@ -412,10 +442,14 @@ describe('useErrorGroups', () => {
|
||||
)
|
||||
expect(execGroups.length).toBeGreaterThan(0)
|
||||
if (execGroups[0].type !== 'execution') return
|
||||
expect(execGroups[0].cards[0].errors[0].displayItemLabel).toBe('KSampler')
|
||||
expect(execGroups[0].cards[0].errors[0].toastTitle).toBe(
|
||||
'KSampler failed'
|
||||
)
|
||||
expect(execGroups[0].cards[0].errors[0]).toMatchObject({
|
||||
message: 'RuntimeError: CUDA out of memory',
|
||||
details: 'line 1\nline 2',
|
||||
isRuntimeError: true,
|
||||
exceptionType: 'RuntimeError'
|
||||
})
|
||||
expect(execGroups[0].cards[0].errors[0].displayItemLabel).toBeUndefined()
|
||||
expect(execGroups[0].cards[0].errors[0].toastTitle).toBeUndefined()
|
||||
})
|
||||
|
||||
it('includes prompt error when present', async () => {
|
||||
@@ -428,7 +462,8 @@ describe('useErrorGroups', () => {
|
||||
await nextTick()
|
||||
|
||||
const promptGroup = groups.allErrorGroups.value.find(
|
||||
(g) => g.type === 'execution' && g.displayTitle === 'No outputs'
|
||||
(g) =>
|
||||
g.type === 'execution' && g.displayTitle === 'Prompt has no outputs'
|
||||
)
|
||||
expect(promptGroup).toBeDefined()
|
||||
})
|
||||
|
||||
@@ -3671,7 +3671,7 @@
|
||||
},
|
||||
"bad_linked_input": {
|
||||
"title": "Invalid connection",
|
||||
"message": "A linked input connection is malformed.",
|
||||
"message": "A node connection could not be read correctly.",
|
||||
"details": "{nodeName} has an invalid connection for {inputName}.",
|
||||
"itemLabel": "{nodeName} - {inputName}",
|
||||
"toastTitle": "Invalid connection",
|
||||
@@ -3681,46 +3681,57 @@
|
||||
"title": "Invalid connection",
|
||||
"message": "Connected nodes are using incompatible input and output types.",
|
||||
"details": "{nodeName} has an incompatible connection for {inputName}.",
|
||||
"detailsWithTypes": "{nodeName}'s {inputName} input expects {expectedType}, but the connected output is {receivedType}.",
|
||||
"itemLabel": "{nodeName} - {inputName}",
|
||||
"toastTitle": "Invalid connection",
|
||||
"toastMessage": "{nodeName} has an incompatible connection for {inputName}."
|
||||
"toastMessage": "{nodeName} has an incompatible connection for {inputName}.",
|
||||
"toastMessageWithTypes": "{nodeName}'s {inputName} input expects {expectedType}, but the connected output is {receivedType}."
|
||||
},
|
||||
"invalid_input_type": {
|
||||
"title": "Invalid input",
|
||||
"message": "An input value has the wrong type.",
|
||||
"details": "{nodeName} couldn't convert {inputName} to the expected type.",
|
||||
"detailsWithValue": "The value {receivedValue} for {nodeName}'s {inputName} couldn't be converted to {expectedType}.",
|
||||
"itemLabel": "{nodeName} - {inputName}",
|
||||
"toastTitle": "Invalid input",
|
||||
"toastMessage": "{nodeName} couldn't convert {inputName} to the expected type."
|
||||
"toastMessage": "{nodeName} couldn't convert {inputName} to the expected type.",
|
||||
"toastMessageWithValue": "The value {receivedValue} for {nodeName}'s {inputName} couldn't be converted to {expectedType}."
|
||||
},
|
||||
"value_smaller_than_min": {
|
||||
"title": "Input out of range",
|
||||
"message": "Some input values are outside the allowed range.",
|
||||
"details": "{nodeName} has a value below the minimum for {inputName}.",
|
||||
"detailsWithValue": "The value {receivedValue} for {nodeName}'s {inputName} is below the minimum {minValue}.",
|
||||
"itemLabel": "{nodeName} - {inputName}",
|
||||
"toastTitle": "Input out of range",
|
||||
"toastMessage": "{nodeName} has a value below the minimum for {inputName}."
|
||||
"toastMessage": "{nodeName} has a value below the minimum for {inputName}.",
|
||||
"toastMessageWithValue": "The value {receivedValue} for {nodeName}'s {inputName} is below the minimum {minValue}."
|
||||
},
|
||||
"value_bigger_than_max": {
|
||||
"title": "Input out of range",
|
||||
"message": "Some input values are outside the allowed range.",
|
||||
"details": "{nodeName} has a value above the maximum for {inputName}.",
|
||||
"detailsWithValue": "The value {receivedValue} for {nodeName}'s {inputName} is above the maximum {maxValue}.",
|
||||
"itemLabel": "{nodeName} - {inputName}",
|
||||
"toastTitle": "Input out of range",
|
||||
"toastMessage": "{nodeName} has a value above the maximum for {inputName}."
|
||||
"toastMessage": "{nodeName} has a value above the maximum for {inputName}.",
|
||||
"toastMessageWithValue": "The value {receivedValue} for {nodeName}'s {inputName} is above the maximum {maxValue}."
|
||||
},
|
||||
"value_not_in_list": {
|
||||
"title": "Invalid input",
|
||||
"message": "Some input values are not available for this node.",
|
||||
"details": "{nodeName} has an unsupported value for {inputName}.",
|
||||
"detailsWithValue": "The value {receivedValue} for {nodeName}'s {inputName} is not available.",
|
||||
"itemLabel": "{nodeName} - {inputName}",
|
||||
"toastTitle": "Invalid input",
|
||||
"toastMessage": "{nodeName} has an unsupported value for {inputName}."
|
||||
"toastMessage": "{nodeName} has an unsupported value for {inputName}.",
|
||||
"toastMessageWithValue": "The value {receivedValue} for {nodeName}'s {inputName} is not available."
|
||||
},
|
||||
"custom_validation_failed": {
|
||||
"title": "Invalid input",
|
||||
"message": "A node rejected one or more input values.",
|
||||
"details": "{nodeName} rejected the value for {inputName}.",
|
||||
"detailsWithRawDetails": "{nodeName} failed custom validation: {rawDetails}",
|
||||
"itemLabel": "{nodeName} - {inputName}",
|
||||
"toastTitle": "Invalid input",
|
||||
"toastMessage": "{nodeName} rejected the value for {inputName}."
|
||||
@@ -3729,22 +3740,27 @@
|
||||
"title": "Validation failed",
|
||||
"message": "The workflow couldn't validate a connected node.",
|
||||
"details": "{nodeName} couldn't validate {inputName}.",
|
||||
"detailsWithRawDetails": "{nodeName} couldn't validate {inputName}: {rawDetails}",
|
||||
"itemLabel": "{nodeName} - {inputName}",
|
||||
"toastTitle": "Validation failed",
|
||||
"toastMessage": "{nodeName} couldn't validate {inputName}."
|
||||
"toastMessage": "{nodeName} couldn't validate {inputName}.",
|
||||
"toastMessageWithRawDetails": "{nodeName} couldn't validate {inputName}: {rawDetails}"
|
||||
},
|
||||
"exception_during_validation": {
|
||||
"title": "Validation failed",
|
||||
"message": "The node could not be validated.",
|
||||
"details": "{nodeName} could not be validated.",
|
||||
"message": "The workflow could not be validated because a node validation check failed unexpectedly.",
|
||||
"details": "{nodeName} failed during validation.",
|
||||
"detailsWithRawDetails": "{nodeName} failed during validation: {rawDetails}",
|
||||
"itemLabel": "{nodeName}",
|
||||
"toastTitle": "Validation failed",
|
||||
"toastMessage": "{nodeName} could not be validated."
|
||||
"toastMessage": "{nodeName} failed during validation.",
|
||||
"toastMessageWithRawDetails": "{nodeName} failed during validation: {rawDetails}"
|
||||
},
|
||||
"dependency_cycle": {
|
||||
"title": "Invalid workflow",
|
||||
"message": "The workflow has a circular node connection.",
|
||||
"details": "{nodeName} is part of a circular connection.",
|
||||
"detailsWithRawDetails": "{nodeName} is part of a circular connection: {rawDetails}",
|
||||
"itemLabel": "{nodeName}",
|
||||
"toastTitle": "Invalid workflow",
|
||||
"toastMessage": "{nodeName} is part of a circular connection."
|
||||
|
||||
@@ -11,17 +11,22 @@ import { i18n } from '@/i18n'
|
||||
function nodeValidationError(
|
||||
type: string,
|
||||
inputName?: string,
|
||||
details = inputName ?? ''
|
||||
details = inputName ?? '',
|
||||
extraInfo: Record<string, unknown> = {}
|
||||
): NodeValidationError {
|
||||
const extra_info =
|
||||
inputName || Object.keys(extraInfo).length > 0
|
||||
? {
|
||||
...(inputName ? { input_name: inputName } : {}),
|
||||
...extraInfo
|
||||
}
|
||||
: undefined
|
||||
|
||||
return {
|
||||
type,
|
||||
message: 'Validation failed',
|
||||
details,
|
||||
extra_info: inputName
|
||||
? {
|
||||
input_name: inputName
|
||||
}
|
||||
: undefined
|
||||
extra_info
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +180,160 @@ describe('errorMessageResolver', () => {
|
||||
).toEqual(expected)
|
||||
})
|
||||
|
||||
it('includes received values in validation range and option details', () => {
|
||||
expect(
|
||||
resolveRunErrorMessage({
|
||||
kind: 'node_validation',
|
||||
error: nodeValidationError(
|
||||
'return_type_mismatch',
|
||||
'images',
|
||||
'images, received_type(LATENT) mismatch input_type(IMAGE)',
|
||||
{
|
||||
input_config: ['IMAGE', {}],
|
||||
received_type: 'LATENT'
|
||||
}
|
||||
),
|
||||
nodeDisplayName: 'Preview Image'
|
||||
})
|
||||
).toMatchObject({
|
||||
displayDetails:
|
||||
"Preview Image's images input expects IMAGE, but the connected output is LATENT.",
|
||||
toastMessage:
|
||||
"Preview Image's images input expects IMAGE, but the connected output is LATENT."
|
||||
})
|
||||
|
||||
expect(
|
||||
resolveRunErrorMessage({
|
||||
kind: 'node_validation',
|
||||
error: nodeValidationError(
|
||||
'invalid_input_type',
|
||||
'steps',
|
||||
"steps, abc, invalid literal for int() with base 10: 'abc'",
|
||||
{
|
||||
input_config: ['INT', {}],
|
||||
received_value: 'abc'
|
||||
}
|
||||
),
|
||||
nodeDisplayName: 'KSampler'
|
||||
})
|
||||
).toMatchObject({
|
||||
displayDetails:
|
||||
"The value abc for KSampler's steps couldn't be converted to INT.",
|
||||
toastMessage:
|
||||
"The value abc for KSampler's steps couldn't be converted to INT."
|
||||
})
|
||||
|
||||
expect(
|
||||
resolveRunErrorMessage({
|
||||
kind: 'node_validation',
|
||||
error: nodeValidationError('value_smaller_than_min', 'steps', 'steps', {
|
||||
input_config: ['INT', { min: 1 }],
|
||||
received_value: 0
|
||||
}),
|
||||
nodeDisplayName: 'KSampler'
|
||||
})
|
||||
).toMatchObject({
|
||||
displayDetails:
|
||||
"The value 0 for KSampler's steps is below the minimum 1.",
|
||||
toastMessage: "The value 0 for KSampler's steps is below the minimum 1."
|
||||
})
|
||||
|
||||
expect(
|
||||
resolveRunErrorMessage({
|
||||
kind: 'node_validation',
|
||||
error: nodeValidationError('value_bigger_than_max', 'cfg', 'cfg', {
|
||||
input_config: ['FLOAT', { max: 30 }],
|
||||
received_value: 40
|
||||
}),
|
||||
nodeDisplayName: 'KSampler'
|
||||
})
|
||||
).toMatchObject({
|
||||
displayDetails:
|
||||
"The value 40 for KSampler's cfg is above the maximum 30.",
|
||||
toastMessage: "The value 40 for KSampler's cfg is above the maximum 30."
|
||||
})
|
||||
|
||||
expect(
|
||||
resolveRunErrorMessage({
|
||||
kind: 'node_validation',
|
||||
error: nodeValidationError(
|
||||
'value_not_in_list',
|
||||
'scheduler',
|
||||
'scheduler',
|
||||
{
|
||||
received_value: 'not-a-scheduler'
|
||||
}
|
||||
),
|
||||
nodeDisplayName: 'KSampler'
|
||||
})
|
||||
).toMatchObject({
|
||||
displayDetails:
|
||||
"The value not-a-scheduler for KSampler's scheduler is not available.",
|
||||
toastMessage:
|
||||
"The value not-a-scheduler for KSampler's scheduler is not available."
|
||||
})
|
||||
})
|
||||
|
||||
it('includes raw details when validation itself fails unexpectedly', () => {
|
||||
expect(
|
||||
resolveRunErrorMessage({
|
||||
kind: 'node_validation',
|
||||
error: nodeValidationError(
|
||||
'exception_during_inner_validation',
|
||||
'images',
|
||||
'list index out of range'
|
||||
),
|
||||
nodeDisplayName: 'Image Scale'
|
||||
})
|
||||
).toMatchObject({
|
||||
displayTitle: 'Validation failed',
|
||||
displayMessage: "The workflow couldn't validate a connected node.",
|
||||
displayDetails:
|
||||
"Image Scale couldn't validate images: list index out of range",
|
||||
displayItemLabel: 'Image Scale - images',
|
||||
toastTitle: 'Validation failed',
|
||||
toastMessage:
|
||||
"Image Scale couldn't validate images: list index out of range"
|
||||
})
|
||||
|
||||
expect(
|
||||
resolveRunErrorMessage({
|
||||
kind: 'node_validation',
|
||||
error: nodeValidationError(
|
||||
'exception_during_validation',
|
||||
undefined,
|
||||
'tuple index out of range'
|
||||
),
|
||||
nodeDisplayName: 'Preview Image'
|
||||
})
|
||||
).toMatchObject({
|
||||
displayTitle: 'Validation failed',
|
||||
displayMessage:
|
||||
'The workflow could not be validated because a node validation check failed unexpectedly.',
|
||||
displayDetails:
|
||||
'Preview Image failed during validation: tuple index out of range',
|
||||
displayItemLabel: 'Preview Image',
|
||||
toastTitle: 'Validation failed',
|
||||
toastMessage:
|
||||
'Preview Image failed during validation: tuple index out of range'
|
||||
})
|
||||
|
||||
expect(
|
||||
resolveRunErrorMessage({
|
||||
kind: 'node_validation',
|
||||
error: nodeValidationError(
|
||||
'exception_during_validation',
|
||||
undefined,
|
||||
''
|
||||
),
|
||||
nodeDisplayName: 'Preview Image'
|
||||
})
|
||||
).toMatchObject({
|
||||
displayDetails: 'Preview Image failed during validation.',
|
||||
toastMessage: 'Preview Image failed during validation.'
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves custom validation image failures as image-not-loaded copy', () => {
|
||||
expect(
|
||||
resolveRunErrorMessage({
|
||||
@@ -197,6 +356,51 @@ describe('errorMessageResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('includes raw details for generic custom validation failures', () => {
|
||||
expect(
|
||||
resolveRunErrorMessage({
|
||||
kind: 'node_validation',
|
||||
error: nodeValidationError(
|
||||
'custom_validation_failed',
|
||||
'setting',
|
||||
'setting - Unsupported lab value: bad-value'
|
||||
),
|
||||
nodeDisplayName: 'Custom Validation Error'
|
||||
})
|
||||
).toMatchObject({
|
||||
catalogId: 'custom_validation_failed',
|
||||
displayTitle: 'Invalid input',
|
||||
displayMessage: 'A node rejected one or more input values.',
|
||||
displayDetails:
|
||||
'Custom Validation Error failed custom validation: setting - Unsupported lab value: bad-value',
|
||||
displayItemLabel: 'Custom Validation Error - setting',
|
||||
toastTitle: 'Invalid input',
|
||||
toastMessage: 'Custom Validation Error rejected the value for setting.'
|
||||
})
|
||||
})
|
||||
|
||||
it('includes raw cycle paths for dependency cycle details', () => {
|
||||
expect(
|
||||
resolveRunErrorMessage({
|
||||
kind: 'node_validation',
|
||||
error: nodeValidationError(
|
||||
'dependency_cycle',
|
||||
undefined,
|
||||
'7 (ImageScale) -> 7 (ImageScale)'
|
||||
),
|
||||
nodeDisplayName: 'Image Scale'
|
||||
})
|
||||
).toMatchObject({
|
||||
displayTitle: 'Invalid workflow',
|
||||
displayMessage: 'The workflow has a circular node connection.',
|
||||
displayDetails:
|
||||
'Image Scale is part of a circular connection: 7 (ImageScale) to 7 (ImageScale)',
|
||||
displayItemLabel: 'Image Scale',
|
||||
toastTitle: 'Invalid workflow',
|
||||
toastMessage: 'Image Scale is part of a circular connection.'
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves known prompt errors with run error rules', () => {
|
||||
expect(
|
||||
resolveRunErrorMessage({
|
||||
|
||||
@@ -30,10 +30,12 @@ interface ErrorResolveContext {
|
||||
nodeDisplayName?: string
|
||||
}
|
||||
|
||||
type CatalogParams = Record<string, string | number>
|
||||
|
||||
function translateCatalogMessage(
|
||||
key: string,
|
||||
fallback: string,
|
||||
params?: Record<string, string | number>
|
||||
params?: CatalogParams
|
||||
): string {
|
||||
if (te(key)) return params ? t(key, params) : t(key)
|
||||
if (!params) return fallback
|
||||
@@ -46,7 +48,7 @@ function translateCatalogMessage(
|
||||
function translateOptionalCatalogMessage(
|
||||
key: string,
|
||||
fallback?: string,
|
||||
params?: Record<string, string | number>
|
||||
params?: CatalogParams
|
||||
): string | undefined {
|
||||
if (te(key)) return params ? t(key, params) : t(key)
|
||||
return fallback?.trim() ? fallback : undefined
|
||||
@@ -91,6 +93,190 @@ function nodeInputItemLabel(nodeName: string, inputName: string): string {
|
||||
return `${nodeName} - ${inputName}`
|
||||
}
|
||||
|
||||
function formatRawDetailsForCatalog(details: string): string {
|
||||
return details.replace(/\s*->\s*/g, ' to ')
|
||||
}
|
||||
|
||||
function formatCatalogValue(value: unknown): string | undefined {
|
||||
if (value === undefined || value === null) return undefined
|
||||
if (typeof value === 'string') return value
|
||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.stringify(value)
|
||||
} catch {
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
function getInputConfigValue(
|
||||
error: NodeValidationError,
|
||||
key: 'min' | 'max'
|
||||
): string | undefined {
|
||||
const inputConfig = error.extra_info?.input_config
|
||||
if (!Array.isArray(inputConfig)) return undefined
|
||||
|
||||
const config = inputConfig[1]
|
||||
if (!config || typeof config !== 'object') return undefined
|
||||
|
||||
return formatCatalogValue((config as Record<string, unknown>)[key])
|
||||
}
|
||||
|
||||
function getInputConfigType(error: NodeValidationError): string | undefined {
|
||||
const inputConfig = error.extra_info?.input_config
|
||||
if (!Array.isArray(inputConfig)) return undefined
|
||||
|
||||
return formatCatalogValue(inputConfig[0])
|
||||
}
|
||||
|
||||
function getValidationParams(
|
||||
error: NodeValidationError,
|
||||
nodeName: string,
|
||||
inputName: string
|
||||
): CatalogParams {
|
||||
const params: CatalogParams = { nodeName, inputName }
|
||||
const receivedValue = formatCatalogValue(error.extra_info?.received_value)
|
||||
const receivedType = formatCatalogValue(error.extra_info?.received_type)
|
||||
const expectedType = getInputConfigType(error)
|
||||
const minValue = getInputConfigValue(error, 'min')
|
||||
const maxValue = getInputConfigValue(error, 'max')
|
||||
|
||||
if (receivedValue !== undefined) params.receivedValue = receivedValue
|
||||
if (receivedType !== undefined) params.receivedType = receivedType
|
||||
if (expectedType !== undefined) params.expectedType = expectedType
|
||||
if (minValue !== undefined) params.minValue = minValue
|
||||
if (maxValue !== undefined) params.maxValue = maxValue
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
function hasParams(params: CatalogParams, keys: string[]): boolean {
|
||||
return keys.every((key) => params[key] !== undefined)
|
||||
}
|
||||
|
||||
function getValueSpecificCopyKeys(
|
||||
errorType: string,
|
||||
params: CatalogParams
|
||||
): {
|
||||
detailsKey: string
|
||||
toastMessageKey: string
|
||||
} {
|
||||
switch (errorType) {
|
||||
case 'return_type_mismatch':
|
||||
if (hasParams(params, ['expectedType', 'receivedType'])) {
|
||||
return {
|
||||
detailsKey: 'detailsWithTypes',
|
||||
toastMessageKey: 'toastMessageWithTypes'
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'invalid_input_type':
|
||||
if (hasParams(params, ['receivedValue', 'expectedType'])) {
|
||||
return {
|
||||
detailsKey: 'detailsWithValue',
|
||||
toastMessageKey: 'toastMessageWithValue'
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'value_smaller_than_min':
|
||||
if (hasParams(params, ['receivedValue', 'minValue'])) {
|
||||
return {
|
||||
detailsKey: 'detailsWithValue',
|
||||
toastMessageKey: 'toastMessageWithValue'
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'value_bigger_than_max':
|
||||
if (hasParams(params, ['receivedValue', 'maxValue'])) {
|
||||
return {
|
||||
detailsKey: 'detailsWithValue',
|
||||
toastMessageKey: 'toastMessageWithValue'
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'value_not_in_list':
|
||||
if (hasParams(params, ['receivedValue'])) {
|
||||
return {
|
||||
detailsKey: 'detailsWithValue',
|
||||
toastMessageKey: 'toastMessageWithValue'
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return {
|
||||
detailsKey: 'details',
|
||||
toastMessageKey: 'toastMessage'
|
||||
}
|
||||
}
|
||||
|
||||
function getRawDetailsCopyKeys(error: NodeValidationError): {
|
||||
detailsKey: string
|
||||
toastMessageKey: string
|
||||
} {
|
||||
return error.details.trim()
|
||||
? {
|
||||
detailsKey: 'detailsWithRawDetails',
|
||||
toastMessageKey: 'toastMessageWithRawDetails'
|
||||
}
|
||||
: {
|
||||
detailsKey: 'details',
|
||||
toastMessageKey: 'toastMessage'
|
||||
}
|
||||
}
|
||||
|
||||
function getExceptionDuringValidationCopyKeys(error: NodeValidationError): {
|
||||
detailsKey: string
|
||||
toastMessageKey: string
|
||||
} {
|
||||
return getRawDetailsCopyKeys(error)
|
||||
}
|
||||
|
||||
function getRawDetailsOnlyCopyKeys(error: NodeValidationError): {
|
||||
detailsKey: string
|
||||
toastMessageKey: string
|
||||
} {
|
||||
if (!error.details.trim()) {
|
||||
return {
|
||||
detailsKey: 'details',
|
||||
toastMessageKey: 'toastMessage'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
detailsKey: 'detailsWithRawDetails',
|
||||
toastMessageKey: 'toastMessage'
|
||||
}
|
||||
}
|
||||
|
||||
function getValidationCopyKeys(
|
||||
error: NodeValidationError,
|
||||
params: CatalogParams
|
||||
): {
|
||||
detailsKey: string
|
||||
toastMessageKey: string
|
||||
} {
|
||||
if (error.type === 'exception_during_validation') {
|
||||
return getExceptionDuringValidationCopyKeys(error)
|
||||
}
|
||||
|
||||
if (error.type === 'exception_during_inner_validation') {
|
||||
return getRawDetailsCopyKeys(error)
|
||||
}
|
||||
|
||||
if (error.type === 'custom_validation_failed') {
|
||||
return getRawDetailsOnlyCopyKeys(error)
|
||||
}
|
||||
|
||||
if (error.type === 'dependency_cycle') {
|
||||
return getRawDetailsOnlyCopyKeys(error)
|
||||
}
|
||||
|
||||
return getValueSpecificCopyKeys(error.type, params)
|
||||
}
|
||||
|
||||
const VALIDATION_ERROR_RULES: Record<string, ValidationCatalogRule> = {
|
||||
[REQUIRED_INPUT_MISSING_TYPE]: {
|
||||
catalogId: REQUIRED_INPUT_MISSING_CATALOG_ID,
|
||||
@@ -162,13 +348,24 @@ function resolveValidationCatalogCopy(
|
||||
): ResolvedErrorMessage {
|
||||
const nodeName = normalizeNodeName(context.nodeDisplayName)
|
||||
const inputName = getInputName(error)
|
||||
const params = { nodeName, inputName }
|
||||
const rawDetails = formatRawDetailsForCatalog(error.details.trim())
|
||||
const params = {
|
||||
...getValidationParams(error, nodeName, inputName),
|
||||
rawDetails
|
||||
}
|
||||
const keyPrefix = `errorCatalog.validationErrors.${rule.key}`
|
||||
const titleFallback = error.type || error.message
|
||||
const itemLabelFallback =
|
||||
rule.itemLabel === 'node'
|
||||
? nodeName
|
||||
: nodeInputItemLabel(nodeName, inputName)
|
||||
const copyKeys =
|
||||
rule.key === 'image_not_loaded'
|
||||
? {
|
||||
detailsKey: 'details',
|
||||
toastMessageKey: 'toastMessage'
|
||||
}
|
||||
: getValidationCopyKeys(error, params)
|
||||
|
||||
return {
|
||||
catalogId: rule.catalogId,
|
||||
@@ -183,7 +380,7 @@ function resolveValidationCatalogCopy(
|
||||
params
|
||||
),
|
||||
displayDetails: translateOptionalCatalogMessage(
|
||||
`${keyPrefix}.details`,
|
||||
`${keyPrefix}.${copyKeys.detailsKey}`,
|
||||
error.details,
|
||||
params
|
||||
),
|
||||
@@ -198,7 +395,7 @@ function resolveValidationCatalogCopy(
|
||||
params
|
||||
),
|
||||
toastMessage: translateCatalogMessage(
|
||||
`${keyPrefix}.toastMessage`,
|
||||
`${keyPrefix}.${copyKeys.toastMessageKey}`,
|
||||
error.message,
|
||||
params
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user