github.com/operandinc/gqlgen@v0.16.1/client/withfilesoption.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"mime/multipart"
     9  	"net/http"
    10  	"net/textproto"
    11  	"os"
    12  	"strings"
    13  )
    14  
    15  type fileFormDataMap struct {
    16  	mapKey string
    17  	file   *os.File
    18  }
    19  
    20  func findFiles(parentMapKey string, variables map[string]interface{}) []*fileFormDataMap {
    21  	files := []*fileFormDataMap{}
    22  	for key, value := range variables {
    23  		if v, ok := value.(map[string]interface{}); ok {
    24  			files = append(files, findFiles(parentMapKey+"."+key, v)...)
    25  		} else if v, ok := value.([]map[string]interface{}); ok {
    26  			for i, arr := range v {
    27  				files = append(files, findFiles(fmt.Sprintf(`%s.%s.%d`, parentMapKey, key, i), arr)...)
    28  			}
    29  		} else if v, ok := value.([]*os.File); ok {
    30  			for i, file := range v {
    31  				files = append(files, &fileFormDataMap{
    32  					mapKey: fmt.Sprintf(`%s.%s.%d`, parentMapKey, key, i),
    33  					file:   file,
    34  				})
    35  			}
    36  		} else if v, ok := value.(*os.File); ok {
    37  			files = append(files, &fileFormDataMap{
    38  				mapKey: parentMapKey + "." + key,
    39  				file:   v,
    40  			})
    41  		}
    42  	}
    43  
    44  	return files
    45  }
    46  
    47  // WithFiles encodes the outgoing request body as multipart form data for file variables
    48  func WithFiles() Option {
    49  	return func(bd *Request) {
    50  		bodyBuf := &bytes.Buffer{}
    51  		bodyWriter := multipart.NewWriter(bodyBuf)
    52  
    53  		//-b7955bd2e1d17b67ac157b9e9ddb6238888caefc6f3541920a1debad284d
    54  		// Content-Disposition: form-data; name="operations"
    55  		//
    56  		// {"query":"mutation ($input: Input!) {}","variables":{"input":{"file":{}}}
    57  		requestBody, _ := json.Marshal(bd)
    58  		bodyWriter.WriteField("operations", string(requestBody))
    59  
    60  		// --b7955bd2e1d17b67ac157b9e9ddb6238888caefc6f3541920a1debad284d
    61  		// Content-Disposition: form-data; name="map"
    62  		//
    63  		// `{ "0":["variables.input.file"] }`
    64  		// or
    65  		// `{ "0":["variables.input.files.0"], "1":["variables.input.files.1"] }`
    66  		// or
    67  		// `{ "0": ["variables.input.0.file"], "1": ["variables.input.1.file"] }`
    68  		// or
    69  		// `{ "0": ["variables.req.0.file", "variables.req.1.file"] }`
    70  		mapData := ""
    71  		filesData := findFiles("variables", bd.Variables)
    72  		filesGroup := [][]*fileFormDataMap{}
    73  		for _, fd := range filesData {
    74  			foundDuplicate := false
    75  			for j, fg := range filesGroup {
    76  				f1, _ := fd.file.Stat()
    77  				f2, _ := fg[0].file.Stat()
    78  				if os.SameFile(f1, f2) {
    79  					foundDuplicate = true
    80  					filesGroup[j] = append(filesGroup[j], fd)
    81  				}
    82  			}
    83  
    84  			if !foundDuplicate {
    85  				filesGroup = append(filesGroup, []*fileFormDataMap{fd})
    86  			}
    87  		}
    88  		if len(filesGroup) > 0 {
    89  			mapDataFiles := []string{}
    90  
    91  			for i, fileData := range filesGroup {
    92  				mapDataFiles = append(
    93  					mapDataFiles,
    94  					fmt.Sprintf(`"%d":[%s]`, i, strings.Join(collect(fileData, wrapMapKeyInQuotes), ",")),
    95  				)
    96  			}
    97  
    98  			mapData = `{` + strings.Join(mapDataFiles, ",") + `}`
    99  		}
   100  		bodyWriter.WriteField("map", mapData)
   101  
   102  		// --b7955bd2e1d17b67ac157b9e9ddb6238888caefc6f3541920a1debad284d
   103  		// Content-Disposition: form-data; name="0"; filename="tempFile"
   104  		// Content-Type: text/plain; charset=utf-8
   105  		// or
   106  		// Content-Type: application/octet-stream
   107  		//
   108  		for i, fileData := range filesGroup {
   109  			h := make(textproto.MIMEHeader)
   110  			h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%d"; filename="%s"`, i, fileData[0].file.Name()))
   111  			b, _ := ioutil.ReadFile(fileData[0].file.Name())
   112  			h.Set("Content-Type", http.DetectContentType(b))
   113  			ff, _ := bodyWriter.CreatePart(h)
   114  			ff.Write(b)
   115  		}
   116  		bodyWriter.Close()
   117  
   118  		bd.HTTP.Body = ioutil.NopCloser(bodyBuf)
   119  		bd.HTTP.Header.Set("Content-Type", bodyWriter.FormDataContentType())
   120  	}
   121  }
   122  
   123  func collect(strArr []*fileFormDataMap, f func(s *fileFormDataMap) string) []string {
   124  	result := make([]string, len(strArr))
   125  	for i, str := range strArr {
   126  		result[i] = f(str)
   127  	}
   128  	return result
   129  }
   130  
   131  func wrapMapKeyInQuotes(s *fileFormDataMap) string {
   132  	return fmt.Sprintf("\"%s\"", s.mapKey)
   133  }