@@ -67,7 +67,11 @@ type UploadState struct {
6767// }
6868//
6969// // Use `result` or `resp` as needed
70- func (c * Client ) DoMultiPartRequest (method , endpoint string , files map [string ][]string , formDataFields map [string ]string , fileContentTypes map [string ]string , formDataPartHeaders map [string ]http.Header , out interface {}) (* http.Response , error ) {
70+ func (c * Client ) DoMultiPartRequest (method , endpoint string , files map [string ][]string , formDataFields map [string ]string , fileContentTypes map [string ]string , formDataPartHeaders map [string ]http.Header , encodingType string , out interface {}) (* http.Response , error ) {
71+ if encodingType != "raw" && encodingType != "base64" {
72+ c .Sugar .Errorw ("Invalid encoding type specified" , zap .String ("encodingType" , encodingType ))
73+ return nil , fmt .Errorf ("invalid encoding type: %s. Must be 'raw' or 'base64'" , encodingType )
74+ }
7175
7276 if method != http .MethodPost && method != http .MethodPut {
7377 c .Sugar .Error ("HTTP method not supported for multipart request" , zap .String ("method" , method ))
@@ -92,20 +96,21 @@ func (c *Client) DoMultiPartRequest(method, endpoint string, files map[string][]
9296 var body io.Reader
9397 var contentType string
9498
95- // Create multipart body in a function to ensure it runs again on retry
9699 createBody := func () error {
97100 var err error
98- body , contentType , err = createStreamingMultipartRequestBody (files , formDataFields , fileContentTypes , formDataPartHeaders , c .Sugar )
101+ body , contentType , err = createStreamingMultipartRequestBody (files , formDataFields , fileContentTypes , formDataPartHeaders , encodingType , c .Sugar )
99102 if err != nil {
100103 c .Sugar .Errorw ("Failed to create streaming multipart request body" , zap .Error (err ))
101104 } else {
102- c .Sugar .Infow ("Successfully created streaming multipart request body" , zap .String ("content_type" , contentType ))
105+ c .Sugar .Infow ("Successfully created streaming multipart request body" ,
106+ zap .String ("content_type" , contentType ),
107+ zap .String ("encoding" , encodingType ))
103108 }
104109 return err
105110 }
106111
107112 if err := createBody (); err != nil {
108- c .Sugar .Errorw ("Failed to create streaming multipart request body" , zap .Error (err ))
113+ c .Sugar .Errorw ("Failed to create multipart request body" , zap .Error (err ))
109114 return nil , err
110115 }
111116
@@ -115,23 +120,33 @@ func (c *Client) DoMultiPartRequest(method, endpoint string, files map[string][]
115120 return nil , err
116121 }
117122
118- c .Sugar .Infow ("Created HTTP Multipart request" , zap .String ("method" , method ), zap .String ("url" , url ), zap .String ("content_type" , contentType ))
123+ c .Sugar .Infow ("Created HTTP Multipart request" ,
124+ zap .String ("method" , method ),
125+ zap .String ("url" , url ),
126+ zap .String ("content_type" , contentType ),
127+ zap .String ("encoding" , encodingType ))
119128
120129 (* c .Integration ).PrepRequestParamsAndAuth (req )
121-
122130 req .Header .Set ("Content-Type" , contentType )
123131
124132 startTime := time .Now ()
125133
126- resp , requestErr := c .http .Do (req )
134+ resp , err := c .http .Do (req )
127135 duration := time .Since (startTime )
128136
129- if requestErr != nil {
130- c .Sugar .Errorw ("Failed to send request" , zap .String ("method" , method ), zap .String ("endpoint" , endpoint ), zap .Error (requestErr ))
131- return nil , requestErr
137+ if err != nil {
138+ c .Sugar .Errorw ("Failed to send request" ,
139+ zap .String ("method" , method ),
140+ zap .String ("endpoint" , endpoint ),
141+ zap .Error (err ))
142+ return nil , err
132143 }
133144
134- c .Sugar .Debugw ("Request sent successfully" , zap .String ("method" , method ), zap .String ("endpoint" , endpoint ), zap .Int ("status_code" , resp .StatusCode ), zap .Duration ("duration" , duration ))
145+ c .Sugar .Debugw ("Request sent successfully" ,
146+ zap .String ("method" , method ),
147+ zap .String ("endpoint" , endpoint ),
148+ zap .Int ("status_code" , resp .StatusCode ),
149+ zap .Duration ("duration" , duration ))
135150
136151 if resp .StatusCode >= 200 && resp .StatusCode < 300 {
137152 return resp , response .HandleAPISuccessResponse (resp , out , c .Sugar )
@@ -161,7 +176,7 @@ func (c *Client) DoMultiPartRequest(method, endpoint string, files map[string][]
161176// - string: The content type of the multipart request body. This includes the boundary string used by the multipart writer.
162177// - error: An error object indicating failure during the construction of the multipart request body. This could be due to issues
163178// such as file reading errors or multipart writer errors.
164- func createStreamingMultipartRequestBody (files map [string ][]string , formDataFields map [string ]string , fileContentTypes map [string ]string , formDataPartHeaders map [string ]http.Header , sugar * zap.SugaredLogger ) (io.Reader , string , error ) {
179+ func createStreamingMultipartRequestBody (files map [string ][]string , formDataFields map [string ]string , fileContentTypes map [string ]string , formDataPartHeaders map [string ]http.Header , encodingType string , sugar * zap.SugaredLogger ) (io.Reader , string , error ) {
165180 pr , pw := io .Pipe ()
166181 writer := multipart .NewWriter (pw )
167182
@@ -177,8 +192,11 @@ func createStreamingMultipartRequestBody(files map[string][]string, formDataFiel
177192
178193 for fieldName , filePaths := range files {
179194 for _ , filePath := range filePaths {
180- sugar .Debugw ("Adding file part" , zap .String ("field_name" , fieldName ), zap .String ("file_path" , filePath ))
181- if err := addFilePart (writer , fieldName , filePath , fileContentTypes , formDataPartHeaders , sugar ); err != nil {
195+ sugar .Debugw ("Adding file part" ,
196+ zap .String ("field_name" , fieldName ),
197+ zap .String ("file_path" , filePath ),
198+ zap .String ("encoding" , encodingType ))
199+ if err := addFilePartWithEncoding (writer , fieldName , filePath , fileContentTypes , formDataPartHeaders , encodingType , sugar ); err != nil {
182200 sugar .Errorw ("Failed to add file part" , zap .Error (err ))
183201 pw .CloseWithError (err )
184202 return
@@ -199,47 +217,33 @@ func createStreamingMultipartRequestBody(files map[string][]string, formDataFiel
199217 return pr , writer .FormDataContentType (), nil
200218}
201219
202- // addFilePart adds a base64 encoded file part to the multipart writer with the provided field name and file path.
203- // This function opens the specified file, sets the appropriate content type and headers, and adds it to the multipart writer.
204- // Parameters:
205- // - writer: The multipart writer used to construct the multipart request body.
206- // - fieldName: The field name for the file part.
207- // - filePath: The path to the file to be included in the request.
208- // - fileContentTypes: A map specifying the content type for each file part. The key is the field name and the value is the
209- // content type (e.g., "image/jpeg").
210- // - formDataPartHeaders: A map specifying custom headers for each part of the multipart form data. The key is the field name
211- // and the value is an http.Header containing the headers for that part.
212- // - sugar: An instance of a logger implementing the logger.Logger interface, used to sugar informational messages, warnings,
213- // and errors encountered during the addition of the file part.
214- //
215- // Returns:
216- // - error: An error object indicating failure during the addition of the file part. This could be due to issues such as
217- // file reading errors or multipart writer errors.
218- func addFilePart (writer * multipart.Writer , fieldName , filePath string , fileContentTypes map [string ]string , formDataPartHeaders map [string ]http.Header , sugar * zap.SugaredLogger ) error {
220+ // addFilePartWithEncoding adds a file part to the multipart writer with specified encoding.
221+ // Supports both raw file content and base64 encoding based on encodingType parameter.
222+ func addFilePartWithEncoding (writer * multipart.Writer , fieldName , filePath string , fileContentTypes map [string ]string , formDataPartHeaders map [string ]http.Header , encodingType string , sugar * zap.SugaredLogger ) error {
219223 file , err := os .Open (filePath )
220224 if err != nil {
221225 sugar .Errorw ("Failed to open file" , zap .String ("filePath" , filePath ), zap .Error (err ))
222226 return err
223227 }
224228 defer file .Close ()
225229
226- // Default fileContentType
227230 contentType := "application/octet-stream"
228231 if ct , ok := fileContentTypes [fieldName ]; ok {
229232 contentType = ct
230233 }
231234
232- header := setFormDataPartHeader (fieldName , filepath .Base (filePath ), contentType , formDataPartHeaders [fieldName ])
235+ header := createFilePartHeader (fieldName , filePath , contentType , formDataPartHeaders [fieldName ], encodingType )
236+ sugar .Debugw ("Created file part header" ,
237+ zap .String ("fieldName" , fieldName ),
238+ zap .String ("contentType" , contentType ),
239+ zap .String ("encoding" , encodingType ))
233240
234241 part , err := writer .CreatePart (header )
235242 if err != nil {
236243 sugar .Errorw ("Failed to create form file part" , zap .String ("fieldName" , fieldName ), zap .Error (err ))
237244 return err
238245 }
239246
240- encoder := base64 .NewEncoder (base64 .StdEncoding , part )
241- defer encoder .Close ()
242-
243247 fileSize , err := file .Stat ()
244248 if err != nil {
245249 sugar .Errorw ("Failed to get file info" , zap .String ("filePath" , filePath ), zap .Error (err ))
@@ -248,12 +252,34 @@ func addFilePart(writer *multipart.Writer, fieldName, filePath string, fileConte
248252
249253 progressLogger := logUploadProgress (file , fileSize .Size (), sugar )
250254 uploadState := & UploadState {}
251- if err := chunkFileUpload (file , encoder , progressLogger , uploadState , sugar ); err != nil {
252- sugar .Errorw ("Failed to copy file content" , zap .String ("filePath" , filePath ), zap .Error (err ))
253- return err
255+
256+ var writeTarget io.Writer = part
257+ if encodingType == "base64" {
258+ encoder := base64 .NewEncoder (base64 .StdEncoding , part )
259+ defer encoder .Close ()
260+ writeTarget = encoder
261+ sugar .Debugw ("Using base64 encoding for file upload" , zap .String ("fieldName" , fieldName ))
262+ } else {
263+ sugar .Debugw ("Using raw encoding for file upload" , zap .String ("fieldName" , fieldName ))
254264 }
255265
256- return nil
266+ return chunkFileUpload (file , writeTarget , progressLogger , uploadState , sugar )
267+ }
268+
269+ // createFilePartHeader creates the MIME header for a file part with the specified encoding type.
270+ func createFilePartHeader (fieldname , filename , contentType string , customHeaders http.Header , encodingType string ) textproto.MIMEHeader {
271+ header := textproto.MIMEHeader {}
272+ header .Set ("Content-Disposition" , fmt .Sprintf (`form-data; name="%s"; filename="%s"` , fieldname , filepath .Base (filename )))
273+ header .Set ("Content-Type" , contentType )
274+ if encodingType == "base64" {
275+ header .Set ("Content-Transfer-Encoding" , "base64" )
276+ }
277+ for key , values := range customHeaders {
278+ for _ , value := range values {
279+ header .Add (key , value )
280+ }
281+ }
282+ return header
257283}
258284
259285// addFormField adds a form field to the multipart writer with the provided key and value.
@@ -281,6 +307,63 @@ func addFormField(writer *multipart.Writer, key, val string, sugar *zap.SugaredL
281307 return nil
282308}
283309
310+ // addFilePart adds a base64 encoded file part to the multipart writer with the provided field name and file path.
311+ // This function opens the specified file, sets the appropriate content type and headers, and adds it to the multipart writer.
312+ // Parameters:
313+ // - writer: The multipart writer used to construct the multipart request body.
314+ // - fieldName: The field name for the file part.
315+ // - filePath: The path to the file to be included in the request.
316+ // - fileContentTypes: A map specifying the content type for each file part. The key is the field name and the value is the
317+ // content type (e.g., "image/jpeg").
318+ // - formDataPartHeaders: A map specifying custom headers for each part of the multipart form data. The key is the field name
319+ // and the value is an http.Header containing the headers for that part.
320+ // - sugar: An instance of a logger implementing the logger.Logger interface, used to sugar informational messages, warnings,
321+ // and errors encountered during the addition of the file part.
322+ //
323+ // Returns:
324+ // - error: An error object indicating failure during the addition of the file part. This could be due to issues such as
325+ // file reading errors or multipart writer errors.
326+ // func addFilePart(writer *multipart.Writer, fieldName, filePath string, fileContentTypes map[string]string, formDataPartHeaders map[string]http.Header, sugar *zap.SugaredLogger) error {
327+ // file, err := os.Open(filePath)
328+ // if err != nil {
329+ // sugar.Errorw("Failed to open file", zap.String("filePath", filePath), zap.Error(err))
330+ // return err
331+ // }
332+ // defer file.Close()
333+
334+ // // Default fileContentType
335+ // contentType := "application/octet-stream"
336+ // if ct, ok := fileContentTypes[fieldName]; ok {
337+ // contentType = ct
338+ // }
339+
340+ // header := setFormDataPartHeader(fieldName, filepath.Base(filePath), contentType, formDataPartHeaders[fieldName])
341+
342+ // part, err := writer.CreatePart(header)
343+ // if err != nil {
344+ // sugar.Errorw("Failed to create form file part", zap.String("fieldName", fieldName), zap.Error(err))
345+ // return err
346+ // }
347+
348+ // encoder := base64.NewEncoder(base64.StdEncoding, part)
349+ // defer encoder.Close()
350+
351+ // fileSize, err := file.Stat()
352+ // if err != nil {
353+ // sugar.Errorw("Failed to get file info", zap.String("filePath", filePath), zap.Error(err))
354+ // return err
355+ // }
356+
357+ // progressLogger := logUploadProgress(file, fileSize.Size(), sugar)
358+ // uploadState := &UploadState{}
359+ // if err := chunkFileUpload(file, encoder, progressLogger, uploadState, sugar); err != nil {
360+ // sugar.Errorw("Failed to copy file content", zap.String("filePath", filePath), zap.Error(err))
361+ // return err
362+ // }
363+
364+ // return nil
365+ // }
366+
284367// setFormDataPartHeader creates a textproto.MIMEHeader for a form data field with the provided field name, file name, content type, and custom headers.
285368// This function constructs the MIME headers for a multipart form data part, including the content disposition, content type,
286369// and any custom headers specified.
@@ -293,18 +376,18 @@ func addFormField(writer *multipart.Writer, key, val string, sugar *zap.SugaredL
293376//
294377// Returns:
295378// - textproto.MIMEHeader: The constructed MIME header for the form data part.
296- func setFormDataPartHeader (fieldname , filename , contentType string , customHeaders http.Header ) textproto.MIMEHeader {
297- header := textproto.MIMEHeader {}
298- header .Set ("Content-Disposition" , fmt .Sprintf (`form-data; name="%s"; filename="%s"` , fieldname , filename ))
299- header .Set ("Content-Type" , contentType )
300- header .Set ("Content-Transfer-Encoding" , "base64" )
301- for key , values := range customHeaders {
302- for _ , value := range values {
303- header .Add (key , value )
304- }
305- }
306- return header
307- }
379+ // func setFormDataPartHeader(fieldname, filename, contentType string, customHeaders http.Header) textproto.MIMEHeader {
380+ // header := textproto.MIMEHeader{}
381+ // header.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldname, filename))
382+ // header.Set("Content-Type", contentType)
383+ // header.Set("Content-Transfer-Encoding", "base64")
384+ // for key, values := range customHeaders {
385+ // for _, value := range values {
386+ // header.Add(key, value)
387+ // }
388+ // }
389+ // return header
390+ // }
308391
309392// chunkFileUpload reads the file upload into chunks and writes it to the writer.
310393// This function reads the file in chunks and writes it to the provided writer, allowing for progress logging during the upload.
0 commit comments