mirror of https://github.com/langgenius/dify.git
feat: Add customized json schema validation (#25408)
This commit is contained in:
parent
4aba570fa8
commit
37975319f2
|
|
@ -17,7 +17,7 @@ const ErrorMessage: FC<ErrorMessageProps> = ({
|
||||||
className,
|
className,
|
||||||
)}>
|
)}>
|
||||||
<RiErrorWarningFill className='h-4 w-4 shrink-0 text-text-destructive' />
|
<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}
|
{message}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
|
import { z } from 'zod'
|
||||||
import { ArrayType, Type } from './types'
|
import { ArrayType, Type } from './types'
|
||||||
import type { ArrayItems, Field, LLMNodeType } from './types'
|
import type { ArrayItems, Field, LLMNodeType } from './types'
|
||||||
import type { Schema, ValidationError } from 'jsonschema'
|
import { draft07Validator, forbidBooleanProperties } from '@/utils/validators'
|
||||||
import { Validator } from 'jsonschema'
|
import type { ValidationError } from 'jsonschema'
|
||||||
import produce from 'immer'
|
|
||||||
import { z } from 'zod'
|
|
||||||
|
|
||||||
export const checkNodeValid = (_payload: LLMNodeType) => {
|
export const checkNodeValid = (_payload: LLMNodeType) => {
|
||||||
return true
|
return true
|
||||||
|
|
@ -14,7 +13,7 @@ export const getFieldType = (field: Field) => {
|
||||||
if (type !== Type.array || !items)
|
if (type !== Type.array || !items)
|
||||||
return type
|
return type
|
||||||
|
|
||||||
return ArrayType[items.type]
|
return ArrayType[items.type as keyof typeof ArrayType]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getHasChildren = (schema: Field) => {
|
export const getHasChildren = (schema: Field) => {
|
||||||
|
|
@ -115,191 +114,22 @@ export const findPropertyWithPath = (target: any, path: string[]) => {
|
||||||
return current
|
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) => {
|
export const validateSchemaAgainstDraft7 = (schemaToValidate: any) => {
|
||||||
const schema = produce(schemaToValidate, (draft: any) => {
|
// First check against Draft-07
|
||||||
// Make sure the schema has the $schema property for draft-07
|
const result = draft07Validator(schemaToValidate)
|
||||||
if (!draft.$schema)
|
// Then apply custom rule
|
||||||
draft.$schema = 'http://json-schema.org/draft-07/schema#'
|
const customErrors = forbidBooleanProperties(schemaToValidate)
|
||||||
})
|
|
||||||
|
|
||||||
const result = validator.validate(schema, draft07MetaSchema, {
|
return [...result.errors, ...customErrors]
|
||||||
nestedErrors: true,
|
|
||||||
throwError: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Access errors from the validation result
|
|
||||||
const errors = result.valid ? [] : result.errors || []
|
|
||||||
|
|
||||||
return errors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getValidationErrorMessage = (errors: ValidationError[]) => {
|
export const getValidationErrorMessage = (errors: Array<ValidationError | string>) => {
|
||||||
const message = errors.map((error) => {
|
const message = errors.map((error) => {
|
||||||
return `Error: ${error.path.join('.')} ${error.message} Details: ${JSON.stringify(error.stack)}`
|
if (typeof error === 'string')
|
||||||
}).join('; ')
|
return error
|
||||||
|
else
|
||||||
|
return `Error: ${error.stack}\n`
|
||||||
|
}).join('')
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12603,7 +12603,7 @@ snapshots:
|
||||||
|
|
||||||
'@vue/compiler-sfc@3.5.17':
|
'@vue/compiler-sfc@3.5.17':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.28.0
|
'@babel/parser': 7.28.3
|
||||||
'@vue/compiler-core': 3.5.17
|
'@vue/compiler-core': 3.5.17
|
||||||
'@vue/compiler-dom': 3.5.17
|
'@vue/compiler-dom': 3.5.17
|
||||||
'@vue/compiler-ssr': 3.5.17
|
'@vue/compiler-ssr': 3.5.17
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
{
|
||||||
|
"$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"
|
||||||
|
},
|
||||||
|
"$comment": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": true,
|
||||||
|
"readOnly": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"writeOnly": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"contentMediaType": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"contentEncoding": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"if": {
|
||||||
|
"$ref": "#"
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"$ref": "#"
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
"$ref": "#"
|
||||||
|
},
|
||||||
|
"allOf": {
|
||||||
|
"$ref": "#/definitions/schemaArray"
|
||||||
|
},
|
||||||
|
"anyOf": {
|
||||||
|
"$ref": "#/definitions/schemaArray"
|
||||||
|
},
|
||||||
|
"oneOf": {
|
||||||
|
"$ref": "#/definitions/schemaArray"
|
||||||
|
},
|
||||||
|
"not": {
|
||||||
|
"$ref": "#"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type { Schema } from 'jsonschema'
|
||||||
|
import { Validator } from 'jsonschema'
|
||||||
|
import draft07Schema from './draft-07.json'
|
||||||
|
|
||||||
|
const validator = new Validator()
|
||||||
|
|
||||||
|
export const draft07Validator = (schema: any) => {
|
||||||
|
return validator.validate(schema, draft07Schema as unknown as Schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const forbidBooleanProperties = (schema: any, path: string[] = []): string[] => {
|
||||||
|
let errors: string[] = []
|
||||||
|
|
||||||
|
if (schema && typeof schema === 'object' && schema.properties) {
|
||||||
|
for (const [key, val] of Object.entries(schema.properties)) {
|
||||||
|
if (typeof val === 'boolean') {
|
||||||
|
errors.push(
|
||||||
|
`Error: Property '${[...path, key].join('.')}' must not be a boolean schema`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else if (typeof val === 'object') {
|
||||||
|
errors = errors.concat(forbidBooleanProperties(val, [...path, key]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue