Skip to content

fix(dart): derive InputFile filename from path - #1902

Open
abnegate wants to merge 1 commit into
mainfrom
fix/chunked-upload-filename
Open

fix(dart): derive InputFile filename from path#1902
abnegate wants to merge 1 commit into
mainfrom
fix/chunked-upload-filename

Conversation

@abnegate

Copy link
Copy Markdown
Member

Summary

Uploads over 5 MB from the Flutter and Dart SDKs fail with storage_file_empty / "Empty file passed to the endpoint." whenever the caller uses InputFile.fromPath(path: ...) without passing filename:. Files at or under 5 MB upload fine, which is what makes it look like a chunking problem.

It isn't. The SDK chunks correctly. The two upload paths just build the multipart part differently:

Size Path content-disposition sent
≤ 5 MB MultipartFile.fromPath(...) name="file"; filename="video.mp4"
> 5 MB MultipartFile.fromBytes(..., filename: file.filename) name="file"

InputFile.fromPath leaves filename null when the caller omits it. package:http's IO helper does filename ??= segments.last, so the single-request path always ends up with a filename, while fromBytes passes the null straight through and package:http emits the attribute only if (file.filename != null).

A multipart part with no filename is not a file upload. Swoole puts it in $request->post, so $request->getFiles('file') returns [] and Create.php throws STORAGE_FILE_EMPTY. Every chunk is rejected.

Fix

Derive the filename from the path at construction, the same way package:http already does for MultipartFile.fromPath, so both upload paths send an identical part. Dart and Flutter share this template, so one change covers both SDKs. A path with no final segment (/path/to/) still leaves filename null, so nothing starts sending filename="".

Verification

Driving the real generated SDK at a Swoole server that reproduces Appwrite's getFiles('file') check, uploading 12 MB via InputFile.fromPath(path: ...) with no filename:

Before:

RESULT: upload FAILED -> storage_file_empty: Empty file passed to the endpoint.
server: CHUNK REJECTED: no file part (landed in post: fileId,file)

After:

progress 42%  chunk 1/3
progress 83%  chunk 2/3
progress 100% chunk 3/3
RESULT: upload SUCCEEDED, name="holiday-video.mp4"
server: CHUNK OK: name=holiday-video.mp4 size=5242880 range=bytes 0-5242879/12582912
        CHUNK OK: name=holiday-video.mp4 size=5242880 range=bytes 5242880-10485759/12582912
        CHUNK OK: name=holiday-video.mp4 size=2097152 range=bytes 10485760-12582911/12582912

The unit tests were also seen red against the pre-fix template, failing on exactly the null filename:

InputFile creates InputFile from path [E]                        Expected: 'file'      Actual: <null>
InputFile derives filename from path when none is given [E]      Expected: 'video.mp4' Actual: <null>
InputFile derives filename from path on the deprecated ctor [E]  Expected: 'video.mp4' Actual: <null>

Generation suite: 87 passed, 4 skipped. lint-twig: 586 files, 0 errors. Generated Dart SDK passes dart analyze with no issues.

Notes

  • The existing test asserted expect(inputFile.filename, isNull), which pinned the broken behaviour. It is updated rather than removed.
  • Flutter Web is unaffected: InputFile.fromBytes already requires a filename.
  • Present since 2022-06-14, so shipped SDKs need a release to pick this up. Until then the workaround is to pass filename: explicitly.
  • There is no E2E test covering the chunked upload path in this repo; the verification above was run by hand against a real Swoole parser.
  • composer lint (phpcs) reports 219 pre-existing errors across 21 PHP files on main. This PR changes no PHP and leaves them alone.

🤖 Generated with Claude Code

Uploads over 5MB switch from MultipartFile.fromPath to fromBytes, and
only fromPath derives a filename when the caller omits one. Every chunk
part therefore went out with no filename in its content-disposition, so
Swoole parsed it as an ordinary form field instead of a file upload,
getFiles('file') came back empty and the server rejected the upload with
storage_file_empty.

Deriving the filename at construction keeps both upload paths sending
the same part. Dart and Flutter share this template, so both are fixed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

The implementation appears behaviorally sound, but the repository’s observable-behavior testing requirement must be satisfied before merging.

Fix All in Claude CodeFindings

  1. P2 Tests miss upload behavior
Fix with agent prompt
### Issue 1
templates/dart/test/src/input_file_test.dart.twig:43-52
These tests only inspect the intermediate `InputFile.filename` field. They never exercise the chunked multipart path or verify that `Content-Disposition` contains a filename. If `chunkedUpload` stops forwarding this field to `MultipartFile.fromBytes`, all these tests will still pass while uploads over 5 MB fail as empty files again. The repository requires tests to verify observable behavior instead of mirroring implementation state, so this must be addressed before merging.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

  • Preserves explicitly supplied filenames.
  • Applies the behavior to both the factory and deprecated constructor paths.
  • Adds constructor-level tests, but does not test the multipart behavior responsible for the reported failure.

Reviews (1) · Last reviewed commit: "fix(dart): derive InputFile filename fro..."

Comment on lines +43 to +52
expect(
InputFile.fromPath(path: '/path/to/video.mp4').filename,
'video.mp4',
reason: 'chunks of a file over 5MB are sent as bytes, and without a '
'filename the server rejects them as an empty file',
);
expect(InputFile.fromPath(path: './relative/image.png').filename,
'image.png');
expect(InputFile.fromPath(path: 'video.mp4').filename, 'video.mp4');
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Tests miss upload behavior

These tests only inspect the intermediate InputFile.filename field. They never exercise the chunked multipart path or verify that Content-Disposition contains a filename. If chunkedUpload stops forwarding this field to MultipartFile.fromBytes, all these tests will still pass while uploads over 5 MB fail as empty files again. The repository requires tests to verify observable behavior instead of mirroring implementation state, so this must be addressed before merging.

Context Used: Call out and harshly judge implementation-coupled tests. We don't mirror source code, configuration, or version pins in assertions. We test observable behavior; use linters for syntax and schema checks. (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: templates/dart/test/src/input_file_test.dart.twig
Line: 43-52

Comment:
**Tests miss upload behavior**

These tests only inspect the intermediate `InputFile.filename` field. They never exercise the chunked multipart path or verify that `Content-Disposition` contains a filename. If `chunkedUpload` stops forwarding this field to `MultipartFile.fromBytes`, all these tests will still pass while uploads over 5 MB fail as empty files again. The repository requires tests to verify observable behavior instead of mirroring implementation state, so this must be addressed before merging.

**Context Used:** Call out and harshly judge implementation-coupled tests. We don't mirror source code, configuration, or version pins in assertions. We test observable behavior; use linters for syntax and schema checks. ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex

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.

1 participant