Skip to content

fix(api,imports): force parameter-free Content-Type on multipart file part#24

Merged
dgilperez merged 1 commit into
mainfrom
fix/imports-multipart-content-type
May 23, 2026
Merged

fix(api,imports): force parameter-free Content-Type on multipart file part#24
dgilperez merged 1 commit into
mainfrom
fix/imports-multipart-content-type

Conversation

@dgilperez
Copy link
Copy Markdown
Collaborator

@dgilperez dgilperez commented May 23, 2026

Caught during a live smoke test of imports preflight --file against a real Sure instance: the upload was rejected with

{"error":"invalid_file_type","message":"Invalid file type. Please upload a CSV file."}

…even though Sure's Import::ALLOWED_CSV_MIME_TYPES accepts text/csv, text/plain, application/vnd.ms-excel, application/csv.

Root cause

resty's default SetFile uses Go's http.DetectContentType on the first 512 bytes. For an ASCII CSV that returns **text/plain; charset=utf-8\**. Sure does an exact-match include?` on the allow-list, so the charset suffix breaks the check.

Confirmed with an httptest probe:

field=file filename=test-import.csv content-type=text/plain; charset=utf-8

--raw-file-content was a workaround (different code path; server picks the type from the controller's hardcoded "import.csv" filename).

Fix

Layer Change
internal/api/client.go PostMultipart gains a fileContentType string param. When non-empty it switches from SetFile to SetMultipartField, setting the Content-Type on the file part explicitly. Empty preserves prior detection behavior.
cmd/sure-cli/root/imports_cmd.go New mimeForImportFile(path) helper maps extensions to parameter-free MIME types: .csvtext/csv, .ndjsonapplication/x-ndjson, .jsonapplication/json, other → "".
imports create + imports preflight Thread the computed type through.

Tests

  • TestClient_PostMultipart_ExplicitContentType captures the file part's Content-Type header via httptest and asserts it equals text/csv when passed explicitly. Locks the fix in.
  • TestClient_PostMultipart_DefaultContentType confirms "" preserves resty's auto-detection (non-empty value, prior behavior).
  • Existing TestClient_PostMultipart updated to the new signature.
  • TestMimeForImportFile unit-tests the extension→MIME map across case-insensitive matches, absolute paths, unknown extensions.

go test ./... -race green across the suite.

Live verification

Before (against live Sure): 422 invalid_file_type.

After:

{
  "data": {
    "data": {
      "content": {
        "byte_size": 41,
        "content_type": "text/csv",
        "filename": "test-import.csv"
      },
      "errors": [],
      "headers": ["Date", "Name", "Amount"],
      "valid": true,
      ...

Scope

5 files, +154/-7. Caller signature change is contained — only imports_cmd.go and imports_preflight_cmd.go use PostMultipart.

Refs the post-merge smoke test against sure.h.dgilperez.com.

Summary by CodeRabbit

  • Improvements

    • Imports now explicitly specify content-type headers based on file extension (.csv, .ndjson, .json) for improved reliability and compatibility.
  • Tests

    • Expanded test coverage for multipart upload content-type handling to ensure consistent behavior across formats.

Review Change Stack

… part

Caught during the live smoke test of \`imports preflight --file\` against a
real Sure instance: the upload was rejected with
\`{"error":"invalid_file_type","message":"Invalid file type. Please upload a CSV file."}\`
even though Sure's allow-list (\`Import::ALLOWED_CSV_MIME_TYPES\`) accepts
\`text/csv\`, \`text/plain\`, \`application/vnd.ms-excel\`, \`application/csv\`.

Root cause: resty's default \`SetFile\` uses Go's \`http.DetectContentType\`
on the first 512 bytes. For an ASCII CSV that returns
\`text/plain; charset=utf-8\`. Sure does an exact-match \`include?\` on the
allow-list, so the charset suffix breaks the check. Empirically verified
with an httptest probe before patching.

\`--raw-file-content\` was a workaround (different code path, server picks
the type from the controller's hardcoded "import.csv" filename).

Fix:

- \`PostMultipart\` gains a \`fileContentType string\` parameter. When
  non-empty it switches from \`SetFile\` to \`SetMultipartField\` so the
  Content-Type on the file part can be set explicitly. Empty preserves the
  current detection behavior.
- New \`mimeForImportFile(path)\` helper in imports_cmd.go maps file
  extensions to parameter-free MIME types:
  - \`.csv\`     → \`text/csv\`
  - \`.ndjson\`  → \`application/x-ndjson\`
  - \`.json\`    → \`application/json\`  (also in SureImport allow-list)
  - other    → "" (let resty detect, matching prior behavior)
- Both call sites (\`imports create\`, \`imports preflight\`) thread the
  computed type through.

Tests:

- \`TestClient_PostMultipart_ExplicitContentType\` captures the file part's
  Content-Type header via httptest and asserts it equals \`text/csv\` when
  passed explicitly. Locks in the fix.
- \`TestClient_PostMultipart_DefaultContentType\` confirms \`""\` preserves
  resty's auto-detection (non-empty, current behavior).
- Existing \`TestClient_PostMultipart\` updated to the new signature.
- \`TestMimeForImportFile\` unit-tests the extension→MIME map across
  case-insensitive matches, absolute paths, unknown extensions.

Verified live against sure.h.dgilperez.com: \`imports preflight --file
test.csv\` now returns the parsed preflight payload with
\`content_type: "text/csv"\` instead of the 422 it produced before.

\`go test ./... -race\` green across the suite.

Refs the post-merge smoke test.
@superagent-security superagent-security Bot added the contributor:verified Contributor passed trust analysis. label May 23, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9c084542-e1ab-473c-ab43-8a3c5d41a094

📥 Commits

Reviewing files that changed from the base of the PR and between 8eb3455 and 9c8d4b7.

📒 Files selected for processing (5)
  • cmd/sure-cli/root/imports_cmd.go
  • cmd/sure-cli/root/imports_preflight_cmd.go
  • cmd/sure-cli/root/imports_preflight_cmd_test.go
  • internal/api/client.go
  • internal/api/client_test.go

📝 Walkthrough

Walkthrough

CLI import commands now send multipart uploads with explicit MIME types derived from file extensions. A new mimeForImportFile helper selects appropriate types for CSV, NDJSON, and JSON files. The Client.PostMultipart API is extended to accept an optional fileContentType parameter that, when provided, instructs the client to use SetMultipartField with explicit content-type instead of relying on resty's default detection.

Changes

Explicit MIME type handling for multipart imports

Layer / File(s) Summary
MIME type resolution helper
cmd/sure-cli/root/imports_cmd.go, cmd/sure-cli/root/imports_preflight_cmd_test.go
New mimeForImportFile function returns parameter-free MIME types for .csv, .ndjson, .json extensions with case-insensitive matching and absolute-path handling; empty string fallback for unknown extensions. Tests verify extension resolution and edge cases.
Client.PostMultipart API extension
internal/api/client.go, internal/api/client_test.go
Client.PostMultipart signature extended with fileContentType parameter. When non-empty, client opens file and uses SetMultipartField with explicit Content-Type; when empty, falls back to SetFile for default behavior. New tests verify explicit and default content-type handling.
CLI command integration
cmd/sure-cli/root/imports_cmd.go, cmd/sure-cli/root/imports_preflight_cmd.go
Both imports create and imports preflight commands updated to compute and pass explicit MIME type from file path to PostMultipart, replacing reliance on resty's default multipart detection.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • we-promise/sure-cli#2: Both PRs modify the same import/multipart upload flow—adding imports CLI support and the Client.PostMultipart helper—and this PR further extends PostMultipart/imports preflight to set explicit multipart Content-Type based on file extension, so they are directly connected.

Suggested labels

codex

Poem

🐰 A rabbit hops through file uploads bright,
Specifying MIME types with delight—
No guessing games from resty's detection,
Just explicit types in every direction,
CSV, NDJSON, JSON take flight! 🎉

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/imports-multipart-content-type

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@superagent-security superagent-security Bot added the pr:verified PR passed security analysis. label May 23, 2026
@dgilperez dgilperez merged commit d20109e into main May 23, 2026
4 of 5 checks passed
@dgilperez dgilperez deleted the fix/imports-multipart-content-type branch May 23, 2026 22:15
@dgilperez
Copy link
Copy Markdown
Collaborator Author

@jjmata hey, I think this PR is the last of the bunch - I tested the cli a bit against my local sure and all seems to work as expected. I think it's time to release 0.2.0, but you probably want to do that yourself?

@jjmata
Copy link
Copy Markdown
Contributor

jjmata commented May 24, 2026

@jjmata hey, I think this PR is the last of the bunch - I tested the cli a bit against my local sure and all seems to work as expected. I think it's time to release 0.2.0, but you probably want to do that yourself?

Sure, but would love @JSONbored to give this a go as well before we cut a new CLI release. Can you do that? 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor:verified Contributor passed trust analysis. pr:verified PR passed security analysis.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants