refactor: how to add a node type in workflow (#558)

This commit is contained in:
shentongmartin
2025-08-05 14:02:33 +08:00
committed by GitHub
parent 5dafd81a3f
commit bb6ff0026b
96 changed files with 8305 additions and 8717 deletions

View File

@@ -0,0 +1,340 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package httprequester
import (
"fmt"
"regexp"
"strings"
"github.com/cloudwego/eino/compose"
"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"
)
var extractBracesRegexp = regexp.MustCompile(`\{\{(.*?)}}`)
func extractBracesContent(s string) []string {
matches := extractBracesRegexp.FindAllStringSubmatch(s, -1)
var result []string
for _, match := range matches {
if len(match) >= 2 {
result = append(result, match[1])
}
}
return result
}
type ImplicitNodeDependency struct {
NodeID string
FieldPath compose.FieldPath
TypeInfo *vo.TypeInfo
}
func extractImplicitDependency(node *vo.Node, canvas *vo.Canvas) ([]*ImplicitNodeDependency, error) {
dependencies := make([]*ImplicitNodeDependency, 0, len(canvas.Nodes))
url := node.Data.Inputs.APIInfo.URL
urlVars := extractBracesContent(url)
hasReferred := make(map[string]bool)
extractDependenciesFromVars := func(vars []string) error {
for _, v := range vars {
if strings.HasPrefix(v, "block_output_") {
paths := strings.Split(strings.TrimPrefix(v, "block_output_"), ".")
if len(paths) < 2 {
return fmt.Errorf("invalid block_output_ variable: %s", v)
}
if hasReferred[v] {
continue
}
hasReferred[v] = true
dependencies = append(dependencies, &ImplicitNodeDependency{
NodeID: paths[0],
FieldPath: paths[1:],
})
}
}
return nil
}
err := extractDependenciesFromVars(urlVars)
if err != nil {
return nil, err
}
if node.Data.Inputs.Body.BodyType == string(BodyTypeJSON) {
jsonVars := extractBracesContent(node.Data.Inputs.Body.BodyData.Json)
err = extractDependenciesFromVars(jsonVars)
if err != nil {
return nil, err
}
}
if node.Data.Inputs.Body.BodyType == string(BodyTypeRawText) {
rawTextVars := extractBracesContent(node.Data.Inputs.Body.BodyData.Json)
err = extractDependenciesFromVars(rawTextVars)
if err != nil {
return nil, err
}
}
var nodeFinder func(nodes []*vo.Node, nodeID string) *vo.Node
nodeFinder = func(nodes []*vo.Node, nodeID string) *vo.Node {
for i := range nodes {
if nodes[i].ID == nodeID {
return nodes[i]
}
if len(nodes[i].Blocks) > 0 {
if n := nodeFinder(nodes[i].Blocks, nodeID); n != nil {
return n
}
}
}
return nil
}
for _, ds := range dependencies {
fNode := nodeFinder(canvas.Nodes, ds.NodeID)
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
}
tInfo, err := convert.CanvasVariableToTypeInfo(v)
if err != nil {
return nil, err
}
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
}
return dependencies, nil
}
func getTypeInfoByPath(root string, properties []string, tInfoMap map[string]*vo.TypeInfo) (*vo.TypeInfo, bool) {
if len(properties) == 0 {
if tInfo, ok := tInfoMap[root]; ok {
return tInfo, true
}
return nil, false
}
tInfo, ok := tInfoMap[root]
if !ok {
return nil, false
}
return getTypeInfoByPath(properties[0], properties[1:], tInfo.Properties)
}
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 {
for _, v := range vars {
if strings.HasPrefix(v, "block_output_") {
paths := strings.Split(strings.TrimPrefix(v, "block_output_"), ".")
if len(paths) < 2 {
return fmt.Errorf("invalid implicit var : %s", v)
}
for _, dep := range implicitNodeDependencies {
if dep.NodeID == paths[0] && strings.Join(dep.FieldPath, ".") == strings.Join(paths[1:], ".") {
pathValue := prefix + crypto.MD5HexValue(v)
if _, visited := implicitPathVars[pathValue]; visited {
continue
}
implicitPathVars[pathValue] = true
ns.SetInputType(pathValue, dep.TypeInfo)
ns.AddInputSource(&vo.FieldInfo{
Path: []string{pathValue},
Source: vo.FieldSource{
Ref: &vo.Reference{
FromNodeKey: vo.NodeKey(dep.NodeID),
FromPath: dep.FieldPath,
},
},
})
}
}
}
if strings.HasPrefix(v, "global_variable_") {
matches := globalVariableRegex.FindStringSubmatch(v)
if len(matches) < 2 {
continue
}
var varType vo.GlobalVarType
if strings.HasPrefix(v, string(vo.RefSourceTypeGlobalApp)) {
varType = vo.GlobalAPP
} else if strings.HasPrefix(v, string(vo.RefSourceTypeGlobalUser)) {
varType = vo.GlobalUser
} else if strings.HasPrefix(v, string(vo.RefSourceTypeGlobalSystem)) {
varType = vo.GlobalSystem
} else {
return fmt.Errorf("invalid global variable type: %s", v)
}
source := vo.FieldSource{
Ref: &vo.Reference{
VariableType: &varType,
FromPath: []string{matches[1]},
},
}
ns.AddInputSource(&vo.FieldInfo{
Path: []string{prefix + crypto.MD5HexValue(v)},
Source: source,
})
}
}
return nil
}
urlVars := extractBracesContent(inputs.APIInfo.URL)
err = addImplicitVarsSources("__apiInfo_url_", urlVars)
if err != nil {
return err
}
err = applyParamsToSchema(ns, "__headers_", inputs.Headers, n.Parent())
if err != nil {
return err
}
err = applyParamsToSchema(ns, "__params_", inputs.Params, n.Parent())
if err != nil {
return err
}
if inputs.Auth != nil && inputs.Auth.AuthOpen {
authData := inputs.Auth.AuthData
const bearerTokenKey = "__auth_authData_bearerTokenData_token"
if inputs.Auth.AuthType == "BEARER_AUTH" {
bearTokenParam := authData.BearerTokenData[0]
tInfo, err := convert.CanvasBlockInputToTypeInfo(bearTokenParam.Input)
if err != nil {
return err
}
ns.SetInputType(bearerTokenKey, tInfo)
sources, err := convert.CanvasBlockInputToFieldInfo(bearTokenParam.Input, compose.FieldPath{bearerTokenKey}, n.Parent())
if err != nil {
return err
}
ns.AddInputSource(sources...)
}
if inputs.Auth.AuthType == "CUSTOM_AUTH" {
const (
customDataDataKey = "__auth_authData_customData_data_Key"
customDataDataValue = "__auth_authData_customData_data_Value"
)
dataParams := authData.CustomData.Data
keyParam := dataParams[0]
keyTypeInfo, err := convert.CanvasBlockInputToTypeInfo(keyParam.Input)
if err != nil {
return err
}
ns.SetInputType(customDataDataKey, keyTypeInfo)
sources, err := convert.CanvasBlockInputToFieldInfo(keyParam.Input, compose.FieldPath{customDataDataKey}, n.Parent())
if err != nil {
return err
}
ns.AddInputSource(sources...)
valueParam := dataParams[1]
valueTypeInfo, err := convert.CanvasBlockInputToTypeInfo(valueParam.Input)
if err != nil {
return err
}
ns.SetInputType(customDataDataValue, valueTypeInfo)
sources, err = convert.CanvasBlockInputToFieldInfo(valueParam.Input, compose.FieldPath{customDataDataValue}, n.Parent())
if err != nil {
return err
}
ns.AddInputSource(sources...)
}
}
switch BodyType(inputs.Body.BodyType) {
case BodyTypeFormData:
err = applyParamsToSchema(ns, "__body_bodyData_formData_", inputs.Body.BodyData.FormData.Data, n.Parent())
if err != nil {
return err
}
case BodyTypeFormURLEncoded:
err = applyParamsToSchema(ns, "__body_bodyData_formURLEncoded_", inputs.Body.BodyData.FormURLEncoded, n.Parent())
if err != nil {
return err
}
case BodyTypeBinary:
const fileURLName = "__body_bodyData_binary_fileURL"
fileURLInput := inputs.Body.BodyData.Binary.FileURL
ns.SetInputType(fileURLName, &vo.TypeInfo{
Type: vo.DataTypeString,
})
sources, err := convert.CanvasBlockInputToFieldInfo(fileURLInput, compose.FieldPath{fileURLName}, n.Parent())
if err != nil {
return err
}
ns.AddInputSource(sources...)
case BodyTypeJSON:
jsonVars := extractBracesContent(inputs.Body.BodyData.Json)
err = addImplicitVarsSources("__body_bodyData_json_", jsonVars)
if err != nil {
return err
}
case BodyTypeRawText:
rawTextVars := extractBracesContent(inputs.Body.BodyData.RawText)
err = addImplicitVarsSources("__body_bodyData_rawText_", rawTextVars)
if err != nil {
return err
}
}
return nil
}
func applyParamsToSchema(ns *schema.NodeSchema, prefix string, params []*vo.Param, parentNode *vo.Node) error {
for i := range params {
param := params[i]
name := param.Name
tInfo, err := convert.CanvasBlockInputToTypeInfo(param.Input)
if err != nil {
return err
}
fieldName := prefix + crypto.MD5HexValue(name)
ns.SetInputType(fieldName, tInfo)
sources, err := convert.CanvasBlockInputToFieldInfo(param.Input, compose.FieldPath{fieldName}, parentNode)
if err != nil {
return err
}
ns.AddInputSource(sources...)
}
return nil
}

View File

@@ -31,9 +31,14 @@ import (
"strings"
"time"
"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/nodes"
"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"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
"github.com/coze-dev/coze-studio/backend/pkg/sonic"
)
@@ -129,7 +134,7 @@ type Request struct {
FileURL *string
}
var globalVariableReplaceRegexp = regexp.MustCompile(`global_variable_(\w+)\["(\w+)"\]`)
var globalVariableReplaceRegexp = regexp.MustCompile(`global_variable_(\w+)\["(\w+)"]`)
type MD5FieldMapping struct {
HeaderMD5Mapping map[string]string `json:"header_md_5_mapping,omitempty"` // md5 vs key
@@ -184,49 +189,188 @@ type Config struct {
Timeout time.Duration
RetryTimes uint64
IgnoreException bool
DefaultOutput map[string]any
MD5FieldMapping
}
type HTTPRequester struct {
client *http.Client
config *Config
}
func NewHTTPRequester(_ context.Context, cfg *Config) (*HTTPRequester, error) {
if cfg == nil {
return nil, fmt.Errorf("config is requried")
func (c *Config) Adapt(_ context.Context, n *vo.Node, opts ...nodes.AdaptOption) (*schema.NodeSchema, error) {
options := nodes.GetAdaptOptions(opts...)
if options.Canvas == nil {
return nil, fmt.Errorf("canvas is requried when adapting HTTPRequester node")
}
if len(cfg.Method) == 0 {
implicitDeps, err := extractImplicitDependency(n, options.Canvas)
if err != nil {
return nil, err
}
ns := &schema.NodeSchema{
Key: vo.NodeKey(n.ID),
Type: entity.NodeTypeHTTPRequester,
Name: n.Data.Meta.Title,
Configs: c,
}
inputs := n.Data.Inputs
md5FieldMapping := &MD5FieldMapping{}
method := inputs.APIInfo.Method
c.Method = method
reqURL := inputs.APIInfo.URL
c.URLConfig = URLConfig{
Tpl: strings.TrimSpace(reqURL),
}
urlVars := extractBracesContent(reqURL)
md5FieldMapping.SetURLFields(urlVars...)
md5FieldMapping.SetHeaderFields(slices.Transform(inputs.Headers, func(a *vo.Param) string {
return a.Name
})...)
md5FieldMapping.SetParamFields(slices.Transform(inputs.Params, func(a *vo.Param) string {
return a.Name
})...)
if inputs.Auth != nil && inputs.Auth.AuthOpen {
auth := &AuthenticationConfig{}
ty, err := convertAuthType(inputs.Auth.AuthType)
if err != nil {
return nil, err
}
auth.Type = ty
location, err := convertLocation(inputs.Auth.AuthData.CustomData.AddTo)
if err != nil {
return nil, err
}
auth.Location = location
c.AuthConfig = auth
}
bodyConfig := BodyConfig{}
bodyConfig.BodyType = BodyType(inputs.Body.BodyType)
switch BodyType(inputs.Body.BodyType) {
case BodyTypeJSON:
jsonTpl := inputs.Body.BodyData.Json
bodyConfig.TextJsonConfig = &TextJsonConfig{
Tpl: jsonTpl,
}
jsonVars := extractBracesContent(jsonTpl)
md5FieldMapping.SetBodyFields(jsonVars...)
case BodyTypeFormData:
bodyConfig.FormDataConfig = &FormDataConfig{
FileTypeMapping: map[string]bool{},
}
formDataVars := make([]string, 0)
for i := range inputs.Body.BodyData.FormData.Data {
p := inputs.Body.BodyData.FormData.Data[i]
formDataVars = append(formDataVars, p.Name)
if p.Input.Type == vo.VariableTypeString && p.Input.AssistType > vo.AssistTypeNotSet && p.Input.AssistType < vo.AssistTypeTime {
bodyConfig.FormDataConfig.FileTypeMapping[p.Name] = true
}
}
md5FieldMapping.SetBodyFields(formDataVars...)
case BodyTypeRawText:
TextTpl := inputs.Body.BodyData.RawText
bodyConfig.TextPlainConfig = &TextPlainConfig{
Tpl: TextTpl,
}
textPlainVars := extractBracesContent(TextTpl)
md5FieldMapping.SetBodyFields(textPlainVars...)
case BodyTypeFormURLEncoded:
formURLEncodedVars := make([]string, 0)
for _, p := range inputs.Body.BodyData.FormURLEncoded {
formURLEncodedVars = append(formURLEncodedVars, p.Name)
}
md5FieldMapping.SetBodyFields(formURLEncodedVars...)
}
c.BodyConfig = bodyConfig
c.MD5FieldMapping = *md5FieldMapping
if inputs.Setting != nil {
c.Timeout = time.Duration(inputs.Setting.Timeout) * time.Second
c.RetryTimes = uint64(inputs.Setting.RetryTimes)
}
if err := setHttpRequesterInputsForNodeSchema(n, ns, implicitDeps); err != nil {
return nil, err
}
if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil {
return nil, err
}
return ns, nil
}
func convertAuthType(auth string) (AuthType, error) {
switch auth {
case "CUSTOM_AUTH":
return Custom, nil
case "BEARER_AUTH":
return BearToken, nil
default:
return AuthType(0), fmt.Errorf("invalid auth type")
}
}
func convertLocation(l string) (Location, error) {
switch l {
case "header":
return Header, nil
case "query":
return QueryParam, nil
default:
return 0, fmt.Errorf("invalid location")
}
}
func (c *Config) Build(_ context.Context, _ *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
if len(c.Method) == 0 {
return nil, fmt.Errorf("method is requried")
}
hg := &HTTPRequester{}
hg := &HTTPRequester{
urlConfig: c.URLConfig,
method: c.Method,
retryTimes: c.RetryTimes,
authConfig: c.AuthConfig,
bodyConfig: c.BodyConfig,
md5FieldMapping: c.MD5FieldMapping,
}
client := http.DefaultClient
if cfg.Timeout > 0 {
client.Timeout = cfg.Timeout
if c.Timeout > 0 {
client.Timeout = c.Timeout
}
hg.client = client
hg.config = cfg
return hg, nil
}
type HTTPRequester struct {
client *http.Client
urlConfig URLConfig
authConfig *AuthenticationConfig
bodyConfig BodyConfig
method string
retryTimes uint64
md5FieldMapping MD5FieldMapping
}
func (hg *HTTPRequester) Invoke(ctx context.Context, input map[string]any) (output map[string]any, err error) {
var (
req = &Request{}
method = hg.config.Method
retryTimes = hg.config.RetryTimes
method = hg.method
retryTimes = hg.retryTimes
body io.ReadCloser
contentType string
response *http.Response
)
req, err = hg.config.parserToRequest(input)
req, err = hg.parserToRequest(input)
if err != nil {
return nil, err
}
@@ -236,7 +380,7 @@ func (hg *HTTPRequester) Invoke(ctx context.Context, input map[string]any) (outp
Header: http.Header{},
}
httpURL, err := nodes.TemplateRender(hg.config.URLConfig.Tpl, req.URLVars)
httpURL, err := nodes.TemplateRender(hg.urlConfig.Tpl, req.URLVars)
if err != nil {
return nil, err
}
@@ -255,8 +399,8 @@ func (hg *HTTPRequester) Invoke(ctx context.Context, input map[string]any) (outp
params.Set(key, value)
}
if hg.config.AuthConfig != nil {
httpRequest.Header, params, err = hg.config.AuthConfig.addAuthentication(ctx, req.Authentication, httpRequest.Header, params)
if hg.authConfig != nil {
httpRequest.Header, params, err = hg.authConfig.addAuthentication(ctx, req.Authentication, httpRequest.Header, params)
if err != nil {
return nil, err
}
@@ -264,7 +408,7 @@ func (hg *HTTPRequester) Invoke(ctx context.Context, input map[string]any) (outp
u.RawQuery = params.Encode()
httpRequest.URL = u
body, contentType, err = hg.config.BodyConfig.getBodyAndContentType(ctx, req)
body, contentType, err = hg.bodyConfig.getBodyAndContentType(ctx, req)
if err != nil {
return nil, err
}
@@ -479,18 +623,16 @@ func httpGet(ctx context.Context, url string) (*http.Response, error) {
}
func (hg *HTTPRequester) ToCallbackInput(_ context.Context, input map[string]any) (map[string]any, error) {
var (
request = &Request{}
config = hg.config
)
request, err := hg.config.parserToRequest(input)
var request = &Request{}
request, err := hg.parserToRequest(input)
if err != nil {
return nil, err
}
result := make(map[string]any)
result["method"] = config.Method
result["method"] = hg.method
u, err := nodes.TemplateRender(config.URLConfig.Tpl, request.URLVars)
u, err := nodes.TemplateRender(hg.urlConfig.Tpl, request.URLVars)
if err != nil {
return nil, err
}
@@ -508,13 +650,13 @@ func (hg *HTTPRequester) ToCallbackInput(_ context.Context, input map[string]any
}
result["header"] = headers
result["auth"] = nil
if config.AuthConfig != nil {
if config.AuthConfig.Type == Custom {
if hg.authConfig != nil {
if hg.authConfig.Type == Custom {
result["auth"] = map[string]interface{}{
"Key": request.Authentication.Key,
"Value": request.Authentication.Value,
}
} else if config.AuthConfig.Type == BearToken {
} else if hg.authConfig.Type == BearToken {
result["auth"] = map[string]interface{}{
"token": request.Authentication.Token,
}
@@ -522,9 +664,9 @@ func (hg *HTTPRequester) ToCallbackInput(_ context.Context, input map[string]any
}
result["body"] = nil
switch config.BodyConfig.BodyType {
switch hg.bodyConfig.BodyType {
case BodyTypeJSON:
js, err := nodes.TemplateRender(config.BodyConfig.TextJsonConfig.Tpl, request.JsonVars)
js, err := nodes.TemplateRender(hg.bodyConfig.TextJsonConfig.Tpl, request.JsonVars)
if err != nil {
return nil, err
}
@@ -535,7 +677,7 @@ func (hg *HTTPRequester) ToCallbackInput(_ context.Context, input map[string]any
}
result["body"] = ret
case BodyTypeRawText:
tx, err := nodes.TemplateRender(config.BodyConfig.TextPlainConfig.Tpl, request.TextPlainVars)
tx, err := nodes.TemplateRender(hg.bodyConfig.TextPlainConfig.Tpl, request.TextPlainVars)
if err != nil {
return nil, err
@@ -569,7 +711,7 @@ const (
bodyBinaryFileURLPrefix = "binary_fileURL"
)
func (cfg *Config) parserToRequest(input map[string]any) (*Request, error) {
func (hg *HTTPRequester) parserToRequest(input map[string]any) (*Request, error) {
request := &Request{
URLVars: make(map[string]any),
Headers: make(map[string]string),
@@ -583,7 +725,7 @@ func (cfg *Config) parserToRequest(input map[string]any) (*Request, error) {
for key, value := range input {
if strings.HasPrefix(key, apiInfoURLPrefix) {
urlMD5 := strings.TrimPrefix(key, apiInfoURLPrefix)
if urlKey, ok := cfg.URLMD5Mapping[urlMD5]; ok {
if urlKey, ok := hg.md5FieldMapping.URLMD5Mapping[urlMD5]; ok {
if strings.HasPrefix(urlKey, "global_variable_") {
urlKey = globalVariableReplaceRegexp.ReplaceAllString(urlKey, "global_variable_$1.$2")
}
@@ -592,13 +734,13 @@ func (cfg *Config) parserToRequest(input map[string]any) (*Request, error) {
}
if strings.HasPrefix(key, headersPrefix) {
headerKeyMD5 := strings.TrimPrefix(key, headersPrefix)
if headerKey, ok := cfg.HeaderMD5Mapping[headerKeyMD5]; ok {
if headerKey, ok := hg.md5FieldMapping.HeaderMD5Mapping[headerKeyMD5]; ok {
request.Headers[headerKey] = value.(string)
}
}
if strings.HasPrefix(key, paramsPrefix) {
paramKeyMD5 := strings.TrimPrefix(key, paramsPrefix)
if paramKey, ok := cfg.ParamMD5Mapping[paramKeyMD5]; ok {
if paramKey, ok := hg.md5FieldMapping.ParamMD5Mapping[paramKeyMD5]; ok {
request.Params[paramKey] = value.(string)
}
}
@@ -622,7 +764,7 @@ func (cfg *Config) parserToRequest(input map[string]any) (*Request, error) {
bodyKey := strings.TrimPrefix(key, bodyDataPrefix)
if strings.HasPrefix(bodyKey, bodyJsonPrefix) {
jsonMd5Key := strings.TrimPrefix(bodyKey, bodyJsonPrefix)
if jsonKey, ok := cfg.BodyMD5Mapping[jsonMd5Key]; ok {
if jsonKey, ok := hg.md5FieldMapping.BodyMD5Mapping[jsonMd5Key]; ok {
if strings.HasPrefix(jsonKey, "global_variable_") {
jsonKey = globalVariableReplaceRegexp.ReplaceAllString(jsonKey, "global_variable_$1.$2")
}
@@ -632,7 +774,7 @@ func (cfg *Config) parserToRequest(input map[string]any) (*Request, error) {
}
if strings.HasPrefix(bodyKey, bodyFormDataPrefix) {
formDataMd5Key := strings.TrimPrefix(bodyKey, bodyFormDataPrefix)
if formDataKey, ok := cfg.BodyMD5Mapping[formDataMd5Key]; ok {
if formDataKey, ok := hg.md5FieldMapping.BodyMD5Mapping[formDataMd5Key]; ok {
request.FormDataVars[formDataKey] = value.(string)
}
@@ -640,14 +782,14 @@ func (cfg *Config) parserToRequest(input map[string]any) (*Request, error) {
if strings.HasPrefix(bodyKey, bodyFormURLEncodedPrefix) {
formURLEncodeMd5Key := strings.TrimPrefix(bodyKey, bodyFormURLEncodedPrefix)
if formURLEncodeKey, ok := cfg.BodyMD5Mapping[formURLEncodeMd5Key]; ok {
if formURLEncodeKey, ok := hg.md5FieldMapping.BodyMD5Mapping[formURLEncodeMd5Key]; ok {
request.FormURLEncodedVars[formURLEncodeKey] = value.(string)
}
}
if strings.HasPrefix(bodyKey, bodyRawTextPrefix) {
rawTextMd5Key := strings.TrimPrefix(bodyKey, bodyRawTextPrefix)
if rawTextKey, ok := cfg.BodyMD5Mapping[rawTextMd5Key]; ok {
if rawTextKey, ok := hg.md5FieldMapping.BodyMD5Mapping[rawTextMd5Key]; ok {
if strings.HasPrefix(rawTextKey, "global_variable_") {
rawTextKey = globalVariableReplaceRegexp.ReplaceAllString(rawTextKey, "global_variable_$1.$2")
}

View File

@@ -28,6 +28,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema"
"github.com/coze-dev/coze-studio/backend/pkg/lang/crypto"
)
@@ -68,7 +69,7 @@ func TestInvoke(t *testing.T) {
},
},
}
hg, err := NewHTTPRequester(context.Background(), cfg)
hg, err := cfg.Build(context.Background(), &schema.NodeSchema{})
assert.NoError(t, err)
m := map[string]any{
"__apiInfo_url_" + crypto.MD5HexValue("url_v1"): "v1",
@@ -78,7 +79,7 @@ func TestInvoke(t *testing.T) {
"__params_" + crypto.MD5HexValue("p2"): "v2",
}
result, err := hg.Invoke(context.Background(), m)
result, err := hg.(*HTTPRequester).Invoke(context.Background(), m)
assert.NoError(t, err)
assert.Equal(t, `{"message":"success"}`, result["body"])
assert.Equal(t, int64(200), result["statusCode"])
@@ -157,7 +158,7 @@ func TestInvoke(t *testing.T) {
}
// Create an HTTPRequest instance
hg, err := NewHTTPRequester(context.Background(), cfg)
hg, err := cfg.Build(context.Background(), &schema.NodeSchema{})
assert.NoError(t, err)
m := map[string]any{
@@ -171,7 +172,7 @@ func TestInvoke(t *testing.T) {
"__body_bodyData_formData_" + crypto.MD5HexValue("fileURL"): fileServer.URL,
}
result, err := hg.Invoke(context.Background(), m)
result, err := hg.(*HTTPRequester).Invoke(context.Background(), m)
assert.NoError(t, err)
assert.Equal(t, `{"message":"success"}`, result["body"])
assert.Equal(t, int64(200), result["statusCode"])
@@ -228,7 +229,7 @@ func TestInvoke(t *testing.T) {
},
},
}
hg, err := NewHTTPRequester(context.Background(), cfg)
hg, err := cfg.Build(context.Background(), &schema.NodeSchema{})
assert.NoError(t, err)
m := map[string]any{
@@ -241,7 +242,7 @@ func TestInvoke(t *testing.T) {
"__body_bodyData_rawText_" + crypto.MD5HexValue("v2"): "v2",
}
result, err := hg.Invoke(context.Background(), m)
result, err := hg.(*HTTPRequester).Invoke(context.Background(), m)
assert.NoError(t, err)
assert.Equal(t, `{"message":"success"}`, result["body"])
assert.Equal(t, int64(200), result["statusCode"])
@@ -303,7 +304,7 @@ func TestInvoke(t *testing.T) {
}
// Create an HTTPRequest instance
hg, err := NewHTTPRequester(context.Background(), cfg)
hg, err := cfg.Build(context.Background(), &schema.NodeSchema{})
assert.NoError(t, err)
m := map[string]any{
@@ -316,7 +317,7 @@ func TestInvoke(t *testing.T) {
"__body_bodyData_json_" + crypto.MD5HexValue("v2"): "v2",
}
result, err := hg.Invoke(context.Background(), m)
result, err := hg.(*HTTPRequester).Invoke(context.Background(), m)
assert.NoError(t, err)
assert.Equal(t, `{"message":"success"}`, result["body"])
assert.Equal(t, int64(200), result["statusCode"])
@@ -376,7 +377,7 @@ func TestInvoke(t *testing.T) {
}
// Create an HTTPRequest instance
hg, err := NewHTTPRequester(context.Background(), cfg)
hg, err := cfg.Build(context.Background(), &schema.NodeSchema{})
assert.NoError(t, err)
m := map[string]any{
@@ -388,7 +389,7 @@ func TestInvoke(t *testing.T) {
"__body_bodyData_binary_fileURL" + crypto.MD5HexValue("v1"): fileServer.URL,
}
result, err := hg.Invoke(context.Background(), m)
result, err := hg.(*HTTPRequester).Invoke(context.Background(), m)
assert.NoError(t, err)
assert.Equal(t, `{"message":"success"}`, result["body"])
assert.Equal(t, int64(200), result["statusCode"])