Skip to content

export endpoint: optionally apply a sort order to the CSVs in the output zip#558

Open
drewda wants to merge 3 commits intomainfrom
export-sort
Open

export endpoint: optionally apply a sort order to the CSVs in the output zip#558
drewda wants to merge 3 commits intomainfrom
export-sort

Conversation

@drewda
Copy link
Copy Markdown
Member

@drewda drewda commented Jan 13, 2026

No description provided.

Copilot AI review requested due to automatic review settings January 13, 2026 01:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds optional lexicographic sorting functionality to the feed version export endpoint. Users can now specify a sort order ("asc" or "desc") to have all CSV files in the exported zip sorted lexicographically before the archive is created.

Changes:

  • Added SortCSVFiles method to ZipWriterAdapter that reads, sorts, and rewrites CSV files
  • Extended ExportTransforms struct with LexicographicSort field and added validation
  • Added comprehensive test coverage for ascending sort, descending sort, and invalid parameter handling

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
tlcsv/adapter.go Implements CSV sorting logic with SortCSVFiles method and compareRowsLexicographic helper function
server/rest/feed_version_export.go Adds API parameter, validation, and integration with the sort functionality
server/rest/feed_version_export_test.go Adds test cases for ascending/descending sort and invalid parameter validation
doc/openapi/rest.json Updates OpenAPI schema with new lexicographic_sort parameter definition

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tlcsv/adapter.go Outdated
Comment on lines +673 to +692
file, err = os.Create(fullPath)
if err != nil {
return fmt.Errorf("failed to create file %s: %w", filename, err)
}

writer := csv.NewWriter(file)
if err := writer.Write(header); err != nil {
file.Close()
return fmt.Errorf("failed to write header to %s: %w", filename, err)
}
if err := writer.WriteAll(dataRows); err != nil {
file.Close()
return fmt.Errorf("failed to write rows to %s: %w", filename, err)
}
writer.Flush()
if err := writer.Error(); err != nil {
file.Close()
return fmt.Errorf("failed to flush writer for %s: %w", filename, err)
}
file.Close()
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File handle is not closed on all error paths. If writer.Write or writer.WriteAll returns an error, the file is closed, but if writer.Flush succeeds and then writer.Error returns an error, the file is not closed before returning. Consider using defer to ensure the file is always closed, or ensure all error paths close the file handle.

Copilot uses AI. Check for mistakes.
Comment thread tlcsv/adapter.go Outdated
Comment on lines +646 to +651
reader := csv.NewReader(file)
allRows, err := reader.ReadAll()
file.Close()
if err != nil {
return fmt.Errorf("failed to read file %s: %w", filename, err)
}
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loading entire CSV files into memory using ReadAll could cause memory issues with large GTFS feeds. For example, shapes.txt files can contain millions of rows. Consider implementing a streaming sort approach using external sorting algorithms or temporary files for large datasets to avoid potential out-of-memory errors.

Copilot uses AI. Check for mistakes.
Comment thread tlcsv/adapter.go Outdated
Comment on lines +627 to +633
// Close all open files first
for filename, f := range adapter.DirAdapter.files {
if err := f.Close(); err != nil {
return fmt.Errorf("failed to close file %s: %w", filename, err)
}
delete(adapter.DirAdapter.files, filename)
}
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SortCSVFiles method closes all files and deletes them from adapter.DirAdapter.files map (line 632), but then ZipWriterAdapter.Close() expects those file handles to still be open for seeking and copying to the zip archive (lines 742-758). This will result in an empty files map when Close() is called, meaning no files will be added to the zip archive. The sorting should preserve the files in a way that Close() can still access them, or Close() should be modified to handle already-closed files by reopening them.

Copilot uses AI. Check for mistakes.
Comment thread server/rest/feed_version_export.go Outdated
// Validate lexicographic sort order if provided
if req.Transforms != nil && req.Transforms.LexicographicSort != "" {
if req.Transforms.LexicographicSort != "asc" && req.Transforms.LexicographicSort != "desc" {
return util.NewBadRequestError(fmt.Sprintf("invalid lexicographic_sort value: %s (must be 'asc' or 'desc')", req.Transforms.LexicographicSort), nil)
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message prefix does not match the established pattern. Other error messages in the validation use lowercase (e.g., "only 'gtfs_zip' format is currently supported" at line 369), but this message uses "invalid lexicographic_sort value". For consistency, consider using lowercase or a consistent capitalization pattern throughout validation error messages.

Suggested change
return util.NewBadRequestError(fmt.Sprintf("invalid lexicographic_sort value: %s (must be 'asc' or 'desc')", req.Transforms.LexicographicSort), nil)
return util.NewBadRequestError(fmt.Sprintf("lexicographic_sort must be 'asc' or 'desc' (got %s)", req.Transforms.LexicographicSort), nil)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants