mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-05-25 23:58:15 +08:00
337 lines
8.6 KiB
TypeScript
337 lines
8.6 KiB
TypeScript
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'
|
|
|
|
export const checkNodeValid = (payload: LLMNodeType) => {
|
|
return true
|
|
}
|
|
|
|
export const getFieldType = (field: Field) => {
|
|
const { type, items } = field
|
|
if (type !== Type.array || !items)
|
|
return type
|
|
|
|
return ArrayType[items.type]
|
|
}
|
|
|
|
export const getHasChildren = (schema: Field) => {
|
|
const complexTypes = [Type.object, Type.array]
|
|
if (!complexTypes.includes(schema.type))
|
|
return false
|
|
if (schema.type === Type.object)
|
|
return schema.properties && Object.keys(schema.properties).length > 0
|
|
if (schema.type === Type.array)
|
|
return schema.items && schema.items.type === Type.object && schema.items.properties && Object.keys(schema.items.properties).length > 0
|
|
}
|
|
|
|
export const getTypeOf = (target: any) => {
|
|
if (target === null) return 'null'
|
|
if (typeof target !== 'object') {
|
|
return typeof target
|
|
}
|
|
else {
|
|
return Object.prototype.toString
|
|
.call(target)
|
|
.slice(8, -1)
|
|
.toLocaleLowerCase()
|
|
}
|
|
}
|
|
|
|
export const inferType = (value: any): Type => {
|
|
const type = getTypeOf(value)
|
|
if (type === 'array') return Type.array
|
|
// type boolean will be treated as string
|
|
if (type === 'boolean') return Type.string
|
|
if (type === 'number') return Type.number
|
|
if (type === 'string') return Type.string
|
|
if (type === 'object') return Type.object
|
|
return Type.string
|
|
}
|
|
|
|
export const jsonToSchema = (json: any): Field => {
|
|
const schema: Field = {
|
|
type: inferType(json),
|
|
}
|
|
|
|
if (schema.type === Type.object) {
|
|
schema.properties = {}
|
|
schema.required = []
|
|
schema.additionalProperties = false
|
|
|
|
Object.entries(json).forEach(([key, value]) => {
|
|
schema.properties![key] = jsonToSchema(value)
|
|
schema.required!.push(key)
|
|
})
|
|
}
|
|
else if (schema.type === Type.array) {
|
|
schema.items = jsonToSchema(json[0]) as ArrayItems
|
|
}
|
|
|
|
return schema
|
|
}
|
|
|
|
export const checkJsonDepth = (json: any) => {
|
|
if (!json || getTypeOf(json) !== 'object')
|
|
return 0
|
|
|
|
let maxDepth = 0
|
|
|
|
if (getTypeOf(json) === 'array') {
|
|
if (json[0] && getTypeOf(json[0]) === 'object')
|
|
maxDepth = checkJsonDepth(json[0])
|
|
}
|
|
else if (getTypeOf(json) === 'object') {
|
|
const propertyDepths = Object.values(json).map(value => checkJsonDepth(value))
|
|
maxDepth = propertyDepths.length ? Math.max(...propertyDepths) + 1 : 1
|
|
}
|
|
|
|
return maxDepth
|
|
}
|
|
|
|
export const checkJsonSchemaDepth = (schema: Field) => {
|
|
if (!schema || getTypeOf(schema) !== 'object')
|
|
return 0
|
|
|
|
let maxDepth = 0
|
|
|
|
if (schema.type === Type.object && schema.properties) {
|
|
const propertyDepths = Object.values(schema.properties).map(value => checkJsonSchemaDepth(value))
|
|
maxDepth = propertyDepths.length ? Math.max(...propertyDepths) + 1 : 1
|
|
}
|
|
else if (schema.type === Type.array && schema.items && schema.items.type === Type.object) {
|
|
maxDepth = checkJsonSchemaDepth(schema.items) + 1
|
|
}
|
|
|
|
return maxDepth
|
|
}
|
|
|
|
export const findPropertyWithPath = (target: any, path: string[]) => {
|
|
let current = target
|
|
for (const key of path)
|
|
current = current[key]
|
|
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#'
|
|
})
|
|
|
|
const result = validator.validate(schema, draft07MetaSchema, {
|
|
nestedErrors: true,
|
|
throwError: false,
|
|
})
|
|
|
|
// Access errors from the validation result
|
|
const errors = result.valid ? [] : result.errors || []
|
|
|
|
return errors
|
|
}
|
|
|
|
export const getValidationErrorMessage = (errors: ValidationError[]) => {
|
|
const message = errors.map((error) => {
|
|
return `Error: ${error.path.join('.')} ${error.message} Details: ${JSON.stringify(error.stack)}`
|
|
}).join('; ')
|
|
return message
|
|
}
|
|
|
|
export const convertBooleanToString = (schema: any) => {
|
|
if (schema.type === Type.boolean)
|
|
schema.type = Type.string
|
|
if (schema.type === Type.array && schema.items && schema.items.type === Type.boolean)
|
|
schema.items.type = Type.string
|
|
if (schema.type === Type.object) {
|
|
schema.properties = Object.entries(schema.properties).reduce((acc, [key, value]) => {
|
|
acc[key] = convertBooleanToString(value)
|
|
return acc
|
|
}, {} as any)
|
|
}
|
|
if (schema.type === Type.array && schema.items && schema.items.type === Type.object) {
|
|
schema.items.properties = Object.entries(schema.items.properties).reduce((acc, [key, value]) => {
|
|
acc[key] = convertBooleanToString(value)
|
|
return acc
|
|
}, {} as any)
|
|
}
|
|
return schema
|
|
}
|
|
|
|
const schemaRootObject = z.object({
|
|
type: z.literal('object'),
|
|
properties: z.record(z.string(), z.any()),
|
|
required: z.array(z.string()),
|
|
additionalProperties: z.boolean().optional(),
|
|
})
|
|
|
|
export const preValidateSchema = (schema: any) => {
|
|
const result = schemaRootObject.safeParse(schema)
|
|
return result
|
|
}
|