feat: Add customized json schema validation (#25408)

This commit is contained in:
Wu Tianwei
2025-09-09 15:15:32 +08:00
committed by GitHub
parent 4aba570fa8
commit 37975319f2
5 changed files with 289 additions and 187 deletions

View File

@@ -17,7 +17,7 @@ const ErrorMessage: FC<ErrorMessageProps> = ({
className,
)}>
<RiErrorWarningFill className='h-4 w-4 shrink-0 text-text-destructive' />
<div className='system-xs-medium max-h-12 grow overflow-y-auto break-words text-text-primary'>
<div className='system-xs-medium max-h-12 grow overflow-y-auto whitespace-pre-line break-words text-text-primary'>
{message}
</div>
</div>

View File

@@ -1,9 +1,8 @@
import { z } from 'zod'
import { ArrayType, Type } from './types'
import type { ArrayItems, Field, LLMNodeType } from './types'
import type { Schema, ValidationError } from 'jsonschema'
import { Validator } from 'jsonschema'
import produce from 'immer'
import { z } from 'zod'
import { draft07Validator, forbidBooleanProperties } from '@/utils/validators'
import type { ValidationError } from 'jsonschema'
export const checkNodeValid = (_payload: LLMNodeType) => {
return true
@@ -14,7 +13,7 @@ export const getFieldType = (field: Field) => {
if (type !== Type.array || !items)
return type
return ArrayType[items.type]
return ArrayType[items.type as keyof typeof ArrayType]
}
export const getHasChildren = (schema: Field) => {
@@ -115,191 +114,22 @@ export const findPropertyWithPath = (target: any, path: string[]) => {
return current
}
const draft07MetaSchema = {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'http://json-schema.org/draft-07/schema#',
title: 'Core schema meta-schema',
definitions: {
schemaArray: {
type: 'array',
minItems: 1,
items: { $ref: '#' },
},
nonNegativeInteger: {
type: 'integer',
minimum: 0,
},
nonNegativeIntegerDefault0: {
allOf: [
{ $ref: '#/definitions/nonNegativeInteger' },
{ default: 0 },
],
},
simpleTypes: {
enum: [
'array',
'boolean',
'integer',
'null',
'number',
'object',
'string',
],
},
stringArray: {
type: 'array',
items: { type: 'string' },
uniqueItems: true,
default: [],
},
},
type: ['object', 'boolean'],
properties: {
$id: {
type: 'string',
format: 'uri-reference',
},
$schema: {
type: 'string',
format: 'uri',
},
$ref: {
type: 'string',
format: 'uri-reference',
},
title: {
type: 'string',
},
description: {
type: 'string',
},
default: true,
readOnly: {
type: 'boolean',
default: false,
},
examples: {
type: 'array',
items: true,
},
multipleOf: {
type: 'number',
exclusiveMinimum: 0,
},
maximum: {
type: 'number',
},
exclusiveMaximum: {
type: 'number',
},
minimum: {
type: 'number',
},
exclusiveMinimum: {
type: 'number',
},
maxLength: { $ref: '#/definitions/nonNegativeInteger' },
minLength: { $ref: '#/definitions/nonNegativeIntegerDefault0' },
pattern: {
type: 'string',
format: 'regex',
},
additionalItems: { $ref: '#' },
items: {
anyOf: [
{ $ref: '#' },
{ $ref: '#/definitions/schemaArray' },
],
default: true,
},
maxItems: { $ref: '#/definitions/nonNegativeInteger' },
minItems: { $ref: '#/definitions/nonNegativeIntegerDefault0' },
uniqueItems: {
type: 'boolean',
default: false,
},
contains: { $ref: '#' },
maxProperties: { $ref: '#/definitions/nonNegativeInteger' },
minProperties: { $ref: '#/definitions/nonNegativeIntegerDefault0' },
required: { $ref: '#/definitions/stringArray' },
additionalProperties: { $ref: '#' },
definitions: {
type: 'object',
additionalProperties: { $ref: '#' },
default: {},
},
properties: {
type: 'object',
additionalProperties: { $ref: '#' },
default: {},
},
patternProperties: {
type: 'object',
additionalProperties: { $ref: '#' },
propertyNames: { format: 'regex' },
default: {},
},
dependencies: {
type: 'object',
additionalProperties: {
anyOf: [
{ $ref: '#' },
{ $ref: '#/definitions/stringArray' },
],
},
},
propertyNames: { $ref: '#' },
const: true,
enum: {
type: 'array',
items: true,
minItems: 1,
uniqueItems: true,
},
type: {
anyOf: [
{ $ref: '#/definitions/simpleTypes' },
{
type: 'array',
items: { $ref: '#/definitions/simpleTypes' },
minItems: 1,
uniqueItems: true,
},
],
},
format: { type: 'string' },
allOf: { $ref: '#/definitions/schemaArray' },
anyOf: { $ref: '#/definitions/schemaArray' },
oneOf: { $ref: '#/definitions/schemaArray' },
not: { $ref: '#' },
},
default: true,
} as unknown as Schema
const validator = new Validator()
export const validateSchemaAgainstDraft7 = (schemaToValidate: any) => {
const schema = produce(schemaToValidate, (draft: any) => {
// Make sure the schema has the $schema property for draft-07
if (!draft.$schema)
draft.$schema = 'http://json-schema.org/draft-07/schema#'
})
// First check against Draft-07
const result = draft07Validator(schemaToValidate)
// Then apply custom rule
const customErrors = forbidBooleanProperties(schemaToValidate)
const result = validator.validate(schema, draft07MetaSchema, {
nestedErrors: true,
throwError: false,
})
// Access errors from the validation result
const errors = result.valid ? [] : result.errors || []
return errors
return [...result.errors, ...customErrors]
}
export const getValidationErrorMessage = (errors: ValidationError[]) => {
export const getValidationErrorMessage = (errors: Array<ValidationError | string>) => {
const message = errors.map((error) => {
return `Error: ${error.path.join('.')} ${error.message} Details: ${JSON.stringify(error.stack)}`
}).join('; ')
if (typeof error === 'string')
return error
else
return `Error: ${error.stack}\n`
}).join('')
return message
}