from collections.abc import Sequence from enum import StrEnum from pydantic import BaseModel, Field, field_validator from dify_graph.entities.base_node_data import BaseNodeData from dify_graph.enums import NodeType from dify_graph.variables.types import SegmentType _WEBHOOK_HEADER_ALLOWED_TYPES = frozenset( { SegmentType.STRING, } ) _WEBHOOK_QUERY_PARAMETER_ALLOWED_TYPES = frozenset( { SegmentType.STRING, SegmentType.NUMBER, SegmentType.BOOLEAN, } ) _WEBHOOK_PARAMETER_ALLOWED_TYPES = _WEBHOOK_HEADER_ALLOWED_TYPES | _WEBHOOK_QUERY_PARAMETER_ALLOWED_TYPES _WEBHOOK_BODY_ALLOWED_TYPES = frozenset( { SegmentType.STRING, SegmentType.NUMBER, SegmentType.BOOLEAN, SegmentType.OBJECT, SegmentType.ARRAY_STRING, SegmentType.ARRAY_NUMBER, SegmentType.ARRAY_BOOLEAN, SegmentType.ARRAY_OBJECT, SegmentType.FILE, } ) class Method(StrEnum): GET = "get" POST = "post" HEAD = "head" PATCH = "patch" PUT = "put" DELETE = "delete" class ContentType(StrEnum): JSON = "application/json" FORM_DATA = "multipart/form-data" FORM_URLENCODED = "application/x-www-form-urlencoded" TEXT = "text/plain" BINARY = "application/octet-stream" class WebhookParameter(BaseModel): """Parameter definition for headers or query params.""" name: str type: SegmentType = SegmentType.STRING required: bool = False @field_validator("type", mode="after") @classmethod def validate_type(cls, v: SegmentType) -> SegmentType: if v not in _WEBHOOK_PARAMETER_ALLOWED_TYPES: raise ValueError(f"Unsupported webhook parameter type: {v}") return v class WebhookBodyParameter(BaseModel): """Body parameter with type information.""" name: str type: SegmentType = SegmentType.STRING required: bool = False @field_validator("type", mode="after") @classmethod def validate_type(cls, v: SegmentType) -> SegmentType: if v not in _WEBHOOK_BODY_ALLOWED_TYPES: raise ValueError(f"Unsupported webhook body parameter type: {v}") return v class WebhookData(BaseNodeData): """ Webhook Node Data. """ class SyncMode(StrEnum): SYNC = "async" # only support type: NodeType = NodeType.TRIGGER_WEBHOOK method: Method = Method.GET content_type: ContentType = Field(default=ContentType.JSON) headers: Sequence[WebhookParameter] = Field(default_factory=list) params: Sequence[WebhookParameter] = Field(default_factory=list) # query parameters body: Sequence[WebhookBodyParameter] = Field(default_factory=list) @field_validator("method", mode="before") @classmethod def normalize_method(cls, v) -> str: """Normalize HTTP method to lowercase to support both uppercase and lowercase input.""" if isinstance(v, str): return v.lower() return v @field_validator("headers", mode="after") @classmethod def validate_header_types(cls, v: Sequence[WebhookParameter]) -> Sequence[WebhookParameter]: for param in v: if param.type not in _WEBHOOK_HEADER_ALLOWED_TYPES: raise ValueError(f"Unsupported webhook header parameter type: {param.type}") return v @field_validator("params", mode="after") @classmethod def validate_query_parameter_types(cls, v: Sequence[WebhookParameter]) -> Sequence[WebhookParameter]: for param in v: if param.type not in _WEBHOOK_QUERY_PARAMETER_ALLOWED_TYPES: raise ValueError(f"Unsupported webhook query parameter type: {param.type}") return v status_code: int = 200 # Expected status code for response response_body: str = "" # Template for response body # Webhook specific fields (not from client data, set internally) webhook_id: str | None = None # Set when webhook trigger is created timeout: int = 30 # Timeout in seconds to wait for webhook response