mirror of
https://github.com/langgenius/dify.git
synced 2025-12-19 17:27:16 -05:00
fix: fix json object validate (#29840)
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from enum import StrEnum, auto
|
from enum import StrEnum, auto
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
@@ -120,7 +121,7 @@ class VariableEntity(BaseModel):
|
|||||||
allowed_file_types: Sequence[FileType] | None = Field(default_factory=list)
|
allowed_file_types: Sequence[FileType] | None = Field(default_factory=list)
|
||||||
allowed_file_extensions: Sequence[str] | None = Field(default_factory=list)
|
allowed_file_extensions: Sequence[str] | None = Field(default_factory=list)
|
||||||
allowed_file_upload_methods: Sequence[FileTransferMethod] | None = Field(default_factory=list)
|
allowed_file_upload_methods: Sequence[FileTransferMethod] | None = Field(default_factory=list)
|
||||||
json_schema: dict[str, Any] | None = Field(default=None)
|
json_schema: str | None = Field(default=None)
|
||||||
|
|
||||||
@field_validator("description", mode="before")
|
@field_validator("description", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -134,11 +135,17 @@ class VariableEntity(BaseModel):
|
|||||||
|
|
||||||
@field_validator("json_schema")
|
@field_validator("json_schema")
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_json_schema(cls, schema: dict[str, Any] | None) -> dict[str, Any] | None:
|
def validate_json_schema(cls, schema: str | None) -> str | None:
|
||||||
if schema is None:
|
if schema is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Draft7Validator.check_schema(schema)
|
json_schema = json.loads(schema)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
raise ValueError(f"invalid json_schema value {schema}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
Draft7Validator.check_schema(json_schema)
|
||||||
except SchemaError as e:
|
except SchemaError as e:
|
||||||
raise ValueError(f"Invalid JSON schema: {e.message}")
|
raise ValueError(f"Invalid JSON schema: {e.message}")
|
||||||
return schema
|
return schema
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from collections.abc import Generator, Mapping, Sequence
|
from collections.abc import Generator, Mapping, Sequence
|
||||||
from typing import TYPE_CHECKING, Any, Union, final
|
from typing import TYPE_CHECKING, Any, Union, final
|
||||||
|
|
||||||
@@ -175,6 +176,13 @@ class BaseAppGenerator:
|
|||||||
value = True
|
value = True
|
||||||
elif value == 0:
|
elif value == 0:
|
||||||
value = False
|
value = False
|
||||||
|
case VariableEntityType.JSON_OBJECT:
|
||||||
|
if not isinstance(value, str):
|
||||||
|
raise ValueError(f"{variable_entity.variable} in input form must be a string")
|
||||||
|
try:
|
||||||
|
json.loads(value)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
raise ValueError(f"{variable_entity.variable} in input form must be a valid JSON object")
|
||||||
case _:
|
case _:
|
||||||
raise AssertionError("this statement should be unreachable.")
|
raise AssertionError("this statement should be unreachable.")
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from jsonschema import Draft7Validator, ValidationError
|
from jsonschema import Draft7Validator, ValidationError
|
||||||
@@ -42,15 +43,25 @@ class StartNode(Node[StartNodeData]):
|
|||||||
if value is None and variable.required:
|
if value is None and variable.required:
|
||||||
raise ValueError(f"{key} is required in input form")
|
raise ValueError(f"{key} is required in input form")
|
||||||
|
|
||||||
if not isinstance(value, dict):
|
|
||||||
raise ValueError(f"{key} must be a JSON object")
|
|
||||||
|
|
||||||
schema = variable.json_schema
|
schema = variable.json_schema
|
||||||
if not schema:
|
if not schema:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if not value:
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Draft7Validator(schema).validate(value)
|
json_schema = json.loads(schema)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
raise ValueError(f"{schema} must be a valid JSON object")
|
||||||
|
|
||||||
|
try:
|
||||||
|
json_value = json.loads(value)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
raise ValueError(f"{value} must be a valid JSON object")
|
||||||
|
|
||||||
|
try:
|
||||||
|
Draft7Validator(json_schema).validate(json_value)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
raise ValueError(f"JSON object for '{key}' does not match schema: {e.message}")
|
raise ValueError(f"JSON object for '{key}' does not match schema: {e.message}")
|
||||||
node_inputs[key] = value
|
node_inputs[key] = json_value
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -46,14 +47,16 @@ def make_start_node(user_inputs, variables):
|
|||||||
|
|
||||||
|
|
||||||
def test_json_object_valid_schema():
|
def test_json_object_valid_schema():
|
||||||
schema = {
|
schema = json.dumps(
|
||||||
"type": "object",
|
{
|
||||||
"properties": {
|
"type": "object",
|
||||||
"age": {"type": "number"},
|
"properties": {
|
||||||
"name": {"type": "string"},
|
"age": {"type": "number"},
|
||||||
},
|
"name": {"type": "string"},
|
||||||
"required": ["age"],
|
},
|
||||||
}
|
"required": ["age"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
variables = [
|
variables = [
|
||||||
VariableEntity(
|
VariableEntity(
|
||||||
@@ -65,7 +68,7 @@ def test_json_object_valid_schema():
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
user_inputs = {"profile": {"age": 20, "name": "Tom"}}
|
user_inputs = {"profile": json.dumps({"age": 20, "name": "Tom"})}
|
||||||
|
|
||||||
node = make_start_node(user_inputs, variables)
|
node = make_start_node(user_inputs, variables)
|
||||||
result = node._run()
|
result = node._run()
|
||||||
@@ -74,12 +77,23 @@ def test_json_object_valid_schema():
|
|||||||
|
|
||||||
|
|
||||||
def test_json_object_invalid_json_string():
|
def test_json_object_invalid_json_string():
|
||||||
|
schema = json.dumps(
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"age": {"type": "number"},
|
||||||
|
"name": {"type": "string"},
|
||||||
|
},
|
||||||
|
"required": ["age", "name"],
|
||||||
|
}
|
||||||
|
)
|
||||||
variables = [
|
variables = [
|
||||||
VariableEntity(
|
VariableEntity(
|
||||||
variable="profile",
|
variable="profile",
|
||||||
label="profile",
|
label="profile",
|
||||||
type=VariableEntityType.JSON_OBJECT,
|
type=VariableEntityType.JSON_OBJECT,
|
||||||
required=True,
|
required=True,
|
||||||
|
json_schema=schema,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -88,38 +102,21 @@ def test_json_object_invalid_json_string():
|
|||||||
|
|
||||||
node = make_start_node(user_inputs, variables)
|
node = make_start_node(user_inputs, variables)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="profile must be a JSON object"):
|
with pytest.raises(ValueError, match='{"age": 20, "name": "Tom" must be a valid JSON object'):
|
||||||
node._run()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("value", ["[1, 2, 3]", "123"])
|
|
||||||
def test_json_object_valid_json_but_not_object(value):
|
|
||||||
variables = [
|
|
||||||
VariableEntity(
|
|
||||||
variable="profile",
|
|
||||||
label="profile",
|
|
||||||
type=VariableEntityType.JSON_OBJECT,
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
user_inputs = {"profile": value}
|
|
||||||
|
|
||||||
node = make_start_node(user_inputs, variables)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="profile must be a JSON object"):
|
|
||||||
node._run()
|
node._run()
|
||||||
|
|
||||||
|
|
||||||
def test_json_object_does_not_match_schema():
|
def test_json_object_does_not_match_schema():
|
||||||
schema = {
|
schema = json.dumps(
|
||||||
"type": "object",
|
{
|
||||||
"properties": {
|
"type": "object",
|
||||||
"age": {"type": "number"},
|
"properties": {
|
||||||
"name": {"type": "string"},
|
"age": {"type": "number"},
|
||||||
},
|
"name": {"type": "string"},
|
||||||
"required": ["age", "name"],
|
},
|
||||||
}
|
"required": ["age", "name"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
variables = [
|
variables = [
|
||||||
VariableEntity(
|
VariableEntity(
|
||||||
@@ -132,7 +129,7 @@ def test_json_object_does_not_match_schema():
|
|||||||
]
|
]
|
||||||
|
|
||||||
# age is a string, which violates the schema (expects number)
|
# age is a string, which violates the schema (expects number)
|
||||||
user_inputs = {"profile": {"age": "twenty", "name": "Tom"}}
|
user_inputs = {"profile": json.dumps({"age": "twenty", "name": "Tom"})}
|
||||||
|
|
||||||
node = make_start_node(user_inputs, variables)
|
node = make_start_node(user_inputs, variables)
|
||||||
|
|
||||||
@@ -141,14 +138,16 @@ def test_json_object_does_not_match_schema():
|
|||||||
|
|
||||||
|
|
||||||
def test_json_object_missing_required_schema_field():
|
def test_json_object_missing_required_schema_field():
|
||||||
schema = {
|
schema = json.dumps(
|
||||||
"type": "object",
|
{
|
||||||
"properties": {
|
"type": "object",
|
||||||
"age": {"type": "number"},
|
"properties": {
|
||||||
"name": {"type": "string"},
|
"age": {"type": "number"},
|
||||||
},
|
"name": {"type": "string"},
|
||||||
"required": ["age", "name"],
|
},
|
||||||
}
|
"required": ["age", "name"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
variables = [
|
variables = [
|
||||||
VariableEntity(
|
VariableEntity(
|
||||||
@@ -161,7 +160,7 @@ def test_json_object_missing_required_schema_field():
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Missing required field "name"
|
# Missing required field "name"
|
||||||
user_inputs = {"profile": {"age": 20}}
|
user_inputs = {"profile": json.dumps({"age": 20})}
|
||||||
|
|
||||||
node = make_start_node(user_inputs, variables)
|
node = make_start_node(user_inputs, variables)
|
||||||
|
|
||||||
@@ -214,7 +213,7 @@ def test_json_object_optional_variable_not_provided():
|
|||||||
variable="profile",
|
variable="profile",
|
||||||
label="profile",
|
label="profile",
|
||||||
type=VariableEntityType.JSON_OBJECT,
|
type=VariableEntityType.JSON_OBJECT,
|
||||||
required=False,
|
required=True,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -223,5 +222,5 @@ def test_json_object_optional_variable_not_provided():
|
|||||||
node = make_start_node(user_inputs, variables)
|
node = make_start_node(user_inputs, variables)
|
||||||
|
|
||||||
# Current implementation raises a validation error even when the variable is optional
|
# Current implementation raises a validation error even when the variable is optional
|
||||||
with pytest.raises(ValueError, match="profile must be a JSON object"):
|
with pytest.raises(ValueError, match="profile is required in input form"):
|
||||||
node._run()
|
node._run()
|
||||||
|
|||||||
Reference in New Issue
Block a user