From 4ff734f15f9afd0b54975b072711649d64ae5a45 Mon Sep 17 00:00:00 2001 From: Zhj Date: Fri, 15 Aug 2025 11:25:27 +0800 Subject: [PATCH] fix: where HTTP node URL, JSON text, and raw text template rendering could not find the corresponding rendering variables (#745) --- .../api/handler/coze/workflow_service_test.go | 78 ++- .../internal/canvas/convert/type_convert.go | 10 +- .../http_implicit_dependencies.json | 578 ++++++++++++++++++ .../internal/nodes/httprequester/adapt.go | 112 +++- .../nodes/httprequester/http_requester.go | 20 +- 5 files changed, 765 insertions(+), 33 deletions(-) create mode 100644 backend/domain/workflow/internal/canvas/examples/httprequester/http_implicit_dependencies.json diff --git a/backend/api/handler/coze/workflow_service_test.go b/backend/api/handler/coze/workflow_service_test.go index ae1ff51b..55ef45e5 100644 --- a/backend/api/handler/coze/workflow_service_test.go +++ b/backend/api/handler/coze/workflow_service_test.go @@ -4655,7 +4655,7 @@ func TestJsonSerializationDeserializationWithWarning(t *testing.T) { }) } -func TestSetAppVariablesFOrSubProcesses(t *testing.T) { +func TestSetAppVariablesForSubProcesses(t *testing.T) { mockey.PatchConvey("app variables for sub_process", t, func() { r := newWfTestRunner(t) defer r.closeFn() @@ -4672,3 +4672,79 @@ func TestSetAppVariablesFOrSubProcesses(t *testing.T) { }) } + +func TestHttpImplicitDependencies(t *testing.T) { + mockey.PatchConvey("test http implicit dependencies", t, func() { + r := newWfTestRunner(t) + defer r.closeFn() + + r.appVarS.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return("1.0", nil).AnyTimes() + + idStr := r.load("httprequester/http_implicit_dependencies.json") + + r.publish(idStr, "v0.0.1", true) + + runner := mockcode.NewMockRunner(r.ctrl) + runner.EXPECT().Run(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, request *coderunner.RunRequest) (*coderunner.RunResponse, error) { + in := request.Params["input"] + _ = in + result := make(map[string]any) + err := sonic.UnmarshalString(in.(string), &result) + if err != nil { + return nil, err + } + + return &coderunner.RunResponse{ + Result: result, + }, nil + }).AnyTimes() + + code.SetCodeRunner(runner) + + mockey.PatchConvey("test http node implicit dependencies", func() { + input := map[string]string{ + "input": "a", + } + result, _ := r.openapiSyncRun(idStr, input) + + batchRets := result["batch"].([]any) + loopRets := result["loop"].([]any) + + for _, r := range batchRets { + assert.Contains(t, []any{ + "http://echo.apifox.com/anything?aa=1.0&cc=1", + "http://echo.apifox.com/anything?aa=1.0&cc=2", + }, r) + } + for _, r := range loopRets { + assert.Contains(t, []any{ + "http://echo.apifox.com/anything?a=1&m=123", + "http://echo.apifox.com/anything?a=2&m=123", + }, r) + } + + }) + + mockey.PatchConvey("node debug http node implicit dependencies", func() { + exeID := r.nodeDebug(idStr, "109387", + withNDInput(map[string]string{ + "__apiInfo_url_87fc7c69536cae843fa7f5113cf0067b": "m", + "__apiInfo_url_ac86361e3cd503952e71986dc091fa6f": "a", + "__body_bodyData_json_ac86361e3cd503952e71986dc091fa6f": "b", + "__body_bodyData_json_f77817a7cf8441279e1cfd8af4eeb1da": "1", + })) + + e := r.getProcess(idStr, exeID, withSpecificNodeID("109387")) + e.assertSuccess() + + ret := make(map[string]any) + err := sonic.UnmarshalString(e.output, &ret) + assert.Nil(t, err) + err = sonic.UnmarshalString(ret["body"].(string), &ret) + assert.Nil(t, err) + assert.Equal(t, ret["url"].(string), "http://echo.apifox.com/anything?a=a&m=m") + + }) + + }) +} diff --git a/backend/domain/workflow/internal/canvas/convert/type_convert.go b/backend/domain/workflow/internal/canvas/convert/type_convert.go index cda4f596..6b2cf554 100644 --- a/backend/domain/workflow/internal/canvas/convert/type_convert.go +++ b/backend/domain/workflow/internal/canvas/convert/type_convert.go @@ -144,7 +144,7 @@ func CanvasBlockInputToTypeInfo(b *vo.BlockInput) (tInfo *vo.TypeInfo, err error } tInfo.Properties[subV.Name] = subTInfo } else if b.Value.Type == vo.BlockInputValueTypeObjectRef { - subV, err := parseParam(subVAny) + subV, err := ParseParam(subVAny) if err != nil { return nil, err } @@ -195,7 +195,7 @@ func CanvasBlockInputToFieldInfo(b *vo.BlockInput, path einoCompose.FieldPath, p for i := range paramList { paramAny := paramList[i] - param, err := parseParam(paramAny) + param, err := ParseParam(paramAny) if err != nil { return nil, err } @@ -343,7 +343,7 @@ func parseBlockInputRef(content any) (*vo.BlockInputReference, error) { return p, nil } -func parseParam(v any) (*vo.Param, error) { +func ParseParam(v any) (*vo.Param, error) { if pa, ok := v.(*vo.Param); ok { return pa, nil } @@ -497,7 +497,7 @@ func SetOutputTypesForNodeSchema(n *vo.Node, ns *schema.NodeSchema) error { func SetOutputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema) error { for _, vAny := range n.Data.Outputs { - param, err := parseParam(vAny) + param, err := ParseParam(vAny) if err != nil { return err } @@ -565,7 +565,7 @@ func BlockInputToNamedTypeInfo(name string, b *vo.BlockInput) (*vo.NamedTypeInfo } tInfo.Properties = append(tInfo.Properties, subNInfo) } else if b.Value.Type == vo.BlockInputValueTypeObjectRef { - subV, err := parseParam(subVAny) + subV, err := ParseParam(subVAny) if err != nil { return nil, err } diff --git a/backend/domain/workflow/internal/canvas/examples/httprequester/http_implicit_dependencies.json b/backend/domain/workflow/internal/canvas/examples/httprequester/http_implicit_dependencies.json new file mode 100644 index 00000000..752238d5 --- /dev/null +++ b/backend/domain/workflow/internal/canvas/examples/httprequester/http_implicit_dependencies.json @@ -0,0 +1,578 @@ +{ + "nodes": [ + { + "id": "100001", + "type": "1", + "meta": { + "position": { + "x": 180, + "y": 26.950000000000003 + } + }, + "data": { + "nodeMeta": { + "description": "工作流的起始节点,用于设定启动工作流需要的信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", + "subTitle": "", + "title": "开始" + }, + "outputs": [ + { + "type": "string", + "name": "input", + "required": false + } + ], + "trigger_parameters": [] + } + }, + { + "id": "900001", + "type": "2", + "meta": { + "position": { + "x": 2880, + "y": 13.950000000000003 + } + }, + "data": { + "nodeMeta": { + "description": "工作流的最终节点,用于返回工作流运行后的结果信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", + "subTitle": "", + "title": "结束" + }, + "inputs": { + "terminatePlan": "returnVariables", + "inputParameters": [ + { + "name": "batch", + "input": { + "type": "list", + "schema": { + "type": "string" + }, + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "125743", + "name": "output" + }, + "rawMeta": { + "type": 99 + } + } + } + }, + { + "name": "loop", + "input": { + "type": "list", + "schema": { + "type": "string" + }, + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "122230", + "name": "output" + }, + "rawMeta": { + "type": 99 + } + } + } + } + ] + } + } + }, + { + "id": "122230", + "type": "21", + "meta": { + "position": { + "x": 941.8355065195586, + "y": -65.71715145436308 + }, + "canvasPosition": { + "x": 560, + "y": 319.9 + } + }, + "data": { + "nodeMeta": { + "title": "循环", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Loop-v2.jpg", + "description": "用于通过设定循环次数和逻辑,重复执行一系列任务", + "mainColor": "#00B2B2", + "subTitle": "循环" + }, + "inputs": { + "loopType": "array", + "loopCount": { + "type": "integer", + "value": { + "type": "literal", + "content": "10" + } + }, + "variableParameters": [ + { + "name": "m", + "input": { + "type": "string", + "value": { + "type": "literal", + "content": "123", + "rawMeta": { + "type": 1 + } + } + } + } + ], + "inputParameters": [ + { + "name": "input", + "input": { + "type": "list", + "value": { + "type": "literal", + "content": "[\"1\",\"2\"]", + "rawMeta": { + "type": 99 + } + }, + "schema": { + "type": "string" + } + } + } + ] + }, + "outputs": [ + { + "name": "output", + "input": { + "type": "list", + "schema": { + "type": "string" + }, + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "164858", + "name": "url" + }, + "rawMeta": { + "type": 1 + } + } + } + } + ] + }, + "blocks": [ + { + "id": "109387", + "type": "45", + "meta": { + "position": { + "x": 180, + "y": 0 + } + }, + "data": { + "nodeMeta": { + "title": "HTTP 请求", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-HTTP.png", + "description": "用于发送API请求,从接口返回数据", + "mainColor": "#3071F2", + "subTitle": "HTTP 请求" + }, + "inputParameters": [], + "inputs": { + "apiInfo": { + "method": "GET", + "url": "http://echo.apifox.com/anything?a={{block_output_122230.input}}&m={{block_output_122230.m}}" + }, + "body": { + "bodyType": "JSON", + "bodyData": { + "binary": { + "fileURL": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "", + "name": "" + } + } + } + }, + "json": "{\n \"index\": {{block_output_122230.index}},\n \"value\": \"{{block_output_122230.input}}\"\n}" + } + }, + "headers": [], + "params": [], + "auth": { + "authType": "BEARER_AUTH", + "authData": { + "customData": { + "addTo": "header" + } + }, + "authOpen": false + }, + "setting": { + "timeout": 120, + "retryTimes": 3 + }, + "settingOnError": {} + }, + "outputs": [ + { + "type": "string", + "name": "body" + }, + { + "type": "integer", + "name": "statusCode" + }, + { + "type": "string", + "name": "headers" + } + ], + "settingOnError": {} + } + }, + { + "id": "164858", + "type": "5", + "meta": { + "position": { + "x": 640, + "y": 12 + } + }, + "data": { + "nodeMeta": { + "title": "代码_1", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Code-v2.jpg", + "description": "编写代码,处理输入变量来生成返回值", + "mainColor": "#00B2B2", + "subTitle": "代码" + }, + "inputs": { + "inputParameters": [ + { + "name": "input", + "input": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "109387", + "name": "body" + }, + "rawMeta": { + "type": 1 + } + } + } + } + ], + "code": "import json\nasync def main(args: Args) -> Output:\n params = args.params\n input_string = params['input']\n return json.loads(input_string)", + "language": 3, + "settingOnError": { + "processType": 1, + "timeoutMs": 60000, + "retryTimes": 0 + } + }, + "outputs": [ + { + "type": "object", + "name": "args", + "schema": [] + }, + { + "type": "string", + "name": "url" + } + ] + } + } + ], + "edges": [ + { + "sourceNodeID": "122230", + "targetNodeID": "109387", + "sourcePortID": "loop-function-inline-output" + }, + { + "sourceNodeID": "109387", + "targetNodeID": "164858" + }, + { + "sourceNodeID": "164858", + "targetNodeID": "122230", + "targetPortID": "loop-function-inline-input" + } + ] + }, + { + "id": "125743", + "type": "28", + "meta": { + "position": { + "x": 2090, + "y": 13 + }, + "canvasPosition": { + "x": 1680, + "y": 306.9 + } + }, + "data": { + "nodeMeta": { + "title": "批处理", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Batch-v2.jpg", + "description": "通过设定批量运行次数和逻辑,运行批处理体内的任务", + "mainColor": "#00B2B2", + "subTitle": "批处理" + }, + "inputs": { + "concurrentSize": { + "type": "integer", + "value": { + "type": "literal", + "content": "10" + } + }, + "batchSize": { + "type": "integer", + "value": { + "type": "literal", + "content": "100" + } + }, + "inputParameters": [ + { + "name": "input", + "input": { + "type": "list", + "value": { + "type": "literal", + "content": "[\"1\",\"2\"]", + "rawMeta": { + "type": 99 + } + }, + "schema": { + "type": "string" + } + } + } + ] + }, + "outputs": [ + { + "name": "output", + "input": { + "type": "list", + "schema": { + "type": "string" + }, + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "182572", + "name": "url" + }, + "rawMeta": { + "type": 1 + } + } + } + } + ] + }, + "blocks": [ + { + "id": "191311", + "type": "45", + "meta": { + "position": { + "x": 180, + "y": 0 + } + }, + "data": { + "nodeMeta": { + "title": "HTTP 请求_1", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-HTTP.png", + "description": "用于发送API请求,从接口返回数据", + "mainColor": "#3071F2", + "subTitle": "HTTP 请求" + }, + "inputParameters": [], + "inputs": { + "apiInfo": { + "method": "GET", + "url": "http://echo.apifox.com/anything?cc={{block_output_125743.input}}&aa={{global_variable_app[\"app\"]}}" + }, + "body": { + "bodyType": "JSON", + "bodyData": { + "binary": { + "fileURL": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "", + "name": "" + } + } + } + }, + "json": "{\n \"value\": {{block_output_125743.index}}\n}" + } + }, + "headers": [], + "params": [], + "auth": { + "authType": "BEARER_AUTH", + "authData": { + "customData": { + "addTo": "header" + } + }, + "authOpen": false + }, + "setting": { + "timeout": 120, + "retryTimes": 3 + }, + "settingOnError": {} + }, + "outputs": [ + { + "type": "string", + "name": "body" + }, + { + "type": "integer", + "name": "statusCode" + }, + { + "type": "string", + "name": "headers" + } + ], + "settingOnError": {} + } + }, + { + "id": "182572", + "type": "5", + "meta": { + "position": { + "x": 640, + "y": 12 + } + }, + "data": { + "nodeMeta": { + "title": "代码_3", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Code-v2.jpg", + "description": "编写代码,处理输入变量来生成返回值", + "mainColor": "#00B2B2", + "subTitle": "代码" + }, + "inputs": { + "inputParameters": [ + { + "name": "input", + "input": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "191311", + "name": "body" + }, + "rawMeta": { + "type": 1 + } + } + } + } + ], + "code": "import json\nasync def main(args: Args) -> Output:\n params = args.params\n input_string = params['input']\n return json.loads(input_string)", + "language": 3, + "settingOnError": { + "processType": 1, + "timeoutMs": 60000, + "retryTimes": 0 + } + }, + "outputs": [ + { + "type": "string", + "name": "url" + } + ] + } + } + ], + "edges": [ + { + "sourceNodeID": "125743", + "targetNodeID": "191311", + "sourcePortID": "batch-function-inline-output" + }, + { + "sourceNodeID": "191311", + "targetNodeID": "182572" + }, + { + "sourceNodeID": "182572", + "targetNodeID": "125743", + "targetPortID": "batch-function-inline-input" + } + ] + } + ], + "edges": [ + { + "sourceNodeID": "100001", + "targetNodeID": "122230" + }, + { + "sourceNodeID": "125743", + "targetNodeID": "900001", + "sourcePortID": "batch-output" + }, + { + "sourceNodeID": "122230", + "targetNodeID": "125743", + "sourcePortID": "loop-output" + } + ], + "versions": { + "loop": "v2" + } +} diff --git a/backend/domain/workflow/internal/nodes/httprequester/adapt.go b/backend/domain/workflow/internal/nodes/httprequester/adapt.go index 060df502..53428f6f 100644 --- a/backend/domain/workflow/internal/nodes/httprequester/adapt.go +++ b/backend/domain/workflow/internal/nodes/httprequester/adapt.go @@ -23,10 +23,12 @@ import ( "github.com/cloudwego/eino/compose" + "github.com/coze-dev/coze-studio/backend/domain/workflow/entity" "github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/convert" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" "github.com/coze-dev/coze-studio/backend/pkg/lang/crypto" + "github.com/coze-dev/coze-studio/backend/pkg/lang/ptr" ) var extractBracesRegexp = regexp.MustCompile(`\{\{(.*?)}}`) @@ -43,9 +45,10 @@ func extractBracesContent(s string) []string { } type ImplicitNodeDependency struct { - NodeID string - FieldPath compose.FieldPath - TypeInfo *vo.TypeInfo + NodeID string + FieldPath compose.FieldPath + TypeInfo *vo.TypeInfo + IsIntermediateVar bool } func extractImplicitDependency(node *vo.Node, canvas *vo.Canvas) ([]*ImplicitNodeDependency, error) { @@ -84,14 +87,24 @@ func extractImplicitDependency(node *vo.Node, canvas *vo.Canvas) ([]*ImplicitNod return nil, err } } + if node.Data.Inputs.Body.BodyType == string(BodyTypeRawText) { - rawTextVars := extractBracesContent(node.Data.Inputs.Body.BodyData.Json) + rawTextVars := extractBracesContent(node.Data.Inputs.Body.BodyData.RawText) err = extractDependenciesFromVars(rawTextVars) if err != nil { return nil, err } } + nodeID2ParentID := make(map[string]string) + for _, n := range canvas.Nodes { + if len(n.Blocks) > 0 { + for _, block := range n.Blocks { + nodeID2ParentID[block.ID] = n.ID + } + } + } + var nodeFinder func(nodes []*vo.Node, nodeID string) *vo.Node nodeFinder = func(nodes []*vo.Node, nodeID string) *vo.Node { for i := range nodes { @@ -111,23 +124,78 @@ func extractImplicitDependency(node *vo.Node, canvas *vo.Canvas) ([]*ImplicitNod if fNode == nil { continue } - tInfoMap := make(map[string]*vo.TypeInfo, len(node.Data.Outputs)) - for _, vAny := range fNode.Data.Outputs { - v, err := vo.ParseVariable(vAny) - if err != nil { - return nil, err + intermediateVars := map[string]bool{} + tInfoMap := make(map[string]*vo.TypeInfo) + parentID, ok := nodeID2ParentID[node.ID] + if ok && parentID == fNode.ID { + // when referencing a composite node, the index field type is available by default + tInfoMap["index"] = &vo.TypeInfo{ + Type: vo.DataTypeInteger, } - tInfo, err := convert.CanvasVariableToTypeInfo(v) - if err != nil { - return nil, err + for _, p := range fNode.Data.Inputs.InputParameters { + tInfo, err := convert.CanvasBlockInputToTypeInfo(p.Input) + if err != nil { + return nil, err + } + + if tInfo.Type != vo.DataTypeArray { + return nil, fmt.Errorf("when referencing a composite node, the input parameter must be an array type") + } + tInfoMap[p.Name] = tInfo.ElemTypeInfo + } + + if fNode.Data.Inputs.Loop != nil { + for _, vAny := range fNode.Data.Inputs.Loop.VariableParameters { + v, err := convert.ParseParam(vAny) + if err != nil { + return nil, err + } + tInfo, err := convert.CanvasBlockInputToTypeInfo(v.Input) + if err != nil { + return nil, err + } + tInfoMap[v.Name] = tInfo + + intermediateVars[v.Name] = true + } + } + + } else if entity.NodeTypeMetas[entity.IDStrToNodeType(fNode.Type)].IsComposite { + for _, vAny := range fNode.Data.Outputs { + v, err := convert.ParseParam(vAny) + if err != nil { + return nil, err + } + tInfo, err := convert.CanvasBlockInputToTypeInfo(v.Input) + if err != nil { + return nil, err + } + tInfoMap[v.Name] = tInfo + } + } else { + for _, vAny := range fNode.Data.Outputs { + v, err := vo.ParseVariable(vAny) + if err != nil { + return nil, err + } + tInfo, err := convert.CanvasVariableToTypeInfo(v) + if err != nil { + return nil, err + } + tInfoMap[v.Name] = tInfo } - tInfoMap[v.Name] = tInfo } + tInfo, ok := getTypeInfoByPath(ds.FieldPath[0], ds.FieldPath[1:], tInfoMap) if !ok { return nil, fmt.Errorf("cannot find type info for dependency: %s", ds.FieldPath) } + ds.TypeInfo = tInfo + if len(intermediateVars) > 0 { + ds.IsIntermediateVar = intermediateVars[strings.Join(ds.FieldPath, ".")] + } + } return dependencies, nil @@ -152,7 +220,7 @@ var globalVariableRegex = regexp.MustCompile(`global_variable_\w+\s*\["(.*?)"]`) func setHttpRequesterInputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema, implicitNodeDependencies []*ImplicitNodeDependency) (err error) { inputs := n.Data.Inputs implicitPathVars := make(map[string]bool) - addImplicitVarsSources := func(prefix string, vars []string) error { + addImplicitVarsSources := func(prefix string, vars []string, parent *vo.Node) error { for _, v := range vars { if strings.HasPrefix(v, "block_output_") { paths := strings.Split(strings.TrimPrefix(v, "block_output_"), ".") @@ -167,7 +235,8 @@ func setHttpRequesterInputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema, impl } implicitPathVars[pathValue] = true ns.SetInputType(pathValue, dep.TypeInfo) - ns.AddInputSource(&vo.FieldInfo{ + + filedInfo := &vo.FieldInfo{ Path: []string{pathValue}, Source: vo.FieldSource{ Ref: &vo.Reference{ @@ -175,7 +244,12 @@ func setHttpRequesterInputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema, impl FromPath: dep.FieldPath, }, }, - }) + } + + if dep.IsIntermediateVar && parent != nil { + filedInfo.Source.Ref.VariableType = ptr.Of(vo.ParentIntermediate) + } + ns.AddInputSource(filedInfo) } } } @@ -215,7 +289,7 @@ func setHttpRequesterInputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema, impl } urlVars := extractBracesContent(inputs.APIInfo.URL) - err = addImplicitVarsSources("__apiInfo_url_", urlVars) + err = addImplicitVarsSources("__apiInfo_url_", urlVars, n.Parent()) if err != nil { return err } @@ -304,13 +378,13 @@ func setHttpRequesterInputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema, impl ns.AddInputSource(sources...) case BodyTypeJSON: jsonVars := extractBracesContent(inputs.Body.BodyData.Json) - err = addImplicitVarsSources("__body_bodyData_json_", jsonVars) + err = addImplicitVarsSources("__body_bodyData_json_", jsonVars, n.Parent()) if err != nil { return err } case BodyTypeRawText: rawTextVars := extractBracesContent(inputs.Body.BodyData.RawText) - err = addImplicitVarsSources("__body_bodyData_rawText_", rawTextVars) + err = addImplicitVarsSources("__body_bodyData_rawText_", rawTextVars, n.Parent()) if err != nil { return err } diff --git a/backend/domain/workflow/internal/nodes/httprequester/http_requester.go b/backend/domain/workflow/internal/nodes/httprequester/http_requester.go index 9deac5c6..acee2c24 100644 --- a/backend/domain/workflow/internal/nodes/httprequester/http_requester.go +++ b/backend/domain/workflow/internal/nodes/httprequester/http_requester.go @@ -360,6 +360,11 @@ type HTTPRequester struct { md5FieldMapping MD5FieldMapping } +func adaptTemplate(template string) string { + return globalVariableReplaceRegexp.ReplaceAllString(template, "global_variable_$1.$2") + +} + func (hg *HTTPRequester) Invoke(ctx context.Context, input map[string]any) (output map[string]any, err error) { var ( req = &Request{} @@ -380,7 +385,7 @@ func (hg *HTTPRequester) Invoke(ctx context.Context, input map[string]any) (outp Header: http.Header{}, } - httpURL, err := nodes.TemplateRender(hg.urlConfig.Tpl, req.URLVars) + httpURL, err := nodes.TemplateRender(adaptTemplate(hg.urlConfig.Tpl), req.URLVars) if err != nil { return nil, err } @@ -524,7 +529,7 @@ func (b *BodyConfig) getBodyAndContentType(ctx context.Context, req *Request) (i switch b.BodyType { case BodyTypeJSON: - jsonString, err := nodes.TemplateRender(b.TextJsonConfig.Tpl, req.JsonVars) + jsonString, err := nodes.TemplateRender(adaptTemplate(b.TextJsonConfig.Tpl), req.JsonVars) if err != nil { return nil, contentType, err } @@ -539,7 +544,7 @@ func (b *BodyConfig) getBodyAndContentType(ctx context.Context, req *Request) (i body = strings.NewReader(form.Encode()) contentType = ContentTypeFormURLEncoded case BodyTypeRawText: - textString, err := nodes.TemplateRender(b.TextPlainConfig.Tpl, req.TextPlainVars) + textString, err := nodes.TemplateRender(adaptTemplate(b.TextPlainConfig.Tpl), req.TextPlainVars) if err != nil { return nil, contentType, err } @@ -632,7 +637,7 @@ func (hg *HTTPRequester) ToCallbackInput(_ context.Context, input map[string]any result := make(map[string]any) result["method"] = hg.method - u, err := nodes.TemplateRender(hg.urlConfig.Tpl, request.URLVars) + u, err := nodes.TemplateRender(adaptTemplate(hg.urlConfig.Tpl), request.URLVars) if err != nil { return nil, err } @@ -666,7 +671,7 @@ func (hg *HTTPRequester) ToCallbackInput(_ context.Context, input map[string]any result["body"] = nil switch hg.bodyConfig.BodyType { case BodyTypeJSON: - js, err := nodes.TemplateRender(hg.bodyConfig.TextJsonConfig.Tpl, request.JsonVars) + js, err := nodes.TemplateRender(adaptTemplate(hg.bodyConfig.TextJsonConfig.Tpl), request.JsonVars) if err != nil { return nil, err } @@ -677,7 +682,7 @@ func (hg *HTTPRequester) ToCallbackInput(_ context.Context, input map[string]any } result["body"] = ret case BodyTypeRawText: - tx, err := nodes.TemplateRender(hg.bodyConfig.TextPlainConfig.Tpl, request.TextPlainVars) + tx, err := nodes.TemplateRender(adaptTemplate(hg.bodyConfig.TextPlainConfig.Tpl), request.TextPlainVars) if err != nil { return nil, err @@ -729,7 +734,7 @@ func (hg *HTTPRequester) parserToRequest(input map[string]any) (*Request, error) if strings.HasPrefix(urlKey, "global_variable_") { urlKey = globalVariableReplaceRegexp.ReplaceAllString(urlKey, "global_variable_$1.$2") } - nodes.SetMapValue(request.URLVars, strings.Split(urlKey, "."), value.(string)) + nodes.SetMapValue(request.URLVars, strings.Split(urlKey, "."), value) } } if strings.HasPrefix(key, headersPrefix) { @@ -777,7 +782,6 @@ func (hg *HTTPRequester) parserToRequest(input map[string]any) (*Request, error) if formDataKey, ok := hg.md5FieldMapping.BodyMD5Mapping[formDataMd5Key]; ok { request.FormDataVars[formDataKey] = value.(string) } - } if strings.HasPrefix(bodyKey, bodyFormURLEncodedPrefix) {