{"record":{"id":"4b5bc0084645425b","repo":"d2lang/d2","slug":"failed-to-unmarshal-range-from-q-missing-end-fie","errorCode":null,"errorMessage":"failed to unmarshal Range from %q: missing End field","messagePattern":"failed to unmarshal Range from %q: missing End field","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2ast/d2ast.go","lineNumber":136,"sourceCode":"// OneLine returns true if the Range starts and ends on the same line.\nfunc (r Range) OneLine() bool {\n\treturn r.Start.Line == r.End.Line\n}\n\n// See docs on Range.\nfunc (r Range) MarshalText() ([]byte, error) {\n\tstart, _ := r.Start.MarshalText()\n\tend, _ := r.End.MarshalText()\n\treturn []byte(fmt.Sprintf(\"%s,%s-%s\", r.Path, start, end)), nil\n}\n\n// See docs on Range.\nfunc (r *Range) UnmarshalText(b []byte) (err error) {\n\tdefer xdefer.Errorf(&err, \"failed to unmarshal Range from %q\", b)\n\n\ti := bytes.LastIndexByte(b, '-')\n\tif i == -1 {\n\t\treturn errors.New(\"missing End field\")\n\t}\n\tend := b[i+1:]\n\tb = b[:i]\n\n\ti = bytes.LastIndexByte(b, ',')\n\tif i == -1 {\n\t\treturn errors.New(\"missing Start field\")\n\t}\n\tstart := b[i+1:]\n\tb = b[:i]\n\n\tr.Path = string(b)\n\terr = r.Start.UnmarshalText(start)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn r.End.UnmarshalText(end)\n}","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2ast/d2ast.go#L118-L154","documentation":"Range.UnmarshalText parses a Range string of the form \"path,start,end\" where start and end are positions separated by the last '-' (e.g. \"file.d2,1:0-1:5\"). This error is wrapped with 'failed to unmarshal Range from %q' when no '-' exists, meaning the End position field is missing. It is thrown by MakeRange/deserialization when given a malformed Range text.","triggerScenarios":"Calling Range.UnmarshalText or MakeRange with a string containing no '-' separator between the start and end positions, e.g. \"file.d2,1:0\".","commonSituations":"Hand-editing serialized range strings, truncating a range when copying from logs, or passing a Position string where a full Range string was expected.","solutions":["Include both positions separated by '-' in the format \"path,start-end\" (each position is \"line:col:byte\"), e.g. \"file.d2,1:0:0-1:5:5\".","Check the input string for accidental truncation or missing '-' before passing it to UnmarshalText.","Verify the string was produced by Range.MarshalText rather than constructed manually."],"exampleFix":"// before\nr.UnmarshalText(\"file.d2,1:0\")\n// after\nr.UnmarshalText(\"file.d2,1:0:0-1:5:5\")","handlingStrategy":"validation","validationCode":"func validRangeText(s string) bool { return strings.Contains(s, \"-\") && strings.Contains(s, \",\") }\nif !validRangeText(input) { return fmt.Errorf(\"range must be \\\"path,start-end\\\", got %q\", input) }","typeGuard":"func isRangeText(s string) bool { i := strings.LastIndexByte(s, '-'); return i > 0 && strings.LastIndexByte(s[:i], ',') != -1 }","tryCatchPattern":"var r d2ast.Range\nif err := r.UnmarshalText(input); err != nil {\n    return fmt.Errorf(\"invalid range %q: %w\", input, err)\n}","preventionTips":["Always produce range strings via MarshalText, never by hand-concatenation.","Sanity-check for '-' and ',' separators before deserializing.","Log the full raw input when deserialization fails to spot truncation."],"tags":["parsing","unmarshal","d2ast"],"backgroundTag":"malformed-range-string","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}