gofr-dev/gofr · error

%w for %q at offset %d: %w

Error message

%w for %q at offset %d: %w

What it means

NewRangeReader failed at the FTP level for a non-not-found reason: RetrFrom returned an error that isFTPNotFoundError did not match, so the library wraps errFailedToCreateReader along with the name, offset, and underlying error. Indicates connection/server problems or unsupported REST (restart) on the server.

Source

Thrown at pkg/gofr/datasource/file/ftp/storage_adapter.go:136

	}

	if offset < 0 {
		return nil, fmt.Errorf("%w (got: %d)", errInvalidOffset, offset)
	}

	if s.conn == nil {
		return nil, errFTPClientNotInitialized
	}

	objectPath := s.buildPath(name)

	reader, err := s.conn.RetrFrom(objectPath, uint64(offset))
	if err != nil {
		if isFTPNotFoundError(err) {
			return nil, fmt.Errorf("%w %q: %w", errObjectNotFound, name, err)
		}

		return nil, fmt.Errorf("%w for %q at offset %d: %w", errFailedToCreateReader, name, offset, err)
	}

	// FTP doesn't support length limit in RetrFrom, so wrap in LimitReader
	if length > 0 {
		return &limitedReadCloser{
			Reader: io.LimitReader(reader, length),
			Closer: reader,
		}, nil
	}

	return reader, nil
}

// limitedReadCloser combines io.LimitReader with Close capability.
type limitedReadCloser struct {
	io.Reader
	io.Closer
}

View on GitHub (pinned to 187eb24962)

Solutions

  1. Verify the server supports REST (byte-range resume); otherwise read the whole object with NewReader and seek client-side
  2. Check that offset < file size via StatObject before ranging
  3. Re-dial/reconnect the FTP client and retry if the connection was idle-timed out
  4. Inspect the wrapped error for the exact FTP response code
  5. Check permissions for the authenticated FTP user

Example fix

// before
r, err := store.NewRangeReader(ctx, "big.bin", 1<<40, 100) // offset beyond EOF
// after
info, _ := store.StatObject(ctx, "big.bin")
if 1<<40 >= info.Size { return io.EOF }
r, err := store.NewRangeReader(ctx, "big.bin", 1<<40, 100)
Defensive patterns

Strategy: retry

Validate before calling

info, err := store.StatObject(ctx, name)
if err != nil { return err }
if offset >= info.Size { return io.EOF }

Type guard

func isFTPNotFound(err error) bool { return errors.Is(err, errObjectNotFound) }

Try / catch

r, err := store.NewRangeReader(ctx, name, offset, length)
if err != nil && !errors.Is(err, errObjectNotFound) {
    // reconnect and retry with backoff; check server REST support on repeated failure
}

Prevention

When it happens

Trigger: storageAdapter.NewRangeReader where s.conn.RetrFrom(objectPath, uint64(offset)) fails with errors like connection closed, 4xx/5xx server responses, or servers not supporting REST byte ranges.

Common situations: Stale/idle FTP connection dropped before the command, FTP server without REST support, offset beyond EOF causing a server-side error, permission issues.

Related errors


AI-assisted analysis of gofr-dev/gofr@187eb24962 (2026-09-01). Data as JSON: /api/errors/fd4d7d98aa475fea. Report an issue: GitHub.