{"record":{"id":"4feba30077a703ef","repo":"d2lang/d2","slug":"failed-to-unmarshal-position-from-q-expected-thr","errorCode":null,"errorMessage":"failed to unmarshal Position from %q: expected three fields","messagePattern":"failed to unmarshal Position from %q: expected three fields","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2ast/d2ast.go","lineNumber":200,"sourceCode":"\treturn fmt.Sprintf(\"%d:%d\", p.Line+1, p.Column+1)\n}\n\nfunc (p Position) Debug() string {\n\treturn fmt.Sprintf(\"%d:%d:%d\", p.Line, p.Column, p.Byte)\n}\n\n// See docs on Range.\nfunc (p Position) MarshalText() ([]byte, error) {\n\treturn []byte(fmt.Sprintf(\"%d:%d:%d\", p.Line, p.Column, p.Byte)), nil\n}\n\n// See docs on Range.\nfunc (p *Position) UnmarshalText(b []byte) (err error) {\n\tdefer xdefer.Errorf(&err, \"failed to unmarshal Position from %q\", b)\n\n\tfields := bytes.Split(b, []byte{':'})\n\tif len(fields) != 3 {\n\t\treturn errors.New(\"expected three fields\")\n\t}\n\n\tp.Line, err = strconv.Atoi(string(fields[0]))\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.Column, err = strconv.Atoi(string(fields[1]))\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.Byte, err = strconv.Atoi(string(fields[2]))\n\treturn err\n}\n\n// From copies src into p. It's used in the d2parser package to set a node's Range.End to\n// the parser's current pos on all return paths with defer.\nfunc (p *Position) From(src *Position) {\n\t*p = *src","sourceCodeStart":182,"sourceCodeEnd":218,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2ast/d2ast.go#L182-L218","documentation":"Position.UnmarshalText splits the input on ':' and requires exactly three fields: line, column, and byte offset. This error means the position string did not have exactly three colon-separated fields. It surfaces through MakeRange when deserializing positions inside a Range.","triggerScenarios":"Calling Position.UnmarshalText (directly or via Range/MakeRange) with strings like \"1:0\" (two fields) or \"1\" (one field) instead of \"1:0:0\".","commonSituations":"Constructing positions from line:column pairs (only two fields) without the byte-offset third field, or copying a position from a different tool with a different format.","solutions":["Provide all three colon-separated fields \"line:col:byte\", e.g. \"1:0:0\".","If you only have line:col, compute the byte offset and include it as the third field.","Use Position.MarshalText output as the source of valid position strings."],"exampleFix":"// before\np.UnmarshalText(\"1:0\")\n// after\np.UnmarshalText(\"1:0:0\")","handlingStrategy":"validation","validationCode":"func validPositionText(s string) bool { return len(strings.Split(s, \":\")) == 3 }\nif !validPositionText(pos) { return fmt.Errorf(\"position must be line:col:byte, got %q\", pos) }","typeGuard":"func isPositionText(s string) bool { return len(strings.Split(s, \":\")) == 3 }","tryCatchPattern":"var p d2ast.Position\nif err := p.UnmarshalText(input); err != nil {\n    return fmt.Errorf(\"bad position %q (need line:col:byte): %w\", input, err)\n}","preventionTips":["Always serialize positions as three colon-separated fields including the byte offset.","Use Position.MarshalText output instead of formatting manually.","Reject two-field line:col strings at input boundaries."],"tags":["parsing","unmarshal","d2ast"],"backgroundTag":"malformed-position-string","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}