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 }