{"record":{"id":"9f568bcb3d743762","repo":"d2lang/d2","slug":"failed-to-unmarshal-range-from-q-missing-start-f","errorCode":null,"errorMessage":"failed to unmarshal Range from %q: missing Start field","messagePattern":"failed to unmarshal Range from %q: missing Start field","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2ast/d2ast.go","lineNumber":143,"sourceCode":"\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}\n\nfunc (r Range) Before(r2 Range) bool {\n\treturn r.Start.Before(r2.Start)\n}\n\n// Position represents a line:column and byte position in a file.\n//","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2ast/d2ast.go#L125-L161","documentation":"After extracting the End field via the last '-', UnmarshalText looks for a ',' separating the path from the Start position. This error means no ',' was found, so the Start field is missing. The wrapping message 'failed to unmarshal Range from %q' includes the offending input.","triggerScenarios":"Calling Range.UnmarshalText/MakeRange with a string that has a '-' but no ',' (e.g. \"1:0-1:5\" without the path part, or \"file.d2 1:0-1:5\").","commonSituations":"Passing a bare position-pair string instead of the full \"path,start-end\" format, or dropping the path and its comma when transforming serialized values.","solutions":["Format the input as \"path,start-end\", e.g. \"file.d2,1:0:0-1:5:5\".","Ensure the path segment followed by ',' is present before the start position.","Round-trip through MarshalText to get a canonical string instead of hand-building one."],"exampleFix":"// before\nr.UnmarshalText(\"1:0:0-1:5:5\")\n// after\nr.UnmarshalText(\"file.d2,1:0:0-1:5:5\")","handlingStrategy":"validation","validationCode":"func hasStartField(s string) bool { i := strings.LastIndexByte(s, '-'); return i > 0 && strings.Contains(s[:i], \",\") }\nif !hasStartField(input) { return errors.New(\"range text needs a comma before the start position\") }","typeGuard":"func isFullRangeText(s string) bool { parts := strings.SplitN(s, \"-\", 2); return len(parts) == 2 && strings.Contains(parts[0], \",\") }","tryCatchPattern":"var r d2ast.Range\nif err := r.UnmarshalText(input); err != nil {\n    return fmt.Errorf(\"range %q missing start/path: %w\", input, err)\n}","preventionTips":["Include the path segment plus ',' before the start position.","Round-trip through MarshalText to canonicalize.","Validate with a quick comma check before calling UnmarshalText."],"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"}