{"record":{"id":"b3627da95de05065","repo":"juanfont/headscale","slug":"w-got-v-d","errorCode":null,"errorMessage":"%w, got: %v(%d)","messagePattern":"%w, got: (.+?)\\((.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":957,"sourceCode":"\t\t\treturn nil\n\t\t}\n\n\t\t// Only contains a port, no protocol\n\t\tif !strings.Contains(vs, \":\") {\n\t\t\tports, err := parsePortRange(vs)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"port range %q: %w\", vs, err)\n\t\t\t}\n\n\t\t\tve.Protocol = ProtocolNameWildcard\n\t\t\tve.Ports = ports\n\n\t\t\treturn nil\n\t\t}\n\n\t\tparts := strings.Split(vs, \":\")\n\t\tif len(parts) != 2 {\n\t\t\treturn fmt.Errorf(\"%w, got: %v(%d)\", ErrProtocolPortInvalidFormat, parts, len(parts))\n\t\t}\n\n\t\tprotocol := Protocol(parts[0])\n\n\t\terr := protocol.validate()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tportsPart := parts[1]\n\n\t\tports, err := parsePortRange(portsPart)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"port range %q: %w\", portsPart, err)\n\t\t}\n\n\t\tve.Protocol = protocol\n\t\tve.Ports = ports","sourceCodeStart":939,"sourceCodeEnd":975,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L939-L975","documentation":"ProtocolPort.UnmarshalJSON split a protocol:port string on ':' and got a part count other than 2 — too many colons or none where the string still reached this branch. ErrProtocolPortInvalidFormat reports the parts and their count so the malformed shape is obvious.","triggerScenarios":"Values like 'tcp:80:443' (two colons, three parts) or an empty/edge string that splits oddly. len(parts) != 2 after strings.Split(vs, \":\").","commonSituations":"Trying to list multiple ports with colons ('tcp:80:443') instead of a range; IPv6 literals or URLs accidentally placed in the protocols field; double separators from concatenation bugs.","solutions":["Use exactly one colon: 'tcp:80' or a range 'tcp:80-90'.","List additional ports as separate entries in the protocols array.","Validate generated policy strings contain at most one colon before writing them."],"exampleFix":"// before\n\"protocols\": [\"tcp:80:443\"]\n\n// after\n\"protocols\": [\"tcp:80\", \"tcp:443\"]","handlingStrategy":"validation","validationCode":"if strings.Count(s, \":\") != 1 {\n    return fmt.Errorf(\"%q must be exactly 'proto:port' (one colon)\", s)\n}","typeGuard":null,"tryCatchPattern":"if err := pp.UnmarshalJSON([]byte(s)); err != nil {\n    if errors.Is(err, v2.ErrProtocolPortInvalidFormat) {\n        // message prints parts and count; split into multiple entries\n    }\n    return err\n}","preventionTips":["Exactly one colon per protocols entry.","Multiple ports -> multiple entries or one range.","Unit-test policy string builders for colon count."],"tags":["policy","acl","protocols","ports","validation","go"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}