mirror of https://github.com/langgenius/dify.git
query param of webhook backend support
This commit is contained in:
parent
32f4d1af8b
commit
080cdda4fa
|
|
@ -5,7 +5,7 @@ Trigger Manager for loading and managing trigger providers and triggers
|
|||
import logging
|
||||
from collections.abc import Mapping
|
||||
from threading import Lock
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from flask import Request
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class WebhookService:
|
|||
data = {
|
||||
"method": request.method,
|
||||
"headers": dict(request.headers),
|
||||
"query_params": dict(request.args),
|
||||
"query_params": cls._extract_query_params(),
|
||||
"body": {},
|
||||
"files": {},
|
||||
}
|
||||
|
|
@ -120,6 +120,43 @@ class WebhookService:
|
|||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def _extract_query_params(cls) -> dict[str, Any]:
|
||||
"""Extract query parameters preserving multi-value entries."""
|
||||
if not request.args:
|
||||
return {}
|
||||
|
||||
query_params: dict[str, Any] = {}
|
||||
for key, value in request.args.items():
|
||||
query_params[key] = cls._convert_query_param_value(value)
|
||||
|
||||
return query_params
|
||||
|
||||
@classmethod
|
||||
def _convert_query_param_value(cls, value: str) -> Any:
|
||||
"""Convert query parameter strings to numbers or booleans when applicable."""
|
||||
lower_value = value.lower()
|
||||
bool_map = {
|
||||
"true": True,
|
||||
"false": False,
|
||||
"yes": True,
|
||||
"no": False,
|
||||
}
|
||||
|
||||
if lower_value in bool_map:
|
||||
return bool_map[lower_value]
|
||||
|
||||
if cls._can_convert_to_number(value):
|
||||
try:
|
||||
numeric_value = float(value)
|
||||
if numeric_value.is_integer():
|
||||
return int(numeric_value)
|
||||
return numeric_value
|
||||
except ValueError:
|
||||
return value
|
||||
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _validate_content_length(cls) -> None:
|
||||
"""Validate request content length against maximum allowed size."""
|
||||
|
|
@ -285,11 +322,24 @@ class WebhookService:
|
|||
return cls._validation_error(f"Required header missing: {header_name}")
|
||||
|
||||
# Validate required query parameters
|
||||
query_params = webhook_data.get("query_params", {})
|
||||
for param in node_data.get("params", []):
|
||||
if param.get("required", False):
|
||||
param_name = param.get("name", "")
|
||||
if param_name not in webhook_data["query_params"]:
|
||||
return cls._validation_error(f"Required query parameter missing: {param_name}")
|
||||
param_name = param.get("name", "")
|
||||
param_type = param.get("type", SegmentType.STRING)
|
||||
is_required = param.get("required", False)
|
||||
|
||||
param_exists = param_name in query_params
|
||||
if is_required and not param_exists:
|
||||
return cls._validation_error(f"Required query parameter missing: {param_name}")
|
||||
|
||||
if not param_exists:
|
||||
continue
|
||||
|
||||
if param_exists and param_type != SegmentType.STRING:
|
||||
param_value = query_params[param_name]
|
||||
validation_result = cls._validate_form_parameter_type(param_name, param_value, param_type)
|
||||
if not validation_result["valid"]:
|
||||
return validation_result
|
||||
|
||||
return {"valid": True}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,12 +26,30 @@ class TestWebhookServiceUnit:
|
|||
|
||||
assert webhook_data["method"] == "POST"
|
||||
assert webhook_data["headers"]["Authorization"] == "Bearer token"
|
||||
assert webhook_data["query_params"]["version"] == "1"
|
||||
assert webhook_data["query_params"]["version"] == 1
|
||||
assert webhook_data["query_params"]["format"] == "json"
|
||||
assert webhook_data["body"]["message"] == "hello"
|
||||
assert webhook_data["body"]["count"] == 42
|
||||
assert webhook_data["files"] == {}
|
||||
|
||||
def test_extract_webhook_data_query_params_remain_strings(self):
|
||||
"""Query parameters remain raw strings during extraction."""
|
||||
app = Flask(__name__)
|
||||
|
||||
with app.test_request_context(
|
||||
"/webhook",
|
||||
method="GET",
|
||||
headers={"Content-Type": "application/json"},
|
||||
query_string="count=42&threshold=3.14&enabled=true¬e=text",
|
||||
):
|
||||
webhook_trigger = MagicMock()
|
||||
webhook_data = WebhookService.extract_webhook_data(webhook_trigger)
|
||||
|
||||
assert webhook_data["query_params"]["count"] == 42
|
||||
assert webhook_data["query_params"]["threshold"] == 3.14
|
||||
assert webhook_data["query_params"]["enabled"] == True
|
||||
assert webhook_data["query_params"]["note"] == "text"
|
||||
|
||||
def test_extract_webhook_data_form_urlencoded(self):
|
||||
"""Test webhook data extraction from form URL encoded request."""
|
||||
app = Flask(__name__)
|
||||
|
|
@ -182,6 +200,92 @@ class TestWebhookServiceUnit:
|
|||
assert result["valid"] is False
|
||||
assert "Required query parameter missing: version" in result["error"]
|
||||
|
||||
def test_validate_webhook_request_query_param_number_type(self):
|
||||
"""Numeric query parameters should validate with numeric types."""
|
||||
webhook_data = {
|
||||
"method": "POST",
|
||||
"headers": {},
|
||||
"query_params": {"count": "42"},
|
||||
"body": {},
|
||||
"files": {},
|
||||
}
|
||||
|
||||
node_config = {
|
||||
"data": {
|
||||
"method": "post",
|
||||
"params": [{"name": "count", "required": True, "type": "number"}],
|
||||
}
|
||||
}
|
||||
|
||||
result = WebhookService.validate_webhook_request(webhook_data, node_config)
|
||||
|
||||
assert result["valid"] is True
|
||||
|
||||
def test_validate_webhook_request_query_param_number_type_invalid(self):
|
||||
"""Numeric query parameter validation should fail for non-numeric values."""
|
||||
webhook_data = {
|
||||
"method": "POST",
|
||||
"headers": {},
|
||||
"query_params": {"count": "forty-two"},
|
||||
"body": {},
|
||||
"files": {},
|
||||
}
|
||||
|
||||
node_config = {
|
||||
"data": {
|
||||
"method": "post",
|
||||
"params": [{"name": "count", "required": True, "type": "number"}],
|
||||
}
|
||||
}
|
||||
|
||||
result = WebhookService.validate_webhook_request(webhook_data, node_config)
|
||||
|
||||
assert result["valid"] is False
|
||||
assert "must be a valid number" in result["error"]
|
||||
|
||||
def test_validate_webhook_request_query_param_boolean_type(self):
|
||||
"""Boolean query parameters should validate with supported boolean strings."""
|
||||
webhook_data = {
|
||||
"method": "POST",
|
||||
"headers": {},
|
||||
"query_params": {"enabled": "true"},
|
||||
"body": {},
|
||||
"files": {},
|
||||
}
|
||||
|
||||
node_config = {
|
||||
"data": {
|
||||
"method": "post",
|
||||
"params": [{"name": "enabled", "required": True, "type": "boolean"}],
|
||||
}
|
||||
}
|
||||
|
||||
result = WebhookService.validate_webhook_request(webhook_data, node_config)
|
||||
|
||||
assert result["valid"] is True
|
||||
|
||||
def test_validate_webhook_request_query_param_string_type_preserved(self):
|
||||
"""String typed query parameters remain as strings even if boolean-like."""
|
||||
webhook_data = {
|
||||
"method": "POST",
|
||||
"headers": {},
|
||||
"query_params": {"flag": "true"},
|
||||
"body": {},
|
||||
"files": {},
|
||||
}
|
||||
|
||||
node_config = {
|
||||
"data": {
|
||||
"method": "post",
|
||||
"params": [{"name": "flag", "required": True, "type": "string"}],
|
||||
}
|
||||
}
|
||||
|
||||
result = WebhookService.validate_webhook_request(webhook_data, node_config)
|
||||
|
||||
assert result["valid"] is True
|
||||
assert webhook_data["query_params"]["flag"] == "true"
|
||||
|
||||
def test_validate_webhook_request_missing_required_body_param(self):
|
||||
"""Test webhook validation with missing required body parameter."""
|
||||
webhook_data = {"method": "POST", "headers": {}, "query_params": {}, "body": {}, "files": {}}
|
||||
|
|
@ -515,7 +619,7 @@ class TestWebhookServiceUnit:
|
|||
assert "must be an array of numbers" in result["error"]
|
||||
|
||||
def test_validate_json_parameter_type_array_bool(self):
|
||||
"""Test JSON parameter type validation for array[bool] type."""
|
||||
"""Test JSON parameter type validation for array[boolean] type."""
|
||||
# Valid array of booleans
|
||||
result = WebhookService._validate_json_parameter_type("flags", [True, False, True], "array[boolean]")
|
||||
assert result["valid"] is True
|
||||
|
|
@ -540,12 +644,6 @@ class TestWebhookServiceUnit:
|
|||
assert result["valid"] is False
|
||||
assert "must be an array of objects" in result["error"]
|
||||
|
||||
def test_validate_json_parameter_type_unknown_type(self):
|
||||
"""Test JSON parameter type validation for unknown type."""
|
||||
# Unknown type should return valid and log warning
|
||||
result = WebhookService._validate_json_parameter_type("data", "anything", "unknown_type")
|
||||
assert result["valid"] is True
|
||||
|
||||
def test_validate_webhook_request_json_type_validation(self):
|
||||
"""Test webhook validation with JSON parameter type validation."""
|
||||
# Test valid JSON types
|
||||
|
|
@ -573,11 +671,11 @@ class TestWebhookServiceUnit:
|
|||
"body": [
|
||||
{"name": "name", "type": "string", "required": True},
|
||||
{"name": "age", "type": "number", "required": True},
|
||||
{"name": "active", "type": "bool", "required": True},
|
||||
{"name": "active", "type": "boolean", "required": True},
|
||||
{"name": "profile", "type": "object", "required": True},
|
||||
{"name": "tags", "type": "array[string]", "required": True},
|
||||
{"name": "scores", "type": "array[number]", "required": True},
|
||||
{"name": "flags", "type": "array[bool]", "required": True},
|
||||
{"name": "flags", "type": "array[boolean]", "required": True},
|
||||
{"name": "items", "type": "array[object]", "required": True},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue