feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { describe, test, expect, vi } from 'vitest';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { codeEmptyValidator } from '../code-empty-validator';
|
||||
|
||||
// 模拟I18n.t方法
|
||||
vi.mock('@coze-arch/i18n', () => ({
|
||||
I18n: { t: vi.fn(key => `translated_${key}`) },
|
||||
}));
|
||||
|
||||
describe('codeEmptyValidator', () => {
|
||||
test('当value.code存在时返回true', () => {
|
||||
const result = codeEmptyValidator({ value: { code: 'some code' } });
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test('当value.code不存在时返回翻译后的错误信息', () => {
|
||||
const result = codeEmptyValidator({ value: {} });
|
||||
expect(I18n.t).toHaveBeenCalledWith('workflow_running_results_error_code');
|
||||
expect(result).toBe('translated_workflow_running_results_error_code');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { type FormItemMaterialContext } from '@flowgram-adapter/free-layout-editor';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { nodeMetaValidator } from '../node-meta-validator';
|
||||
|
||||
// 模拟 I18n.t 方法
|
||||
vi.mock('@coze-arch/i18n', () => ({
|
||||
I18n: { t: vi.fn(key => `translated_${key}`) },
|
||||
}));
|
||||
|
||||
const mockNodesService = {
|
||||
getAllNodes: vi.fn(),
|
||||
getNodeTitle: vi.fn(node => node.title),
|
||||
};
|
||||
|
||||
const baseContext = {
|
||||
playgroundContext: {
|
||||
nodesService: mockNodesService,
|
||||
},
|
||||
} as FormItemMaterialContext;
|
||||
|
||||
describe('nodeMetaValidator', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return true for valid metadata with a unique title', () => {
|
||||
mockNodesService.getAllNodes.mockReturnValue([
|
||||
{ id: 'node2', title: 'AnotherNode' },
|
||||
]);
|
||||
const result = nodeMetaValidator({
|
||||
value: { title: 'ValidTitle' },
|
||||
context: baseContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return an error string for an empty title', () => {
|
||||
// Ensure isTitleRepeated returns false to isolate schema validation
|
||||
mockNodesService.getAllNodes.mockReturnValue([]);
|
||||
const result = nodeMetaValidator({
|
||||
value: { title: '' },
|
||||
context: baseContext,
|
||||
options: {},
|
||||
});
|
||||
// The I18n.t mock for 'workflow_detail_node_name_error_empty' is associated with the .string({...}) definition.
|
||||
// This key is used when Zod creates the error message for the .min(1) rule.
|
||||
// The I18n.t call for 'workflow_node_title_duplicated' happens inside nodeMetaValidator
|
||||
// when the refine schema is built.
|
||||
expect(I18n.t).toHaveBeenCalledWith('workflow_node_title_duplicated');
|
||||
const parsedResult = JSON.parse(result as string);
|
||||
expect(parsedResult.issues[0].message).toBe(
|
||||
I18n.t('workflow_detail_node_name_error_empty'),
|
||||
);
|
||||
expect(parsedResult.issues[0].path).toEqual(['title']);
|
||||
});
|
||||
|
||||
it('should return an error string for a title exceeding max length', () => {
|
||||
// Ensure isTitleRepeated returns false to isolate schema validation
|
||||
mockNodesService.getAllNodes.mockReturnValue([]);
|
||||
const longTitle = 'a'.repeat(64);
|
||||
const result = nodeMetaValidator({
|
||||
value: { title: longTitle },
|
||||
context: baseContext,
|
||||
options: {},
|
||||
});
|
||||
// The I18n.t mock for 'workflow_derail_node_detail_title_max' is associated with the .regex({...}) definition.
|
||||
// This key is used when Zod creates the error message for the regex rule.
|
||||
// The I18n.t call for 'workflow_node_title_duplicated' happens inside nodeMetaValidator
|
||||
// when the refine schema is built.
|
||||
expect(I18n.t).toHaveBeenCalledWith('workflow_node_title_duplicated');
|
||||
const parsedResult = JSON.parse(result as string);
|
||||
expect(parsedResult.issues[0].message).toBe(
|
||||
I18n.t('workflow_derail_node_detail_title_max', { max: '63' }),
|
||||
);
|
||||
expect(parsedResult.issues[0].path).toEqual(['title']);
|
||||
});
|
||||
|
||||
it('should return an error string for a duplicated title', () => {
|
||||
mockNodesService.getAllNodes.mockReturnValue([
|
||||
{ id: 'node1', title: 'ExistingTitle' },
|
||||
{ id: 'node2', title: 'AnotherNode' },
|
||||
{ id: 'node2', title: 'ExistingTitle' }, // 这里模拟一个重复的标题
|
||||
]);
|
||||
const result = nodeMetaValidator({
|
||||
value: { title: 'ExistingTitle' },
|
||||
context: baseContext,
|
||||
options: {},
|
||||
});
|
||||
// The validator returns a stringified Zod error object when parsed.success is false.
|
||||
// The 'workflow_node_title_duplicated' message comes from the refine function.
|
||||
const parsedResult = JSON.parse(result as string);
|
||||
expect(parsedResult.issues[0].message).toBe(
|
||||
I18n.t('workflow_node_title_duplicated'),
|
||||
);
|
||||
expect(parsedResult.issues[0].path).toEqual(['title']);
|
||||
// Ensure I18n.t was called for the duplication message within the refine logic.
|
||||
expect(I18n.t).toHaveBeenCalledWith('workflow_node_title_duplicated');
|
||||
});
|
||||
|
||||
it('should return true for valid metadata with optional fields', () => {
|
||||
mockNodesService.getAllNodes.mockReturnValue([]);
|
||||
const result = nodeMetaValidator({
|
||||
value: {
|
||||
title: 'ValidTitleWithExtras',
|
||||
icon: 'icon.png',
|
||||
subtitle: 'A subtitle',
|
||||
description: 'A description',
|
||||
},
|
||||
context: baseContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if title is empty when checking for duplicates (isTitleRepeated returns false)', () => {
|
||||
// isTitleRepeated returns false if title is empty, so it should pass this specific check
|
||||
// but it will fail the .min(1) check from Zod schema
|
||||
mockNodesService.getAllNodes.mockReturnValue([
|
||||
{ id: 'node1', title: 'ExistingTitle' },
|
||||
]);
|
||||
const result = nodeMetaValidator({
|
||||
value: { title: '' }, // Empty title
|
||||
context: baseContext,
|
||||
options: {},
|
||||
});
|
||||
// This will fail the zod schema's .min(1) check first
|
||||
const parsedResult = JSON.parse(result as string);
|
||||
expect(parsedResult.issues[0].message).toBe(
|
||||
'translated_workflow_detail_node_name_error_empty',
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly handle nodesService.getNodeTitle returning different structure if applicable', () => {
|
||||
// This test is to ensure getNodeTitle mock is robust or to highlight if it needs adjustment
|
||||
// For example, if getNodeTitle actually expects a more complex node object
|
||||
const mockComplexNode = { id: 'nodeC', data: { name: 'ComplexNodeTitle' } };
|
||||
mockNodesService.getAllNodes.mockReturnValue([mockComplexNode]);
|
||||
// Adjusting the mock for getNodeTitle if it's more complex than just node.title
|
||||
const originalGetNodeTitle = mockNodesService.getNodeTitle;
|
||||
mockNodesService.getNodeTitle = vi.fn(node => node.data.name);
|
||||
|
||||
const result = nodeMetaValidator({
|
||||
value: { title: 'TestComplex' },
|
||||
context: baseContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
|
||||
// Restore original mock
|
||||
mockNodesService.getNodeTitle = originalGetNodeTitle;
|
||||
});
|
||||
|
||||
it('should return true when title is not duplicated and nodes exist', () => {
|
||||
mockNodesService.getAllNodes.mockReturnValue([
|
||||
{ id: 'node1', title: 'AnotherTitle1' },
|
||||
{ id: 'node2', title: 'AnotherTitle2' },
|
||||
]);
|
||||
const result = nodeMetaValidator({
|
||||
value: { title: 'UniqueTitle' },
|
||||
context: baseContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle when getAllNodes returns an empty array (no nodes)', () => {
|
||||
mockNodesService.getAllNodes.mockReturnValue([]);
|
||||
const result = nodeMetaValidator({
|
||||
value: { title: 'FirstNodeTitle' },
|
||||
context: baseContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ZodIssueCode } from 'zod';
|
||||
import { describe, it, vi, expect } from 'vitest';
|
||||
|
||||
import { questionOptionValidator } from '../question-option-validator';
|
||||
|
||||
// 模拟 I18n.t 方法
|
||||
vi.mock('@coze-arch/i18n', () => ({
|
||||
I18n: { t: vi.fn(key => `translated_${key}`) },
|
||||
}));
|
||||
|
||||
describe('questionOptionValidator', () => {
|
||||
it('should return true for a valid non-empty unique array', () => {
|
||||
const value = [
|
||||
{ name: 'Option 1', id: '1' },
|
||||
{ name: 'Option 2', id: '2' },
|
||||
];
|
||||
expect(
|
||||
questionOptionValidator({ value, context: {} as any, options: {} }),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for an empty array', () => {
|
||||
const value: Array<{ name?: string; id: string }> = [];
|
||||
expect(
|
||||
questionOptionValidator({ value, context: {} as any, options: {} }),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return error JSON for array with empty name', () => {
|
||||
const value = [
|
||||
{ name: 'Option 1', id: '1' },
|
||||
{ name: '', id: '2' },
|
||||
];
|
||||
const result = questionOptionValidator({
|
||||
value,
|
||||
context: {} as any,
|
||||
options: {},
|
||||
});
|
||||
expect(typeof result).toBe('string');
|
||||
const errors = JSON.parse(result as string);
|
||||
expect(errors.issues).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
code: ZodIssueCode.custom,
|
||||
message: 'translated_workflow_ques_option_notempty',
|
||||
path: [1],
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error JSON for array with whitespace name', () => {
|
||||
const value = [
|
||||
{ name: 'Option 1', id: '1' },
|
||||
{ name: ' ', id: '2' },
|
||||
];
|
||||
const result = questionOptionValidator({
|
||||
value,
|
||||
context: {} as any,
|
||||
options: {},
|
||||
});
|
||||
expect(typeof result).toBe('string');
|
||||
const errors = JSON.parse(result as string);
|
||||
expect(errors.issues).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
code: ZodIssueCode.custom,
|
||||
message: 'translated_workflow_ques_option_notempty',
|
||||
path: [1],
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error JSON for array with duplicate names', () => {
|
||||
const value = [
|
||||
{ name: 'Option 1', id: '1' },
|
||||
{ name: 'Option 1', id: '2' },
|
||||
];
|
||||
const result = questionOptionValidator({
|
||||
value,
|
||||
context: {} as any,
|
||||
options: {},
|
||||
});
|
||||
expect(typeof result).toBe('string');
|
||||
const errors = JSON.parse(result as string);
|
||||
expect(errors.issues).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
code: ZodIssueCode.custom,
|
||||
message: 'translated_workflow_ques_ans_testrun_dulpicate',
|
||||
path: [1],
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error JSON for array with both empty and duplicate names', () => {
|
||||
const value = [
|
||||
{ name: 'Option 1', id: '1' },
|
||||
{ name: '', id: '2' },
|
||||
{ name: 'Option 1', id: '3' },
|
||||
];
|
||||
const result = questionOptionValidator({
|
||||
value,
|
||||
context: {} as any,
|
||||
options: {},
|
||||
});
|
||||
expect(typeof result).toBe('string');
|
||||
const errors = JSON.parse(result as string);
|
||||
expect(errors.issues).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
code: ZodIssueCode.custom,
|
||||
message: 'translated_workflow_ques_option_notempty',
|
||||
path: [1],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
code: ZodIssueCode.custom,
|
||||
message: 'translated_workflow_ques_ans_testrun_dulpicate',
|
||||
path: [2],
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error JSON when value is undefined', () => {
|
||||
const value = undefined;
|
||||
const result = questionOptionValidator({
|
||||
value: value as any,
|
||||
context: {} as any,
|
||||
options: {},
|
||||
}); // Cast to any to bypass TS check for test
|
||||
expect(typeof result).toBe('string');
|
||||
const errors = JSON.parse(result as string);
|
||||
// Zod will throw a different type of error for undefined input on a non-optional schema
|
||||
expect(errors.issues).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
code: ZodIssueCode.invalid_type,
|
||||
expected: 'array',
|
||||
received: 'undefined',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error JSON when value is null', () => {
|
||||
const value = null;
|
||||
const result = questionOptionValidator({
|
||||
value: value as any,
|
||||
context: {} as any,
|
||||
options: {},
|
||||
}); // Cast to any to bypass TS check for test
|
||||
expect(typeof result).toBe('string');
|
||||
const errors = JSON.parse(result as string);
|
||||
expect(errors.issues).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
code: ZodIssueCode.invalid_type,
|
||||
expected: 'array',
|
||||
received: 'null',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error JSON for array with name missing (undefined)', () => {
|
||||
const value = [{ id: '1' }, { name: 'Option 2', id: '2' }]; // name is undefined for the first item
|
||||
const result = questionOptionValidator({
|
||||
value: value as any,
|
||||
context: {} as any,
|
||||
options: {},
|
||||
});
|
||||
expect(typeof result).toBe('string');
|
||||
const errors = JSON.parse(result as string);
|
||||
expect(errors.issues).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
code: ZodIssueCode.invalid_type, // Zod expects a string for name
|
||||
expected: 'string',
|
||||
received: 'undefined',
|
||||
path: [0, 'name'],
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
import { settingOnErrorValidator } from '../setting-on-error-validator';
|
||||
|
||||
vi.mock('@flowgram-adapter/free-layout-editor', () => ({}));
|
||||
|
||||
vi.mock('../setting-on-error', () => ({
|
||||
SettingOnErrorProcessType: {
|
||||
STOP: 1,
|
||||
RETURN: 2,
|
||||
BACKUP: 3,
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock I18n.t
|
||||
vi.mock('@coze-arch/i18n', () => ({
|
||||
I18n: {
|
||||
t: vi.fn(key => key),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('settingOnErrorValidator', () => {
|
||||
it('should return true if value is undefined', () => {
|
||||
expect(settingOnErrorValidator({ value: undefined } as any)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if value is null', () => {
|
||||
expect(settingOnErrorValidator({ value: null } as any)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if settingOnErrorIsOpen is false', () => {
|
||||
const value = {
|
||||
settingOnErrorIsOpen: false,
|
||||
settingOnErrorJSON: '{"key":"value"}',
|
||||
processType: 1,
|
||||
};
|
||||
expect(settingOnErrorValidator({ value } as any)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if settingOnErrorIsOpen is not presented', () => {
|
||||
const value = {
|
||||
processType: 1,
|
||||
timeoutMs: 180000,
|
||||
retryTimes: 0,
|
||||
};
|
||||
expect(settingOnErrorValidator({ value } as any)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if settingOnErrorIsOpen is not presented, and selected a backup model', () => {
|
||||
const value = {
|
||||
processType: 1,
|
||||
timeoutMs: 180000,
|
||||
retryTimes: 1,
|
||||
ext: {
|
||||
backupLLmParam: {
|
||||
temperature: '1',
|
||||
maxTokens: '2200',
|
||||
responseFormat: 2,
|
||||
modelName: 'DeepSeek-R1/250528',
|
||||
modelType: 1748588801,
|
||||
generationDiversity: 'default_val',
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(settingOnErrorValidator({ value } as any)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return error string if settingOnErrorIsOpen is true, processType is RETURN, and settingOnErrorJSON is invalid JSON', () => {
|
||||
const value = {
|
||||
settingOnErrorIsOpen: true,
|
||||
settingOnErrorJSON: 'invalid-json',
|
||||
processType: 2,
|
||||
timeoutMs: 180000,
|
||||
retryTimes: 1,
|
||||
ext: {
|
||||
backupLLmParam: {
|
||||
temperature: '1',
|
||||
maxTokens: '2200',
|
||||
responseFormat: 2,
|
||||
modelName: 'DeepSeek-R1/250528',
|
||||
modelType: 1748588801,
|
||||
generationDiversity: 'default_val',
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = settingOnErrorValidator({ value } as any);
|
||||
expect(result).toBeTypeOf('string');
|
||||
if (typeof result === 'string') {
|
||||
const parsedResult = JSON.parse(result);
|
||||
expect(parsedResult.issues[0].message).toBe(
|
||||
'workflow_exception_ignore_json_error',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return error string if settingOnErrorIsOpen is true, processType is RETURN, and settingOnErrorJSON is valid JSON', () => {
|
||||
const value = {
|
||||
settingOnErrorIsOpen: true,
|
||||
settingOnErrorJSON: '{\n "output": "hello"\n}',
|
||||
processType: 2,
|
||||
timeoutMs: 180000,
|
||||
retryTimes: 1,
|
||||
ext: {
|
||||
backupLLmParam: {
|
||||
temperature: '1',
|
||||
maxTokens: '2200',
|
||||
responseFormat: 2,
|
||||
modelName: 'DeepSeek-R1/250528',
|
||||
modelType: 1748588801,
|
||||
generationDiversity: 'default_val',
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = settingOnErrorValidator({ value } as any);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { systemVariableValidator } from '../system-variable-validator';
|
||||
|
||||
// Mock I18n
|
||||
vi.mock('@coze-arch/i18n', () => ({
|
||||
I18n: {
|
||||
t: vi.fn(key => {
|
||||
if (key === 'variable_240416_01') {
|
||||
return 'System variables cannot start with sys_';
|
||||
}
|
||||
return `translated_${key}`;
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
const mockContext = {} as any; // ValidatorProps context is not used by this validator
|
||||
|
||||
describe('systemVariableValidator', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return true for a valid variable name', () => {
|
||||
const result = systemVariableValidator({
|
||||
value: 'my_variable',
|
||||
context: mockContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for a variable name with leading/trailing spaces that is otherwise valid', () => {
|
||||
const result = systemVariableValidator({
|
||||
value: ' my_variable ',
|
||||
context: mockContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return an error message for a variable name starting with "sys_"', () => {
|
||||
const result = systemVariableValidator({
|
||||
value: 'sys_variable',
|
||||
context: mockContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe('System variables cannot start with sys_');
|
||||
expect(I18n.t).toHaveBeenCalledWith('variable_240416_01');
|
||||
});
|
||||
|
||||
it('should return an error message for a variable name starting with "sys_" even with leading/trailing spaces', () => {
|
||||
const result = systemVariableValidator({
|
||||
value: ' sys_variable ',
|
||||
context: mockContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe('System variables cannot start with sys_');
|
||||
expect(I18n.t).toHaveBeenCalledWith('variable_240416_01');
|
||||
});
|
||||
|
||||
it('should return true for an empty string after trimming', () => {
|
||||
const result = systemVariableValidator({
|
||||
value: ' ',
|
||||
context: mockContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for an empty string input', () => {
|
||||
const result = systemVariableValidator({
|
||||
value: '',
|
||||
context: mockContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for a null input value', () => {
|
||||
const result = systemVariableValidator({
|
||||
value: null as any,
|
||||
context: mockContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for an undefined input value', () => {
|
||||
const result = systemVariableValidator({
|
||||
value: undefined as any,
|
||||
context: mockContext,
|
||||
options: {},
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
export function codeEmptyValidator({ value }) {
|
||||
const code = value?.code;
|
||||
if (!code) {
|
||||
return I18n.t('workflow_running_results_error_code');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
23
frontend/packages/workflow/nodes/src/validators/index.ts
Normal file
23
frontend/packages/workflow/nodes/src/validators/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { nodeMetaValidator } from './node-meta-validator';
|
||||
export { outputTreeValidator } from './output-tree-validator';
|
||||
export { systemVariableValidator } from './system-variable-validator';
|
||||
export { codeEmptyValidator } from './code-empty-validator';
|
||||
export { questionOptionValidator } from './question-option-validator';
|
||||
export { settingOnErrorValidator } from './setting-on-error-validator';
|
||||
export { inputTreeValidator } from './input-tree-validator';
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { z, type ZodSchema } from 'zod';
|
||||
import {
|
||||
ValueExpression,
|
||||
ValueExpressionType,
|
||||
type InputValueVO,
|
||||
} from '@coze-workflow/base/types';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type ValidatorProps } from '@flowgram-adapter/free-layout-editor';
|
||||
import { type FlowNodeEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
import { type PlaygroundContext } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { VARIABLE_NAME_REGEX } from '../constants';
|
||||
|
||||
type Path = string | number;
|
||||
|
||||
interface Issue {
|
||||
path: Path[];
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入树校验器
|
||||
*/
|
||||
export class InputTreeValidator {
|
||||
private node: FlowNodeEntity;
|
||||
private playgroundContext: PlaygroundContext;
|
||||
private issues: Issue[] = [];
|
||||
constructor(node: FlowNodeEntity, playgroundContext: PlaygroundContext) {
|
||||
this.node = node;
|
||||
this.playgroundContext = playgroundContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验函数
|
||||
* @param inputalues
|
||||
* @reurns
|
||||
*/
|
||||
validate(inputValues: InputValueVO[]): Issue[] {
|
||||
this.issues = [];
|
||||
this.validateInputValues(inputValues);
|
||||
return this.issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验多个输入
|
||||
* @param inputValues
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
private validateInputValues(inputValues: InputValueVO[], path: Path[] = []) {
|
||||
if (!inputValues) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < inputValues.length; i++) {
|
||||
const inputValue = inputValues[i] || {};
|
||||
const rules = {
|
||||
name: this.validateName,
|
||||
input: this.validateInput,
|
||||
};
|
||||
|
||||
Object.keys(rules).forEach(key => {
|
||||
const message = rules[key].bind(this)({
|
||||
value: inputValue[key],
|
||||
values: inputValues,
|
||||
});
|
||||
|
||||
if (message) {
|
||||
this.issues.push({
|
||||
message,
|
||||
path: path.concat(i, key),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const children = inputValues[i]?.children || [];
|
||||
// 递归检查子节点
|
||||
this.validateInputValues(children, path.concat(i, 'children'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入名称校验
|
||||
*/
|
||||
private validateName({ value, values }) {
|
||||
if (!value) {
|
||||
return I18n.t('workflow_detail_node_error_name_empty');
|
||||
}
|
||||
|
||||
const names = values.map(v => v.name).filter(Boolean);
|
||||
// 名称格式校验
|
||||
if (!VARIABLE_NAME_REGEX.test(value)) {
|
||||
return I18n.t('workflow_detail_node_error_format');
|
||||
}
|
||||
|
||||
// 重名校验
|
||||
const foundSames = names.filter((name: string) => name === value);
|
||||
|
||||
return foundSames.length > 1
|
||||
? I18n.t('workflow_detail_node_input_duplicated')
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入值校验
|
||||
*/
|
||||
private validateInput({ value }) {
|
||||
const { variableValidationService } = this.playgroundContext;
|
||||
|
||||
// 校验空值
|
||||
if (ValueExpression.isEmpty(value)) {
|
||||
return I18n.t('workflow_detail_node_error_empty');
|
||||
}
|
||||
|
||||
if (value?.type === ValueExpressionType.REF) {
|
||||
return variableValidationService.isRefVariableEligible(value, this.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function inputTreeValidator(params: ValidatorProps<InputValueVO>) {
|
||||
const {
|
||||
value,
|
||||
context: { playgroundContext, node },
|
||||
} = params;
|
||||
const validator = new InputTreeValidator(node, playgroundContext);
|
||||
|
||||
const InputTreeNodeSchema: ZodSchema<any> = z.lazy(() =>
|
||||
z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
input: z.any(),
|
||||
children: z.array(InputTreeNodeSchema).optional(),
|
||||
})
|
||||
.passthrough(),
|
||||
);
|
||||
|
||||
const InputTreeSchema = z
|
||||
.array(InputTreeNodeSchema)
|
||||
.superRefine((data, ctx) => {
|
||||
const issues = validator.validate(data);
|
||||
|
||||
issues.forEach(issue => {
|
||||
ctx.addIssue({
|
||||
path: issue.path,
|
||||
message: issue.message,
|
||||
// FIXME: 表单校验底层依赖了 validation / code,去掉就跑不通了
|
||||
validation: 'regex',
|
||||
code: 'invalid_string',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const parsed = InputTreeSchema.safeParse(value);
|
||||
|
||||
if (!parsed.success) {
|
||||
return JSON.stringify((parsed as any).error);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Ajv from 'ajv';
|
||||
import {
|
||||
variableUtils,
|
||||
generateInputJsonSchema,
|
||||
} from '@coze-workflow/variable';
|
||||
import { type ViewVariableMeta } from '@coze-workflow/base';
|
||||
let ajv;
|
||||
export const jsonSchemaValidator = (
|
||||
v: string,
|
||||
viewVariableMeta: ViewVariableMeta,
|
||||
): boolean => {
|
||||
if (!viewVariableMeta || !v) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const dtoMeta = variableUtils.viewMetaToDTOMeta(viewVariableMeta);
|
||||
const jsonSchema = generateInputJsonSchema(dtoMeta);
|
||||
if (!jsonSchema) {
|
||||
return true;
|
||||
}
|
||||
if (!ajv) {
|
||||
ajv = new Ajv();
|
||||
}
|
||||
try {
|
||||
const validate = ajv.compile(jsonSchema);
|
||||
const valid = validate(JSON.parse(v));
|
||||
return valid;
|
||||
// eslint-disable-next-line @coze-arch/use-error-in-catch
|
||||
} catch (error) {
|
||||
// parse失败说明不是合法值
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type ValidatorProps } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
const NodeMetaSchema = z.object({
|
||||
title: z
|
||||
.string({
|
||||
required_error: I18n.t('workflow_detail_node_name_error_empty'),
|
||||
})
|
||||
.min(1, I18n.t('workflow_detail_node_name_error_empty'))
|
||||
// .regex(
|
||||
// /^[a-zA-Z][a-zA-Z0-9_-]{0,}$/,
|
||||
// I18n.t('workflow_detail_node_error_format'),
|
||||
// )
|
||||
.regex(
|
||||
/^.{0,63}$/,
|
||||
I18n.t('workflow_derail_node_detail_title_max', {
|
||||
max: '63',
|
||||
}),
|
||||
),
|
||||
icon: z.string().optional(),
|
||||
subtitle: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
type NodeMeta = z.infer<typeof NodeMetaSchema>;
|
||||
|
||||
export const nodeMetaValidator = ({
|
||||
value,
|
||||
context,
|
||||
}: ValidatorProps<NodeMeta>) => {
|
||||
const { playgroundContext } = context;
|
||||
function isTitleRepeated(title: string) {
|
||||
if (!title) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { nodesService } = playgroundContext;
|
||||
const nodes = nodesService
|
||||
.getAllNodes()
|
||||
.filter(node => nodesService.getNodeTitle(node) === title);
|
||||
|
||||
return nodes?.length > 1;
|
||||
}
|
||||
|
||||
// 增加节点名重复校验
|
||||
const schema = NodeMetaSchema.refine(
|
||||
({ title }: NodeMeta) => !isTitleRepeated(title),
|
||||
{
|
||||
message: I18n.t('workflow_node_title_duplicated'),
|
||||
path: ['title'],
|
||||
},
|
||||
);
|
||||
const parsed = schema.safeParse(value);
|
||||
|
||||
if (!parsed.success) {
|
||||
return JSON.stringify((parsed as any).error);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type ValidatorProps } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import {
|
||||
OutputTreeSchema,
|
||||
OutputTreeUniqueNameSchema,
|
||||
type OutputTree,
|
||||
} from './schema';
|
||||
|
||||
export function outputTreeValidator(
|
||||
params: ValidatorProps<
|
||||
OutputTree,
|
||||
{
|
||||
uniqueName?: boolean;
|
||||
}
|
||||
>,
|
||||
) {
|
||||
const { value, options } = params;
|
||||
const { uniqueName = false } = options;
|
||||
const parsed = uniqueName
|
||||
? OutputTreeUniqueNameSchema.safeParse(value)
|
||||
: OutputTreeSchema.safeParse(value);
|
||||
|
||||
if (!parsed.success) {
|
||||
return JSON.stringify((parsed as any).error);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { z, type ZodSchema } from 'zod';
|
||||
import { ViewVariableType } from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { jsonSchemaValidator } from '../json-schema-validator';
|
||||
// 定义节点Schema
|
||||
const OutputTreeNodeSchema: ZodSchema<any> = z.lazy(() =>
|
||||
z
|
||||
.object({
|
||||
name: z
|
||||
.string({
|
||||
required_error: I18n.t('workflow_detail_node_error_name_empty'),
|
||||
})
|
||||
.min(1, I18n.t('workflow_detail_node_error_name_empty'))
|
||||
.regex(
|
||||
/^(?!.*\b(true|false|and|AND|or|OR|not|NOT|null|nil|If|Switch)\b)[a-zA-Z_][a-zA-Z_$0-9]*$/,
|
||||
I18n.t('workflow_detail_node_error_format'),
|
||||
),
|
||||
type: z.number(),
|
||||
children: z.array(OutputTreeNodeSchema).optional(),
|
||||
defaultValue: z.any().optional(),
|
||||
})
|
||||
.passthrough(),
|
||||
);
|
||||
|
||||
export const OutputTreeSchema = z.array(OutputTreeNodeSchema);
|
||||
|
||||
// 定义一个辅助函数,用于查找重复名字的节点并返回错误路径
|
||||
const findDuplicates = (nodes, path = []) => {
|
||||
const seen = new Set();
|
||||
let result: {
|
||||
path: (string | number)[];
|
||||
message: string;
|
||||
};
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const { name } = nodes[i];
|
||||
if (seen.has(name)) {
|
||||
// 找到重复项时返回路径和错误信息
|
||||
result = {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
path: path.concat(i, 'name'),
|
||||
message: I18n.t('workflow_detail_node_error_variablename_duplicated'),
|
||||
};
|
||||
break;
|
||||
}
|
||||
seen.add(name);
|
||||
if (nodes[i].children) {
|
||||
// 递归检查子节点
|
||||
const found = findDuplicates(
|
||||
nodes[i].children,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
path.concat(i, 'children'),
|
||||
);
|
||||
if (found) {
|
||||
result = found;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
return result;
|
||||
};
|
||||
|
||||
// 定义同级命名唯一的树结构Schema
|
||||
export const OutputTreeUniqueNameSchema = z
|
||||
.array(OutputTreeNodeSchema)
|
||||
.refine(
|
||||
data => {
|
||||
// 使用自定义函数进行检查
|
||||
const duplicate = findDuplicates(data);
|
||||
return !duplicate;
|
||||
},
|
||||
data => {
|
||||
// 使用自定义函数进行检查
|
||||
const duplicate = findDuplicates(data);
|
||||
return {
|
||||
path: duplicate.path,
|
||||
message: duplicate.message,
|
||||
// FIXME: 表单校验底层依赖了 validation / code,去掉就跑不通了
|
||||
validation: 'regex',
|
||||
code: 'invalid_string',
|
||||
};
|
||||
},
|
||||
)
|
||||
.superRefine((data, ctx) => {
|
||||
// 使用自定义函数进行检查
|
||||
const issues = checkObjectDefaultValue(data);
|
||||
issues.forEach(issue => {
|
||||
ctx.addIssue({
|
||||
path: issue.path,
|
||||
message: issue.message,
|
||||
// FIXME: 表单校验底层依赖了 validation / code,去掉就跑不通了
|
||||
validation: 'regex',
|
||||
code: 'invalid_string',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const checkObjectDefaultValue = nodes => {
|
||||
const result: Array<{
|
||||
path: (string | number)[];
|
||||
message: string;
|
||||
}> = [];
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const { defaultValue, type } = nodes[i];
|
||||
if (typeof defaultValue !== 'string' || !defaultValue) {
|
||||
continue;
|
||||
}
|
||||
if (!ViewVariableType.isJSONInputType(type)) {
|
||||
continue;
|
||||
}
|
||||
if (!jsonSchemaValidator(defaultValue, nodes[i])) {
|
||||
// 找到重复项时返回路径和错误信息
|
||||
result.push({
|
||||
path: [i, 'defaultValue'],
|
||||
message: I18n.t('workflow_debug_wrong_json'),
|
||||
});
|
||||
}
|
||||
// json 类型只检查第一层,不需要递归检查
|
||||
}
|
||||
return result;
|
||||
};
|
||||
// 导出类型别名
|
||||
export type OutputTree = z.infer<typeof OutputTreeSchema>;
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { z, ZodIssueCode } from 'zod';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type ValidatorProps } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
// 自定义验证器,检查数组是否为空,并且没有重复的值
|
||||
const nonEmptyUniqueArray = z
|
||||
.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.superRefine((val, ctx) => {
|
||||
const seenValues = new Set();
|
||||
|
||||
val.forEach((item, idx) => {
|
||||
// 检查非空
|
||||
if (item.name.trim() === '') {
|
||||
ctx.addIssue({
|
||||
code: ZodIssueCode.custom,
|
||||
message: I18n.t(
|
||||
'workflow_ques_option_notempty',
|
||||
{},
|
||||
'选项内容不可为空',
|
||||
),
|
||||
path: [idx],
|
||||
});
|
||||
}
|
||||
|
||||
// 检查重复
|
||||
if (seenValues.has(item.name)) {
|
||||
ctx.addIssue({
|
||||
code: ZodIssueCode.custom,
|
||||
message: I18n.t(
|
||||
'workflow_ques_ans_testrun_dulpicate',
|
||||
{},
|
||||
'选项内容不可重复',
|
||||
),
|
||||
path: [idx],
|
||||
});
|
||||
} else {
|
||||
seenValues.add(item.name);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export function questionOptionValidator({
|
||||
value,
|
||||
}: ValidatorProps<Array<{ name?: string; id: string }>>) {
|
||||
try {
|
||||
nonEmptyUniqueArray.parse(value);
|
||||
} catch (error) {
|
||||
return JSON.stringify(error);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { type ValidatorProps } from '@flowgram-adapter/free-layout-editor';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { SettingOnErrorProcessType } from '../setting-on-error/types';
|
||||
|
||||
const SettingOnErrorSchema = z.object({
|
||||
settingOnErrorIsOpen: z.boolean().optional(),
|
||||
settingOnErrorJSON: z.string().optional(),
|
||||
processType: z.number().optional(),
|
||||
});
|
||||
|
||||
type SettingOnError = z.infer<typeof SettingOnErrorSchema>;
|
||||
|
||||
export const settingOnErrorValidator = ({
|
||||
value,
|
||||
}: ValidatorProps<SettingOnError>) => {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function isJSONVerified(settingOnError: SettingOnError) {
|
||||
if (settingOnError?.settingOnErrorIsOpen) {
|
||||
if (
|
||||
settingOnError?.processType &&
|
||||
settingOnError?.processType !== SettingOnErrorProcessType.RETURN
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
JSON.parse(settingOnError?.settingOnErrorJSON as string);
|
||||
// eslint-disable-next-line @coze-arch/use-error-in-catch
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// json 合法性校验
|
||||
const schemeParesd = SettingOnErrorSchema.refine(
|
||||
settingOnError => isJSONVerified(settingOnError),
|
||||
{
|
||||
message: I18n.t('workflow_exception_ignore_json_error'),
|
||||
},
|
||||
).safeParse(value);
|
||||
|
||||
if (!schemeParesd.success) {
|
||||
return JSON.stringify((schemeParesd as any).error);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type ValidatorProps } from '@flowgram-adapter/free-layout-editor';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
export function systemVariableValidator({ value }: ValidatorProps<string>) {
|
||||
const trimmed = value?.trim() || '';
|
||||
if (trimmed.startsWith('sys_')) {
|
||||
return I18n.t('variable_240416_01') || ' ';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user