-
Notifications
You must be signed in to change notification settings - Fork 0
feat(http): add form parser #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #40 +/- ##
==========================================
- Coverage 66.19% 65.35% -0.84%
==========================================
Files 12 12
Lines 704 739 +35
==========================================
+ Hits 466 483 +17
- Misses 227 240 +13
- Partials 11 16 +5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request adds a form parser to the http/param
package, allowing parsing of application/x-www-form-urlencoded
data into structs. The changes are generally in the right direction, introducing new constants, types, and functions to handle form parameters, along with a test case.
However, there are a few issues to address. The most critical one is that the new FormParamFunc
for customizing form parsing is defined but never used, making part of the new public API non-functional. Another important issue is that the parser uses r.Form
which can lead to query parameters being parsed as form data, instead of r.PostForm
which would correctly limit parsing to the request body. I've also included a suggestion to improve code maintainability by reducing duplication.
Once these points are addressed, the feature will be a solid addition.
0d56b8a
to
ac7a3a3
Compare
@@ -43,6 +51,7 @@ type PathParamFunc func(r *http.Request, key string) string | |||
type Parser struct { | |||
ParamTagResolver TagResolver | |||
PathParamFunc PathParamFunc | |||
FormParamFunc FormParamFunc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove
I don't think it makes sense to have any customisation possible here - the default go std parser should be always called. In contrast, the PathParam func is not handled by go std lib, and the PathParamFunc allows caller to configure to their particular path routing library (e.g. chi) to extract the path placeholder/variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the note. I agree that likely every func would use just the default, but some might still benefit from interecepting the parsing somehow, for example with a logging mechanism, prefixing of the key, etc. It would perhaps also make sense to make these functions return error
for additional validation, but that's out of the scope of this PR.
Anyway, I don't see any harm in having this option here so I don't see a reason for removal, especially with the default being also implemented in the lib, which somewhat benefits the dev experience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I would lean towards keeping the library simple and not providing options that are not obvious what they are for
- the implementation in this PR is a bit weird how it does some part (
ParseForm
or nowParseMultipartForm
) in the "hardcoded" part, but then allow reading the populated fields/form values in the customFormParamFunc
- adding the func for form but not for query is inconsistent, maybe there could be a more general way that handles all types of "param", if it is needed for something at all.
- I'd argue that logging should happen on different layer, instead of this library providing a way to completely change the behaviour of one of the param types. Or as in above bullet point, the library should provide a more obvious way to intercept the parsed params (of all types if possible), without possibility to "break" the core functionality of this library
Yes, there is a possibility to have everything somehow customisable/configurable. I think we should not attempt to create such configurability without a need in some projects using this library. And if this need arises, I think it would make more sense to handle the issue in a thought out, thorough approach, rather than a side effect of one of the implementation of one of the param types.
That being said it is somewhat minor issue, PR looks good to me either way, after the minor comment I added.
http/param/param.go
Outdated
if r.Method != http.MethodPost && r.Method != http.MethodPut && r.Method != http.MethodPatch { | ||
return fmt.Errorf("struct's field was tagged for parsing the form parameter (%s) but request method is not POST, PUT or PATCH", paramName) | ||
} | ||
if err := r.ParseForm(); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
testing with curl
, naively googling how to do form requests with it, curl -F name=value
sends a different format - multipart/form-data
. The ParseForm
handles application/x-www-form-urlencoded
and application/octet-stream
.
(my suggestion) Changing this to ParseMultipartForm
would handle all content types as before + the multipart/form-data
.
Removing this (and still calling FormParamFunc
or PostFormValue
below - which itself calls ParseMultipartForm
on first call) would also handle all content types as before + the multipart/form-data
, but would't allow handling errors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, thanks for pointing that out! Opting for the suggested option. 👍
http/param/param.go
Outdated
if r.Method != http.MethodPost && r.Method != http.MethodPut && r.Method != http.MethodPatch { | ||
return fmt.Errorf("struct's field was tagged for parsing the form parameter (%s) but request method is not POST, PUT or PATCH", paramName) | ||
} | ||
if err := r.ParseForm(); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(thought, but no change needed in the end)
initially, I thought it would be nice (or even needed, as request body likely cannot be read multiple times) to parse body only once (only if there is at least one field tagged as form parameter), and not for each form parameter. But looks like both ParseForm
and ParseMultipartForm
already do both "check this is only called once per request (idempotent)" and "save parse result elsewhere (req.PostForm and req.Form)", so probably no change needed here.
type structWithFormParams struct { | ||
Subject string `param:"form=subject"` | ||
Amount *int `param:"form=amount"` | ||
Object *maybeShinyObject `param:"form=object"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider also a test where optional (pointer) fields are not present in the request (similar to TestParser_Parse_QueryParam_Pointers
- no params
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, added.
Prerequisite: #41 |
The form parser is useful for parsing application/x-www-form-urlencoded data. Signed-off-by: Marek Cermak <[email protected]>
- use FormParamFunc - use PostForm instead of Form - simplify newPath Signed-off-by: Marek Cermak <[email protected]>
Signed-off-by: Marek Cermak <[email protected]>
Signed-off-by: Marek Cermak <[email protected]>
dd4f66b
to
3618505
Compare
Signed-off-by: Marek Cermak <[email protected]>
return fmt.Errorf("parsing multipart form: %w", err) | ||
} | ||
// Try to parse regular form if not multipart form. | ||
if err := r.ParseForm(); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is called from the ParseMultipartForm
, so it is unnecesary to have it also here.
The form parser is useful for parsing application/x-www-form-urlencoded data.