Help us shape the future of Go! Take the 2026 Go Developer Survey

export

package
v0.30.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package export streams CSV, JSON and NDJSON downloads row by row, so a large export never sits in memory:

return export.CSV(c.Writer(), c.Request(), "users.csv", []string{"id", "email"},
	orm.QueryOf[User](db).Each(c.Context()),
	func(u User) []string { return []string{strconv.FormatInt(u.ID, 10), u.Email} })

Responses are attachments with a sanitized file name (RFC 6266, with an RFC 5987 filename* for non-ASCII names), Cache-Control: no-store and nosniff. CSV cells that a spreadsheet would run as a formula are written as text. Nothing is sent before the first row, so a query that fails up front still reaches the error handler; a failure after that aborts the connection so the client sees a failed download, not a truncated file.

See https://github.com/AlexGalhardo/vukem/blob/main/docs/subsystems.md#exports.

Index

Examples

Constants

View Source
const (
	// DefaultFlushEvery is the number of rows between flushes.
	DefaultFlushEvery = 500
	// DefaultWriteTimeout is how long each flush window may take before
	// the write deadline cuts a stalled client.
	DefaultWriteTimeout = time.Minute
)

Variables

View Source
var ErrIncomplete = errors.New("export: response incomplete")

ErrIncomplete wraps a failure after the response started: the client got part of the export and the connection was dropped.

Functions

func CSV

func CSV[T any](
	w http.ResponseWriter, r *http.Request, filename string, header []string,
	rows iter.Seq2[T, error], record func(T) []string, opts ...Option,
) error

CSV streams rows as a CSV attachment: header first, then record(row) for each row. Pass orm Query.Each as rows to read from the database lazily.

Example
users := seq(user{1, "Ana"}, user{2, "=SUM(A1:A9)"})

handler := func(w http.ResponseWriter, r *http.Request) {
	// With the ORM: orm.QueryOf[User](db).Each(r.Context()) as rows.
	err := export.CSV(w, r, "users.csv", []string{"id", "name"}, users,
		func(u user) []string { return []string{strconv.Itoa(u.ID), u.Name} })
	// Before the first row nothing was sent and an error page still fits;
	// ErrIncomplete means the download already started and was aborted.
	if err != nil && !errors.Is(err, export.ErrIncomplete) {
		http.Error(w, "export failed", http.StatusInternalServerError)

		return
	}
}

rec := httptest.NewRecorder()
handler(rec, httptest.NewRequest(http.MethodGet, "/users.csv", http.NoBody))

fmt.Println(rec.Header().Get("Content-Disposition"))
fmt.Print(rec.Body.String())
Output:
attachment; filename="users.csv"
id,name
1,Ana
2,'=SUM(A1:A9)

func ContentDisposition

func ContentDisposition(filename string) string

ContentDisposition returns an attachment Content-Disposition for filename (RFC 6266). The name is reduced to its last path element (either separator) and loses control characters (C0, C1, U+2028/2029), format characters (bidi overrides, zero-width), leading dots, trailing dots and spaces and the characters Windows forbids; Windows device names (CON, NUL, COM1...) get a "_" prefix; it is cut to 200 characters keeping the extension, and is "download" when nothing is left. Non-ASCII names get an ASCII filename plus an RFC 5987 filename*.

func JSON

func JSON[T any](
	w http.ResponseWriter,
	r *http.Request,
	filename string,
	rows iter.Seq2[T, error],
	opts ...Option,
) error

JSON streams rows as a JSON array attachment, one element per row.

func NDJSON

func NDJSON[T any](
	w http.ResponseWriter,
	r *http.Request,
	filename string,
	rows iter.Seq2[T, error],
	opts ...Option,
) error

NDJSON streams rows as newline-delimited JSON (application/x-ndjson), one object per line.

func SafeCell

func SafeCell(s string) string

SafeCell guards a CSV cell against formula injection (OWASP CSV injection): a cell starting with =, +, -, @, tab or carriage return gets a leading apostrophe so spreadsheets show it as text. Some spreadsheets skip leading spaces, line breaks and quotes, so a cell whose first other character is =, +, - or @ is guarded too; the trade-off is that text such as " - note" or "'@handle" also gains the apostrophe. Plain decimal numbers ("-42", "+3.14") cannot run anything and stay as they are.

func WriteCSV

func WriteCSV(
	w http.ResponseWriter,
	r *http.Request,
	filename string,
	fn func(*CSVWriter) error,
	opts ...Option,
) error

WriteCSV streams a CSV attachment whose records fn writes, for exports that are not one row per item (several sections, summary rows).

Types

type CSVWriter

type CSVWriter struct {
	// contains filtered or unexported fields
}

CSVWriter writes CSV records to an export started by WriteCSV.

func (*CSVWriter) Write

func (w *CSVWriter) Write(record []string) error

Write writes one record (an empty record writes an empty line). Each cell goes through SafeCell unless InsecureRawCells is set.

type Option

type Option func(*options)

Option configures an export.

func FlushEvery

func FlushEvery(n int) Option

FlushEvery flushes the response after every n rows (values below 1 flush after every row). The end of the export is always flushed.

func InsecureRawCells

func InsecureRawCells() Option

InsecureRawCells writes CSV cells as given, without the formula-injection guard. Use it only for files never opened in a spreadsheet.

func WithBOM

func WithBOM() Option

WithBOM starts a CSV export with a UTF-8 byte order mark, which Excel needs to read non-ASCII text correctly. JSON and NDJSON ignore it.

func WriteTimeout

func WriteTimeout(d time.Duration) Option

WriteTimeout keeps the write deadline d ahead of every write to the client, so a long export outlives the server's WriteTimeout while a client that stops reading for d is still cut. The deadline is cleared when the export ends. Zero or negative keeps the server's deadline.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL