diff --git a/graphql/upload.go b/graphql/upload.go index b603ab04c84..3c708d2444f 100644 --- a/graphql/upload.go +++ b/graphql/upload.go @@ -1,15 +1,20 @@ package graphql import ( + "encoding/json" "fmt" "io" ) +var ( + invalidUpload = "%T is not an Upload" +) + type Upload struct { - File io.ReadSeeker - Filename string - Size int64 - ContentType string + File io.ReadSeeker `json:"file"` + Filename string `json:"filename"` + Size int64 `json:"size"` + ContentType string `json:"contentType"` } func MarshalUpload(f Upload) Marshaler { @@ -18,10 +23,39 @@ func MarshalUpload(f Upload) Marshaler { }) } +// UnmarshalUpload reads an Upload from a JSON-encoded value. func UnmarshalUpload(v any) (Upload, error) { - upload, ok := v.(Upload) - if !ok { - return Upload{}, fmt.Errorf("%T is not an Upload", v) + switch t := v.(type) { + case Upload: + return t, nil + case map[string]interface{}: + upload, err := unmarshalUploadMap(t) + if err != nil { + return Upload{}, fmt.Errorf(invalidUpload, v) + } + + return upload, nil + default: + return Upload{}, fmt.Errorf(invalidUpload, v) } +} + +// unmarshalUploadMap reads an Upload from a map value and returns the struct if it's not empty +func unmarshalUploadMap(m map[string]interface{}) (Upload, error) { + var upload Upload + + out, err := json.Marshal(m) + if err != nil { + return upload, err + } + + // Unmarshal the JSON-encoded value into the Upload struct + // ignoring the error because we want to return the Upload struct if it's not empty + json.Unmarshal(out, &upload) // nolint: errcheck + + if (Upload{}) == upload { + return Upload{}, fmt.Errorf(invalidUpload, m) + } + return upload, nil } diff --git a/graphql/upload_test.go b/graphql/upload_test.go new file mode 100644 index 00000000000..17273b4c2a1 --- /dev/null +++ b/graphql/upload_test.go @@ -0,0 +1,81 @@ +package graphql + +import ( + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUnmarshalUpload(t *testing.T) { + // Create a ReadSeeker with nil value to test the Upload struct + file := io.ReadSeeker(nil) + + tests := []struct { + name string + input any + want Upload + wantErr bool + }{ + { + name: "valid Upload struct", + input: Upload{ + File: file, + Filename: "test.txt", + Size: 1234, + ContentType: "text/plain", + }, + want: Upload{ + File: file, + Filename: "test.txt", + Size: 1234, + ContentType: "text/plain", + }, + wantErr: false, + }, + { + name: "invalid type", + input: "invalid", + want: Upload{}, + wantErr: true, + }, + { + name: "valid JSON", + input: map[string]interface{}{ + "file": file, + "filename": "test.txt", + "size": 1234, + "contentType": "text/plain", + }, + want: Upload{ + File: file, + Filename: "test.txt", + Size: 1234, + ContentType: "text/plain", + }, + wantErr: false, + }, + { + name: "invalid JSON", + input: map[string]interface{}{ + "hello": "invalid", + }, + want: Upload{}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := UnmarshalUpload(tt.input) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + assert.Equal(t, tt.want, got) + }) + } +}