feat: Form and multipart - #380
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
|
Hey @nielsenko, could you please review this PR? it is connected to the issue #379 Thank you! PS. I haven't added any docs into the site because I wasn't really if I should do it, or if you guys have any sort of guidelines for new docs in the site |
|
@namzug16 Thank you for your contribution - I'll get to it Wednesday next week. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #380 +/- ##
==========================================
- Coverage 92.74% 92.50% -0.24%
==========================================
Files 110 117 +7
Lines 4702 5260 +558
Branches 2380 2634 +254
==========================================
+ Hits 4361 4866 +505
- Misses 341 394 +53
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@namzug16 Looking at this now. Sorry for the delay. |
|
@namzug16 Thank you for taking the time to look into this. I have taken the liberty to suggest some changes. Typed APIForm fields now support a typed api similar to path and query parameters. const nameField = StringFormField('name');
const ageField = IntFormField('age');
const avatarFile = FormFile('avatar');
router.post('/profile', (req) async {
final form = await req.multipartForm(uploadStorage: TempUploadStorage());
try {
final name = form.fields.get(nameField); // 400 if missing
final age = form.fields(ageField); // null if missing, 400 if not an int
final avatar = form.files.get(avatarFile);
return Response.ok();
} finally {
await form.dispose();
}
});Content-TypeContent-Type stays on
This also fixes a bug on main. Multipart
Hardening
Breaking changes
|
|
Opened a PR towards your branch here: namzug16#1 with my suggestions |
|
Hey @nielsenko, thank you for the suggestions and for taking the time for this PR 🚀🚀 I've merged your PR |
- Add ExtValue for the ext-value grammar, with UTF-8 as the only charset - Percent-encode every byte outside attr-char in ExtValue.encode - Type ContentDispositionParameter.language as LanguageTag BREAKING CHANGE: ContentDispositionParameter has no encoding, and extended parameters always encode as UTF-8. Pass a LanguageTag as language. Parsing throws FormatException for a charset other than UTF-8 and for any malformed ext-value.
- Decode form text through decodeFormText in urlEncodedForm, MultipartPart.readAsString and multipart form fields
- Write each chunk with RandomAccessFile.writeFrom in TempUploadStorage - Close the temp file before deleting it when the content fails
- Default every FormLimits constructor parameter to its FormLimits.defaults value - Add FormLimits.copyWith
- Rename UploadedFile.openRead to read - Remove fieldName from UploadedFile, MemoryUploadedFile and TempUploadedFile, which FileFieldEntry.name carries
- Add MultipartFieldPart, MultipartFilePart and MultipartOtherPart - Move name to MultipartFieldPart and MultipartFilePart - Make MultipartFilePart.filename non-nullable - Remove MultipartPart.isField, isFile, name and filename
- Remove C0 and C1 controls, line and paragraph separators and bidirectional formatting characters from MultipartFilePart.filename - Make MultipartFilePart.filename nullable - Set it to null for ".", ".." and names that end in a separator
- Classify a named form-data part with filename="" as a MultipartFilePart with a null filename - Add MultipartFilePart.hasEmptyFilename - Leave file parts with an empty filename out of multipartForm
- Add BodyType.parameters, BodyType.parameter and BodyType.validate - Lowercase parameter names and reject charset in the BodyType constructor - Validate parameter names in BodyType.toHeaderValue - Add a parameters argument to Body.fromData and Body.fromDataStream - Carry Content-Type parameters through the dart:io adapter and drop the request parameters it cannot write back - Pass the multipart/byteranges boundary through Body in StaticHandler BREAKING CHANGE: BodyType has no const constructor. Drop const from BodyType(...) calls.
- Read the form media type, charset and boundary from Body.bodyType - Remove defaultEncoding from urlEncodedForm, formData and multipartForm - Remove Headers.contentType and the MutableHeaders.contentType setter
- Rename TempUploadStorage.directoryPrefix to prefix - Make the TempUploadedFile constructor private
- Replace the per-type form catches in RelicServer with one on FormException - Respond with FormException.statusCode, its message and Connection: close
- Add FormField, StringFormField, NumFormField, IntFormField, DoubleFormField and FormFile - Make FormFields and UploadedFiles AccessorState subclasses with get, call, tryGet and getAll - Add MissingFormFieldException and InvalidFormFieldException, whose responses keep the connection open - Remove getRequired and contains from FormFields and UploadedFiles
- Add FormLimit - Make FormLimitExceededException.limit a FormLimit - Throw FormLimitExceededException for FormLimits.maxBodySize from urlEncodedForm and multipart
- Give each MultipartPart body the part Content-Type as its bodyType - Take a content stream in the MultipartPart factory - Remove MultipartPart.contentType - Replace UploadedFile.contentType and the contentType argument of UploadStorage.store with bodyType
- Listen to MimeMultipartTransformer in a guarded zone in _parts - Map FormatException to MalformedFormDataException
- Add HeaderScannerInternal.utf8, which reads every non-ASCII code unit as obs-text - Parse multipart Content-Disposition with ContentDispositionHeaderInternal.parseFormData
- Read MultipartFilePart.filename from the filename parameter only - Classify a part with only filename* as a MultipartFilePart with a null filename - Keep parameters with a * undecoded, with the * in their name, in ContentDispositionHeaderInternal.parseFormData
- Add a copy argument to MemoryUploadedFile, true by default - Pass copy: false from MemoryUploadStorage
Set each upload directory to mode 0700 with chmod through dart:ffi on every platform except Windows.
4e56831 to
7b57d05
Compare
|
Rebased on main. Added three last commits |
7b57d05 to
0d59901
Compare
Description
In this PR I'm adding first class form parsing support to Relic, including URL-encoded forms, multipart form-data streaming, multipart aggregation, upload metadata models, temporary file upload storage (on relic_io), and related tests/examples
This makes it easier for Relic handlers to safely read submitted form fields and uploaded files without manually parsing request bodies, which is pretty useful when developing SSR applications
Related Issues
Pre-Launch Checklist
Please ensure that your PR meets the following requirements before submitting:
///), ensuring consistency with existing project documentation.Breaking Changes
Code examples