{"record":{"id":"c1433e4d6b0ea10c","repo":"fatedier/frp","slug":"exactly-one-proxy-type-block-is-required","errorCode":null,"errorMessage":"exactly one proxy type block is required","messagePattern":"exactly one proxy type block is required","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"client/http/model/proxy_definition.go","lineNumber":37,"sourceCode":"\tSTCP   *v1.STCPProxyConfig   `json:\"stcp,omitempty\"`\n\tSUDP   *v1.SUDPProxyConfig   `json:\"sudp,omitempty\"`\n\tXTCP   *v1.XTCPProxyConfig   `json:\"xtcp,omitempty\"`\n}\n\nfunc (p *ProxyDefinition) Validate(pathName string, isUpdate bool) error {\n\tif strings.TrimSpace(p.Name) == \"\" {\n\t\treturn fmt.Errorf(\"proxy name is required\")\n\t}\n\tif !IsProxyType(p.Type) {\n\t\treturn fmt.Errorf(\"invalid proxy type: %s\", p.Type)\n\t}\n\tif isUpdate && pathName != \"\" && pathName != p.Name {\n\t\treturn fmt.Errorf(\"proxy name in URL must match name in body\")\n\t}\n\n\t_, blockType, blockCount := p.activeBlock()\n\tif blockCount != 1 {\n\t\treturn fmt.Errorf(\"exactly one proxy type block is required\")\n\t}\n\tif blockType != p.Type {\n\t\treturn fmt.Errorf(\"proxy type block %q does not match type %q\", blockType, p.Type)\n\t}\n\treturn nil\n}\n\nfunc (p *ProxyDefinition) ToConfigurer() (v1.ProxyConfigurer, error) {\n\tblock, _, _ := p.activeBlock()\n\tif block == nil {\n\t\treturn nil, fmt.Errorf(\"exactly one proxy type block is required\")\n\t}\n\n\tcfg := block\n\tcfg.GetBaseConfig().Name = p.Name\n\tcfg.GetBaseConfig().Type = p.Type\n\treturn cfg, nil\n}","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/client/http/model/proxy_definition.go#L19-L55","documentation":"Returned by ProxyDefinition.Validate when the number of populated type blocks (tcp/udp/http/https/tcpmux/stcp/sudp/xtcp) in a proxy definition is not exactly one. Both zero blocks (only top-level name/type given) and two or more blocks (e.g. both \"tcp\" and \"http\" set) produce this error.","triggerScenarios":"POST/PUT /api/proxies with {\"name\":\"x\",\"type\":\"tcp\"} but no \"tcp\" block; or {\"name\":\"x\",\"type\":\"tcp\",\"tcp\":{...},\"http\":{...}} where two blocks are non-null.","commonSituations":"Forgetting the nested config block entirely; switching a proxy from http to tcp by changing \"type\" and adding the new block while forgetting to null out the old one; JSON merge of definitions accumulating blocks.","solutions":["Include exactly one type block matching the \"type\" field, e.g. \"tcp\": {\"localPort\": 22, \"remotePort\": 6000} for type tcp.","When changing proxy type, replace (not merge) the definition so the old block is absent/omitted (use omitempty semantics: null or drop the key).","Validate the request body shape before sending: exactly one non-null block among the eight keys."],"exampleFix":"// before\n{\"name\": \"ssh\", \"type\": \"tcp\"}\n\n// after\n{\"name\": \"ssh\", \"type\": \"tcp\", \"tcp\": {\"localPort\": 22, \"remotePort\": 6000}}","handlingStrategy":"validation","validationCode":"func countBlocks(def map[string]any) int {\n    n := 0\n    for _, k := range []string{\"tcp\",\"udp\",\"http\",\"https\",\"tcpmux\",\"stcp\",\"sudp\",\"xtcp\"} {\n        if def[k] != nil { n++ }\n    }\n    return n\n}\n// require countBlocks(def) == 1 before POST","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Build definitions from a struct with omitempty so absent blocks serialize as omitted.","When switching proxy type, construct a fresh definition instead of mutating.","Add a unit test asserting exactly one block per generated definition."],"tags":["api","validation","proxy-type","frpc-admin"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}