hasura / go-graphql-client

Package graphql provides a GraphQL client implementation.
MIT License
395 stars 91 forks source link

How can we handle multi-part request to add "file" in the request? #104

Open wangmir opened 1 year ago

wangmir commented 1 year ago

Same as title.

When I wanna send file with Upload scalar, (https://gqlgen.com/reference/file-upload/) How can we send this request?

Is there any features to support multi-part request on this library? or should I manually add that.

hgiasac commented 1 year ago

Hi @wangmir ,

This library only supports JSON content type. I have no idea if there is another supported graphql client in Go. However, you can use a plain HTTP client to write a simple request with mime/multipart body.

wangmir commented 1 year ago

Hi @hgiasac ,

Yes, but I just wanted to utilize the rich functionalities of this library.

When I check the code, I think we can still reuse some of the code, and maybe similar to the requestModifier approach, we can make a custom byte buffer on this.

from below code,

// Request the common method that send graphql request
func (c *Client) request(ctx context.Context, query string, variables map[string]interface{}, options ...Option) ([]byte, *http.Response, io.Reader, Errors) {
    in := GraphQLRequestPayload{
        Query:     query,
        Variables: variables,
    }
    var buf bytes.Buffer
    err := json.NewEncoder(&buf).Encode(in)
    if err != nil {
        return nil, nil, nil, Errors{newError(ErrGraphQLEncode, err)}
    }

    reqReader := bytes.NewReader(buf.Bytes())
    request, err := http.NewRequestWithContext(ctx, http.MethodPost, c.url, reqReader)
    if err != nil {
        e := newError(ErrRequestError, fmt.Errorf("problem constructing request: %w", err))
        if c.debug {
            e = e.withRequest(request, reqReader)
        }
        return nil, nil, nil, Errors{e}
    }
    request.Header.Add("Content-Type", "application/json")

    if c.requestModifier != nil {
        c.requestModifier(request)
    }

    resp, err := c.httpClient.Do(request)
    // ... rest of the code    
}

We can make the below part as the default byte buffer writer. The input will be GraphQLRequestPayload

    var buf bytes.Buffer
    err := json.NewEncoder(&buf).Encode(in)
    if err != nil {
        return nil, nil, nil, Errors{newError(ErrGraphQLEncode, err)}
    }

And, when I want to make multi-part request, then we can just customize this byte buffer to do something like,

var b bytes.Buffer
    w := multipart.NewWriter(&b)

    // Add the query and variables fields
    fw, err := w.CreateFormField("operations")
    if err != nil {
        fmt.Println("Error creating form field: ", err)
        return
    }
    _, _ = fw.Write([]byte(`{"query":"mutation ($input: PostInput!) { createPost(input: $input) { id, title, fileUrl } }","variables":{"input":{"title":"My Title","file":null}}}`))

    fw, err = w.CreateFormField("map")
    if err != nil {
        fmt.Println("Error creating form field: ", err)
        return
    }
    _, _ = fw.Write([]byte(`{"0":["variables.input.file"]}`))

    // Add the file field
    file, err := os.Open(tmpFile.Name())
    if err != nil {
        fmt.Println("Error opening file: ", err)
        return
    }
    defer file.Close()

    fw, err = w.CreateFormFile("0", tmpFile.Name())
    if err != nil {
        fmt.Println("Error creating form file: ", err)
        return
    }
    _, _ = io.Copy(fw, file)

    // Don't forget to close the multipart writer.
    w.Close()

How about that?

If u agree, then I can make a PR to fix this.

hgiasac commented 1 year ago

I get your idea. Feel free to make a PR. However, you need to make sure the change doesn't break the current interface and API design.