d2lang/d2 · error

failed to unmarshal Range from %q: missing End field

Error message

failed to unmarshal Range from %q: missing End field

What it means

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.

Source

Thrown at d2ast/d2ast.go:136

// OneLine returns true if the Range starts and ends on the same line.
func (r Range) OneLine() bool {
	return r.Start.Line == r.End.Line
}

// See docs on Range.
func (r Range) MarshalText() ([]byte, error) {
	start, _ := r.Start.MarshalText()
	end, _ := r.End.MarshalText()
	return []byte(fmt.Sprintf("%s,%s-%s", r.Path, start, end)), nil
}

// See docs on Range.
func (r *Range) UnmarshalText(b []byte) (err error) {
	defer xdefer.Errorf(&err, "failed to unmarshal Range from %q", b)

	i := bytes.LastIndexByte(b, '-')
	if i == -1 {
		return errors.New("missing End field")
	}
	end := b[i+1:]
	b = b[:i]

	i = bytes.LastIndexByte(b, ',')
	if i == -1 {
		return errors.New("missing Start field")
	}
	start := b[i+1:]
	b = b[:i]

	r.Path = string(b)
	err = r.Start.UnmarshalText(start)
	if err != nil {
		return err
	}
	return r.End.UnmarshalText(end)
}

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. 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".
  2. Check the input string for accidental truncation or missing '-' before passing it to UnmarshalText.
  3. Verify the string was produced by Range.MarshalText rather than constructed manually.

Example fix

// before
r.UnmarshalText("file.d2,1:0")
// after
r.UnmarshalText("file.d2,1:0:0-1:5:5")
Defensive patterns

Strategy: validation

Validate before calling

func validRangeText(s string) bool { return strings.Contains(s, "-") && strings.Contains(s, ",") }
if !validRangeText(input) { return fmt.Errorf("range must be \"path,start-end\", got %q", input) }

Type guard

func isRangeText(s string) bool { i := strings.LastIndexByte(s, '-'); return i > 0 && strings.LastIndexByte(s[:i], ',') != -1 }

Try / catch

var r d2ast.Range
if err := r.UnmarshalText(input); err != nil {
    return fmt.Errorf("invalid range %q: %w", input, err)
}

Prevention

When it happens

Trigger: Calling Range.UnmarshalText or MakeRange with a string containing no '-' separator between the start and end positions, e.g. "file.d2,1:0".

Common situations: Hand-editing serialized range strings, truncating a range when copying from logs, or passing a Position string where a full Range string was expected.

Related errors


AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31). Data as JSON: /api/errors/4b5bc0084645425b. Report an issue: GitHub.