mirror of
				https://github.com/langgenius/dify.git
				synced 2025-10-31 10:53:02 +00: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
 | |
| }
 | 
