schema: add support for multipart files#17
Conversation
WalkthroughThis pull request refines the logic for handling multipart file fields across the codebase. In Changes
Sequence Diagram(s)sequenceDiagram
participant C as Caller
participant D as Decoder
participant H as Helper (isMultipartField/handleMultipartField)
C->>D: Call Decode(dst, src, files)
D->>D: Process src and initialize multipartFiles if provided
D->>D: Iterate over fields in dst for decoding
alt Field is multipart type
D->>H: Check using isMultipartField(fieldType)
H-->>D: Return true
D->>H: Call handleMultipartField(field, multipart files)
H-->>D: Set multipart field value
else Field is standard type
D->>D: Perform regular decoding
end
D-->>C: Return decoded object
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #17 +/- ##
==========================================
+ Coverage 87.77% 88.55% +0.77%
==========================================
Files 4 4
Lines 867 926 +59
==========================================
+ Hits 761 820 +59
Misses 90 90
Partials 16 16
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
decoder.go (3)
83-93: Possible collision when inserting file paths intosrc.
Currently, the code overwrites entries insrcwith an empty slice whenever a key frommultipartFilesalready exists insrc. This has potential to discard existing data if there's a parameter collision.for path := range multipartFiles { - src[path] = []string{""} + if _, exists := src[path]; !exists { + src[path] = []string{""} + } }
104-104: Remove or replace debug print.
fmt.Println(parts)appears to be a leftover debugging statement. Consider removing or using structured logging.- fmt.Println(parts) + // fmt.Println(parts) // remove or replace with logger if needed
105-115: Discourage printing errors directly.
Usingfmt.Print(err)in production code could clutter output. Prefer proper logging or returning the error in a structured way.} else if !d.ignoreUnknownKeys { - fmt.Print(err) + // log.Printf("parsePath error: %v", err) errors[path] = UnknownKeyError{Key: path} }🧰 Tools
🪛 GitHub Check: codecov/patch
[warning] 107-108: decoder.go#L107-L108
Added lines #L107 - L108 were not covered by tests
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
cache.go(1 hunks)decoder.go(6 hunks)decoder_test.go(2 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
decoder_test.go
2718-2718: Error return value of decoder.Decode is not checked
(errcheck)
🪛 GitHub Check: lint
decoder_test.go
[failure] 2718-2718:
Error return value of decoder.Decode is not checked (errcheck)
🪛 GitHub Actions: golangci-lint
decoder_test.go
[error] 2718-2718: Error return value of decoder.Decode is not checked (errcheck)
🪛 GitHub Check: codecov/patch
decoder.go
[warning] 107-108: decoder.go#L107-L108
Added lines #L107 - L108 were not covered by tests
[warning] 351-352: decoder.go#L351-L352
Added lines #L351 - L352 were not covered by tests
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: unit (1.23.x, windows-latest)
- GitHub Check: unit (1.22.x, windows-latest)
🔇 Additional comments (6)
decoder.go (4)
11-11: Import is consistent with usage.
No immediate issues with adding"mime/multipart"as it’s necessary for handlingFileHeader.
306-310: Definition of multipart field types.
These constants clearly define multipart file pointer types, which is good for readability. No further issues noted.
346-369: Logic for checking multipart field type looks good.
All recognized multipart field forms are covered and no redundant checks are found.🧰 Tools
🪛 GitHub Check: codecov/patch
[warning] 351-352: decoder.go#L351-L352
Added lines #L351 - L352 were not covered by tests
400-404: Early return is intentional for multipart fields.
If a struct field is a multipart field, we skip normal decoding. This appears to be by design.cache.go (1)
66-73: Conditional check forisMultipartField.
Skipping slices of structs if they’re multipart-typed is consistent with the new decoding logic.decoder_test.go (1)
2672-2795: General test coverage for multipart file decoding is well-structured.
Adequately verifies that nested multipart fields are assigned correctly.🧰 Tools
🪛 golangci-lint (1.62.2)
2718-2718: Error return value of
decoder.Decodeis not checked(errcheck)
🪛 GitHub Check: lint
[failure] 2718-2718:
Error return value ofdecoder.Decodeis not checked (errcheck)🪛 GitHub Actions: golangci-lint
[error] 2718-2718: Error return value of
decoder.Decodeis not checked (errcheck)
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (6)
decoder_test.go (3)
2844-2898: Improve error handling in benchmark.Move the error check inside the benchmark loop to catch errors in intermediate iterations.
for i := 0; i < b.N; i++ { - err = decoder.Decode(&s, data, fileHeaders) + if err := decoder.Decode(&s, data, fileHeaders); err != nil { + b.Fatalf("Failed to decode at iteration %d: %v", i, err) + } } - -if err != nil { - b.Fatalf("Failed to decode: %v", err) -}
2900-2943: Add descriptive names to test cases.Consider adding descriptive names to test cases to improve readability and debugging.
tc := []struct { + name string typ reflect.Type input map[string][]string expected bool }{ { + name: "plain string type", typ: reflect.TypeOf(string("")), expected: false, }, { + name: "string slice type", typ: reflect.TypeOf([]string{}), expected: false, }, // ... rest of test cases } -for _, tt := range tc { +for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { if isMultipartField(tt.typ) != tt.expected { t.Errorf("Expected %v, got %v", tt.expected, isMultipartField(tt.typ)) } + }) }
2981-3055: Restructure test into sub-tests for better organization.Consider using sub-tests to group related test cases and improve test output.
func TestHandleMultipartField(t *testing.T) { t.Parallel() // ... setup code ... - ok := handleMultipartField(rv.FieldByName("F"), files["f"]) - if !ok { - t.Error("Expected handleMultipartField to return true") - } - // ... more tests ... + tests := []struct { + name string + field string + wantOk bool + validate func(*testing.T, S) + }{ + { + name: "single file pointer", + field: "F", + wantOk: true, + validate: func(t *testing.T, s S) { + if s.F == nil { + t.Error("Expected F to be a file header, got nil") + } + if s.F.Filename != "test.txt" { + t.Errorf("Expected F.Filename to be 'test.txt', got %s", s.F.Filename) + } + }, + }, + // ... more test cases + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := S{} + rv := reflect.ValueOf(&s).Elem() + if got := handleMultipartField(rv.FieldByName(tt.field), files["f"]); got != tt.wantOk { + t.Errorf("handleMultipartField() = %v, want %v", got, tt.wantOk) + } + tt.validate(t, s) + }) + }decoder.go (3)
304-307: Consider using const declarations for type safety.The type declarations could be made more type-safe using const declarations.
-var ( +const ( multipartFileHeaderPointerType = reflect.TypeOf(&multipart.FileHeader{}) sliceMultipartFileHeaderPointerType = reflect.TypeOf([]*multipart.FileHeader{}) -)
311-349: Simplify function structure with early returns.The function could be simplified by using early returns to reduce nesting.
func handleMultipartField(field reflect.Value, files []*multipart.FileHeader) bool { fieldType := field.Type() if !isMultipartField(fieldType) { return false } // Skip if files are empty and field is multipart if len(files) == 0 { return true } // Check for *multipart.FileHeader if fieldType == multipartFileHeaderPointerType { field.Set(reflect.ValueOf(files[0])) return true } // Check for []*multipart.FileHeader if fieldType == sliceMultipartFileHeaderPointerType { field.Set(reflect.ValueOf(files)) return true } // Check for *[]*multipart.FileHeader if fieldType.Kind() != reflect.Pointer { return false } fieldType = fieldType.Elem() if fieldType != sliceMultipartFileHeaderPointerType { return false } if field.IsNil() { field.Set(reflect.New(fieldType)) } field.Elem().Set(reflect.ValueOf(files)) return true }🧰 Tools
🪛 GitHub Check: codecov/patch
[warning] 348-348: decoder.go#L348
Added line #L348 was not covered by tests
83-93: Add input validation for files parameter.Consider validating the files parameter to ensure consistent behavior.
func (d *Decoder) Decode(dst interface{}, src map[string][]string, files ...map[string][]*multipart.FileHeader) error { + if src == nil { + return errors.New("schema: source map is nil") + } + var multipartFiles map[string][]*multipart.FileHeader if len(files) > 0 { + if files[0] == nil { + return errors.New("schema: files map is nil") + } multipartFiles = files[0] } // Add files as empty string values to src in order to make path parsing work easily for path := range multipartFiles { + if path == "" { + return errors.New("schema: empty path in files map") + } src[path] = []string{""} }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
decoder.go(6 hunks)decoder_test.go(2 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
decoder.go
[warning] 106-107: decoder.go#L106-L107
Added lines #L106 - L107 were not covered by tests
[warning] 348-348: decoder.go#L348
Added line #L348 was not covered by tests
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: unit (1.23.x, windows-latest)
- GitHub Check: unit (1.22.x, windows-latest)
🔇 Additional comments (1)
decoder_test.go (1)
2672-2842: LGTM! Comprehensive test coverage for multipart file decoding.The test thoroughly covers:
- Single file fields
- File slice fields
- Nested struct fields
- Required field validation
- Empty file handling
- Various pointer combinations
This PR adds support for multipart files to Gorilla decoder as a part of gofiber/fiber#2002
To-Do List:
requiredanddefaultwith multipart files.Summary by CodeRabbit
New Features
Refactor
Tests