mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-18 06:55:51 +08:00
feat: replace ajv with jsonschema for JSON validation and update related components
This commit is contained in:
parent
0319e35b4d
commit
9ee8fa644e
@ -4,7 +4,7 @@ import cn from '@/utils/classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { checkDepth } from '../../utils'
|
||||
import { checkJsonDepth } from '../../utils'
|
||||
import { JSON_SCHEMA_MAX_DEPTH } from '@/config'
|
||||
import CodeEditor from './code-editor'
|
||||
import ErrorMessage from './error-message'
|
||||
@ -59,7 +59,7 @@ const JsonImporter: FC<JsonImporterProps> = ({
|
||||
setParseError(new Error('Root must be an object, not an array or primitive value.'))
|
||||
return
|
||||
}
|
||||
const maxDepth = checkDepth(parsedJSON)
|
||||
const maxDepth = checkJsonDepth(parsedJSON)
|
||||
if (maxDepth > JSON_SCHEMA_MAX_DEPTH) {
|
||||
setParseError({
|
||||
type: 'error',
|
||||
@ -72,10 +72,10 @@ const JsonImporter: FC<JsonImporterProps> = ({
|
||||
setOpen(false)
|
||||
}
|
||||
catch (e: any) {
|
||||
if (e instanceof SyntaxError)
|
||||
if (e instanceof Error)
|
||||
setParseError(e)
|
||||
else
|
||||
setParseError(new Error('Unknown error'))
|
||||
setParseError(new Error('Invalid JSON'))
|
||||
}
|
||||
}, [onSubmit, json])
|
||||
|
||||
|
@ -9,12 +9,20 @@ import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import VisualEditor from './visual-editor'
|
||||
import SchemaEditor from './schema-editor'
|
||||
import { convertBooleanToString, getValidationErrorMessage, jsonToSchema, validateSchemaAgainstDraft7 } from '../../utils'
|
||||
import {
|
||||
checkJsonSchemaDepth,
|
||||
convertBooleanToString,
|
||||
getValidationErrorMessage,
|
||||
jsonToSchema,
|
||||
preValidateSchema,
|
||||
validateSchemaAgainstDraft7,
|
||||
} from '../../utils'
|
||||
import { MittProvider, VisualEditorContextProvider } from './visual-editor/context'
|
||||
import ErrorMessage from './error-message'
|
||||
import { useVisualEditorStore } from './visual-editor/store'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { JSON_SCHEMA_MAX_DEPTH } from '@/config'
|
||||
|
||||
type JsonSchemaConfigProps = {
|
||||
defaultSchema?: SchemaRoot
|
||||
@ -74,18 +82,26 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
|
||||
if (currentTab === value) return
|
||||
if (currentTab === SchemaView.JsonSchema) {
|
||||
try {
|
||||
const parsedJson = JSON.parse(json)
|
||||
const schema = convertBooleanToString(parsedJson)
|
||||
const schema = JSON.parse(json)
|
||||
setParseError(null)
|
||||
const ajvError = validateSchemaAgainstDraft7(schema)
|
||||
if (ajvError.length > 0) {
|
||||
setValidationError(getValidationErrorMessage(ajvError))
|
||||
const result = preValidateSchema(schema)
|
||||
if (!result.success) {
|
||||
setValidationError(result.error.message)
|
||||
return
|
||||
}
|
||||
else {
|
||||
setJsonSchema(schema)
|
||||
setValidationError('')
|
||||
const schemaDepth = checkJsonSchemaDepth(schema)
|
||||
if (schemaDepth > JSON_SCHEMA_MAX_DEPTH) {
|
||||
setValidationError(`Schema exceeds maximum depth of ${JSON_SCHEMA_MAX_DEPTH}.`)
|
||||
return
|
||||
}
|
||||
convertBooleanToString(schema)
|
||||
const validationErrors = validateSchemaAgainstDraft7(schema)
|
||||
if (validationErrors.length > 0) {
|
||||
setValidationError(getValidationErrorMessage(validationErrors))
|
||||
return
|
||||
}
|
||||
setJsonSchema(schema)
|
||||
setValidationError('')
|
||||
}
|
||||
catch (error) {
|
||||
setValidationError('')
|
||||
@ -135,7 +151,7 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
|
||||
|
||||
const handleResetDefaults = useCallback(() => {
|
||||
if (currentTab === SchemaView.VisualEditor) {
|
||||
setHoveringProperty('')
|
||||
setHoveringProperty(null)
|
||||
advancedEditing && setAdvancedEditing(false)
|
||||
isAddingNewField && setIsAddingNewField(false)
|
||||
}
|
||||
@ -153,15 +169,24 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
|
||||
try {
|
||||
schema = JSON.parse(json)
|
||||
setParseError(null)
|
||||
const ajvError = validateSchemaAgainstDraft7(schema)
|
||||
if (ajvError.length > 0) {
|
||||
setValidationError(getValidationErrorMessage(ajvError))
|
||||
const result = preValidateSchema(schema)
|
||||
if (!result.success) {
|
||||
setValidationError(result.error.message)
|
||||
return
|
||||
}
|
||||
else {
|
||||
setJsonSchema(schema)
|
||||
setValidationError('')
|
||||
const schemaDepth = checkJsonSchemaDepth(schema)
|
||||
if (schemaDepth > JSON_SCHEMA_MAX_DEPTH) {
|
||||
setValidationError(`Schema exceeds maximum depth of ${JSON_SCHEMA_MAX_DEPTH}.`)
|
||||
return
|
||||
}
|
||||
convertBooleanToString(schema)
|
||||
const validationErrors = validateSchemaAgainstDraft7(schema)
|
||||
if (validationErrors.length > 0) {
|
||||
setValidationError(getValidationErrorMessage(validationErrors))
|
||||
return
|
||||
}
|
||||
setJsonSchema(schema)
|
||||
setValidationError('')
|
||||
}
|
||||
catch (error) {
|
||||
setValidationError('')
|
||||
|
@ -47,14 +47,13 @@ const GeneratedResult: FC<GeneratedResultProps> = ({
|
||||
const jsonSchema = useMemo(() => formatJSON(schema), [schema])
|
||||
|
||||
const handleApply = useCallback(() => {
|
||||
const ajvError = validateSchemaAgainstDraft7(schema)
|
||||
if (ajvError.length > 0) {
|
||||
setValidationError(getValidationErrorMessage(ajvError))
|
||||
}
|
||||
else {
|
||||
onApply()
|
||||
setValidationError('')
|
||||
const validationErrors = validateSchemaAgainstDraft7(schema)
|
||||
if (validationErrors.length > 0) {
|
||||
setValidationError(getValidationErrorMessage(validationErrors))
|
||||
return
|
||||
}
|
||||
onApply()
|
||||
setValidationError('')
|
||||
}, [schema, onApply])
|
||||
|
||||
return (
|
||||
|
@ -47,6 +47,15 @@ const TYPE_OPTIONS = [
|
||||
{ value: ArrayType.object, text: 'array[object]' },
|
||||
]
|
||||
|
||||
const MAXIMUM_DEPTH_TYPE_OPTIONS = [
|
||||
{ value: Type.string, text: 'string' },
|
||||
{ value: Type.number, text: 'number' },
|
||||
// { value: Type.boolean, text: 'boolean' },
|
||||
{ value: ArrayType.string, text: 'array[string]' },
|
||||
{ value: ArrayType.number, text: 'array[number]' },
|
||||
// { value: ArrayType.boolean, text: 'array[boolean]' },
|
||||
]
|
||||
|
||||
const EditCard: FC<EditCardProps> = ({
|
||||
fields,
|
||||
depth,
|
||||
@ -63,7 +72,8 @@ const EditCard: FC<EditCardProps> = ({
|
||||
const { emit, useSubscribe } = useMittContext()
|
||||
const blurWithActions = useRef(false)
|
||||
|
||||
const disableAddBtn = depth >= JSON_SCHEMA_MAX_DEPTH || (currentFields.type !== Type.object && currentFields.type !== ArrayType.object)
|
||||
const maximumDepthReached = depth === JSON_SCHEMA_MAX_DEPTH
|
||||
const disableAddBtn = maximumDepthReached || (currentFields.type !== Type.object && currentFields.type !== ArrayType.object)
|
||||
const hasAdvancedOptions = currentFields.type === Type.string || currentFields.type === Type.number
|
||||
const isAdvancedEditing = advancedEditing || isAddingNewField
|
||||
|
||||
@ -205,7 +215,7 @@ const EditCard: FC<EditCardProps> = ({
|
||||
/>
|
||||
<TypeSelector
|
||||
currentValue={currentFields.type}
|
||||
items={TYPE_OPTIONS}
|
||||
items={maximumDepthReached ? MAXIMUM_DEPTH_TYPE_OPTIONS : TYPE_OPTIONS}
|
||||
onSelect={handleTypeChange}
|
||||
popupClassName={'z-[1000]'}
|
||||
/>
|
||||
|
@ -19,7 +19,7 @@ const VisualEditor: FC<VisualEditorProps> = (props) => {
|
||||
schema={schema}
|
||||
required={false}
|
||||
path={[]}
|
||||
depth={1}
|
||||
depth={0}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -23,28 +23,31 @@ type SchemaNodeProps = {
|
||||
|
||||
// Support 10 levels of indentation
|
||||
const indentPadding: Record<number, string> = {
|
||||
1: 'pl-0',
|
||||
2: 'pl-[20px]',
|
||||
3: 'pl-[40px]',
|
||||
4: 'pl-[60px]',
|
||||
5: 'pl-[80px]',
|
||||
6: 'pl-[100px]',
|
||||
7: 'pl-[120px]',
|
||||
8: 'pl-[140px]',
|
||||
9: 'pl-[160px]',
|
||||
10: 'pl-[180px]',
|
||||
0: 'pl-0',
|
||||
1: 'pl-[20px]',
|
||||
2: 'pl-[40px]',
|
||||
3: 'pl-[60px]',
|
||||
4: 'pl-[80px]',
|
||||
5: 'pl-[100px]',
|
||||
6: 'pl-[120px]',
|
||||
7: 'pl-[140px]',
|
||||
8: 'pl-[160px]',
|
||||
9: 'pl-[180px]',
|
||||
10: 'pl-[200px]',
|
||||
}
|
||||
|
||||
const indentLeft: Record<number, string> = {
|
||||
2: 'left-0',
|
||||
3: 'left-[20px]',
|
||||
4: 'left-[40px]',
|
||||
5: 'left-[60px]',
|
||||
6: 'left-[80px]',
|
||||
7: 'left-[100px]',
|
||||
8: 'left-[120px]',
|
||||
9: 'left-[140px]',
|
||||
10: 'left-[160px]',
|
||||
0: 'left-0',
|
||||
1: 'left-[20px]',
|
||||
2: 'left-[40px]',
|
||||
3: 'left-[60px]',
|
||||
4: 'left-[80px]',
|
||||
5: 'left-[100px]',
|
||||
6: 'left-[120px]',
|
||||
7: 'left-[140px]',
|
||||
8: 'left-[160px]',
|
||||
9: 'left-[180px]',
|
||||
10: 'left-[200px]',
|
||||
}
|
||||
|
||||
const SchemaNode: FC<SchemaNodeProps> = ({
|
||||
@ -61,13 +64,13 @@ const SchemaNode: FC<SchemaNodeProps> = ({
|
||||
const isAddingNewField = useVisualEditorStore(state => state.isAddingNewField)
|
||||
const advancedEditing = useVisualEditorStore(state => state.advancedEditing)
|
||||
|
||||
const { run: setHoveringPropertyDebounced } = useDebounceFn((path: string) => {
|
||||
const { run: setHoveringPropertyDebounced } = useDebounceFn((path: string | null) => {
|
||||
setHoveringProperty(path)
|
||||
}, { wait: 50 })
|
||||
|
||||
const hasChildren = useMemo(() => getHasChildren(schema), [schema])
|
||||
const type = useMemo(() => getFieldType(schema), [schema])
|
||||
const isHovering = hoveringProperty === path.join('.') && depth > 1
|
||||
const isHovering = hoveringProperty === path.join('.')
|
||||
|
||||
const handleExpand = () => {
|
||||
setIsExpanded(!isExpanded)
|
||||
@ -80,16 +83,16 @@ const SchemaNode: FC<SchemaNodeProps> = ({
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (advancedEditing || isAddingNewField) return
|
||||
setHoveringPropertyDebounced('')
|
||||
setHoveringPropertyDebounced(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<div className={classNames('relative z-10', indentPadding[depth])}>
|
||||
{depth > 1 && hasChildren && (
|
||||
{depth > 0 && hasChildren && (
|
||||
<div className={classNames(
|
||||
'flex items-center absolute top-0 w-5 h-7 px-0.5 z-10 bg-background-section-burn',
|
||||
indentLeft[depth],
|
||||
indentLeft[depth - 1],
|
||||
)}>
|
||||
<button
|
||||
onClick={handleExpand}
|
||||
@ -108,7 +111,7 @@ const SchemaNode: FC<SchemaNodeProps> = ({
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{isHovering ? (
|
||||
{(isHovering && depth > 0) ? (
|
||||
<EditCard
|
||||
fields={{
|
||||
name,
|
||||
@ -135,7 +138,7 @@ const SchemaNode: FC<SchemaNodeProps> = ({
|
||||
<div className={classNames(
|
||||
'flex justify-center w-5 absolute top-7 z-0',
|
||||
schema.description ? 'h-[calc(100%-3rem)]' : 'h-[calc(100%-1.75rem)]',
|
||||
indentLeft[depth + 1],
|
||||
indentLeft[depth],
|
||||
)}>
|
||||
<Divider
|
||||
type='vertical'
|
||||
@ -180,7 +183,7 @@ const SchemaNode: FC<SchemaNodeProps> = ({
|
||||
)}
|
||||
|
||||
{
|
||||
depth === 1 && !isAddingNewField && (
|
||||
depth === 0 && !isAddingNewField && (
|
||||
<AddField />
|
||||
)
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import type { SchemaRoot } from '../../../types'
|
||||
import { VisualEditorContext } from './context'
|
||||
|
||||
type VisualEditorStore = {
|
||||
hoveringProperty: string | ''
|
||||
setHoveringProperty: (propertyPath: string) => void
|
||||
hoveringProperty: string | null
|
||||
setHoveringProperty: (propertyPath: string | null) => void
|
||||
isAddingNewField: boolean
|
||||
setIsAddingNewField: (isAdding: boolean) => void
|
||||
advancedEditing: boolean
|
||||
@ -15,8 +15,8 @@ type VisualEditorStore = {
|
||||
}
|
||||
|
||||
export const createVisualEditorStore = () => createStore<VisualEditorStore>(set => ({
|
||||
hoveringProperty: '',
|
||||
setHoveringProperty: (propertyPath: string) => set({ hoveringProperty: propertyPath }),
|
||||
hoveringProperty: null,
|
||||
setHoveringProperty: (propertyPath: string | null) => set({ hoveringProperty: propertyPath }),
|
||||
isAddingNewField: false,
|
||||
setIsAddingNewField: (isAdding: boolean) => set({ isAddingNewField: isAdding }),
|
||||
advancedEditing: false,
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { ArrayType, Type } from './types'
|
||||
import type { ArrayItems, Field, LLMNodeType } from './types'
|
||||
import type { ErrorObject } from 'ajv'
|
||||
import { validateDraft07 } from '@/public/validate-esm.mjs'
|
||||
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
|
||||
@ -58,22 +59,37 @@ export const jsonToSchema = (json: any): Field => {
|
||||
return schema
|
||||
}
|
||||
|
||||
export const checkDepth = (json: any, currentDepth = 1) => {
|
||||
const type = inferType(json)
|
||||
if (type !== Type.object && type !== Type.array)
|
||||
return currentDepth
|
||||
export const checkJsonDepth = (json: any) => {
|
||||
if (!json || typeof json !== 'object')
|
||||
return 0
|
||||
|
||||
let maxDepth = currentDepth
|
||||
if (type === Type.object) {
|
||||
Object.keys(json).forEach((key) => {
|
||||
const depth = checkDepth(json[key], currentDepth + 1)
|
||||
maxDepth = Math.max(maxDepth, depth)
|
||||
})
|
||||
let maxDepth = 0
|
||||
|
||||
if (Array.isArray(json) && json[0] && typeof json[0] === 'object') {
|
||||
maxDepth = checkJsonDepth(json[0]) + 1
|
||||
}
|
||||
else if (type === Type.array && json.length > 0) {
|
||||
const depth = checkDepth(json[0], currentDepth + 1)
|
||||
maxDepth = Math.max(maxDepth, depth)
|
||||
else if (typeof 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 || typeof 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
|
||||
}
|
||||
|
||||
@ -84,6 +100,169 @@ 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
|
||||
@ -91,17 +270,20 @@ export const validateSchemaAgainstDraft7 = (schemaToValidate: any) => {
|
||||
draft.$schema = 'http://json-schema.org/draft-07/schema#'
|
||||
})
|
||||
|
||||
const valid = validateDraft07(schema)
|
||||
const result = validator.validate(schema, draft07MetaSchema, {
|
||||
nestedErrors: true,
|
||||
throwError: false,
|
||||
})
|
||||
|
||||
// Access errors from the validation result
|
||||
const errors = valid ? [] : (validateDraft07 as any).errors || []
|
||||
const errors = result.valid ? [] : result.errors || []
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
export const getValidationErrorMessage = (errors: ErrorObject[]) => {
|
||||
export const getValidationErrorMessage = (errors: ValidationError[]) => {
|
||||
const message = errors.map((error) => {
|
||||
return `Error: ${error.instancePath} ${error.message} Details: ${JSON.stringify(error.params)}`
|
||||
return `Error: ${error.path.join('.')} ${error.message} Details: ${JSON.stringify(error.stack)}`
|
||||
}).join('; ')
|
||||
return message
|
||||
}
|
||||
@ -125,3 +307,15 @@ export const convertBooleanToString = (schema: 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
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const Ajv = require('ajv')
|
||||
const standaloneCode = require('ajv/dist/standalone').default
|
||||
|
||||
const ajv = new Ajv({
|
||||
allErrors: true,
|
||||
verbose: true,
|
||||
code: { source: true, esm: true },
|
||||
})
|
||||
|
||||
const moduleCode = standaloneCode(ajv, {
|
||||
validateDraft07: 'http://json-schema.org/draft-07/schema#',
|
||||
})
|
||||
|
||||
/**
|
||||
* @see {@link https://github.com/ajv-validator/ajv/issues/2209}
|
||||
*/
|
||||
const preamble = [
|
||||
'"use strict";',
|
||||
].join('')
|
||||
const imports = new Set()
|
||||
const requireRegex = /const (\S+)\s*=\s*require\((.+)\)\.(\S+);/g
|
||||
const replaced = moduleCode
|
||||
.replace(requireRegex, (_match, p1, p2, p3) => {
|
||||
imports.add(`import { ${p3} as ${p1} } from ${p2};`)
|
||||
return ''
|
||||
})
|
||||
.replace('"use strict";', '')
|
||||
|
||||
const uglyOutput = [preamble, Array.from(imports).join(''), replaced].join(
|
||||
'',
|
||||
)
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, '../public/validate-esm.mjs'), uglyOutput)
|
@ -56,7 +56,6 @@
|
||||
"@tanstack/react-query": "^5.60.5",
|
||||
"@tanstack/react-query-devtools": "^5.60.5",
|
||||
"ahooks": "^3.8.4",
|
||||
"ajv": "^8.17.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"classnames": "^2.5.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
@ -74,6 +73,7 @@
|
||||
"immer": "^9.0.19",
|
||||
"js-audio-recorder": "^1.0.7",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsonschema": "^1.5.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"katex": "^0.16.21",
|
||||
"ky": "^1.7.2",
|
||||
|
11
web/pnpm-lock.yaml
generated
11
web/pnpm-lock.yaml
generated
@ -103,9 +103,6 @@ importers:
|
||||
ahooks:
|
||||
specifier: ^3.8.4
|
||||
version: 3.8.4(react@19.0.0)
|
||||
ajv:
|
||||
specifier: ^8.17.1
|
||||
version: 8.17.1
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0
|
||||
@ -157,6 +154,9 @@ importers:
|
||||
js-cookie:
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
jsonschema:
|
||||
specifier: ^1.5.0
|
||||
version: 1.5.0
|
||||
jwt-decode:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
@ -5854,6 +5854,9 @@ packages:
|
||||
jsonfile@6.1.0:
|
||||
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
|
||||
|
||||
jsonschema@1.5.0:
|
||||
resolution: {integrity: sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==}
|
||||
|
||||
jsx-ast-utils@3.3.5:
|
||||
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
@ -15253,6 +15256,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
graceful-fs: 4.2.11
|
||||
|
||||
jsonschema@1.5.0: {}
|
||||
|
||||
jsx-ast-utils@3.3.5:
|
||||
dependencies:
|
||||
array-includes: 3.1.8
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user