hasura / go-graphql-client

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

Set operation name on request payload #103

Closed pontusntengnas closed 1 year ago

pontusntengnas commented 1 year ago

This PR sets the operation name not only in the query string but also in the request payload.

My usecase is that the server (gqlgen) only recognises the operation name if it exists in the payload and on the server I need the name for logging purposes.

hgiasac commented 1 year ago

Hi @pontusntengnas,

Thanks for contributing. Your idea makes sense. However, we shouldn't duplicate the logic twice. You can create private functions for constructQuery and constructMutation to return the constructOptionsOutput:


func constructQuery(v interface{}, variables map[string]interface{}, options ...Option) (string, *constructOptionsOutput, error) {
    //
}

// ConstructQuery build GraphQL query string from struct and variables
func ConstructQuery(v interface{}, variables map[string]interface{}, options ...Option) (string, error) {
    query, _, err := constructQuery(v, variables, options...)
    if err != nil {
        return "", err
    }

    return query, err
}

func constructMutation(v interface{}, variables map[string]interface{}, options ...Option) (string, *constructOptionsOutput, error) {
    // ...
}

// ConstructMutation build GraphQL mutation string from struct and variables
func ConstructMutation(v interface{}, variables map[string]interface{}, options ...Option) (string, error) {
    query, _, err := constructMutation(v, variables, options...)
    if err != nil {
        return "", err
    }

    return query, err
}

Then pass the output to request:

func (c *Client) buildAndRequest(ctx context.Context, op operationType, v interface{}, variables map[string]interface{}, options ...Option) ([]byte, *http.Response, io.Reader, Errors) {
    var query string
    var err error
    var optionOutput *constructOptionsOutput
    switch op {
    case queryOperation:
        query, optionOutput, err = constructQuery(v, variables, options...)
    case mutationOperation:
        query, optionOutput, err = constructMutation(v, variables, options...)
    }

    if err != nil {
        return nil, nil, nil, Errors{newError(ErrGraphQLEncode, err)}
    }

    return c.request(ctx, query, variables, optionOutput)
}

// Request the common method that send graphql request
func (c *Client) request(ctx context.Context, query string, variables map[string]interface{}, options *constructOptionsOutput) ([]byte, *http.Response, io.Reader, Errors) {
in := GraphQLRequestPayload{
        Query:     query,
        Variables: variables,
    }

    if options != nil {
        in.OperationName = options.operationName
    }
        // ...
}

For Exec and ExecRaw we need to build options before requesting


// Executes a pre-built query and unmarshals the response into v. Unlike the Query method you have to specify in the query the
// fields that you want to receive as they are not inferred from v. This method is useful if you need to build the query dynamically.
func (c *Client) Exec(ctx context.Context, query string, v interface{}, variables map[string]interface{}, options ...Option) error {

    optionsOutput, err := constructOptions(options)
    if err != nil {
        return err
    }
    data, resp, respBuf, errs := c.request(ctx, query, variables, optionsOutput)
    return c.processResponse(v, data, resp, respBuf, errs)
}

// Executes a pre-built query and returns the raw json message. Unlike the Query method you have to specify in the query the
// fields that you want to receive as they are not inferred from the interface. This method is useful if you need to build the query dynamically.
func (c *Client) ExecRaw(ctx context.Context, query string, variables map[string]interface{}, options ...Option) ([]byte, error) {
    optionsOutput, err := constructOptions(options)
    if err != nil {
        return nil, err
    }
    data, _, _, errs := c.request(ctx, query, variables, optionsOutput)
    if len(errs) > 0 {
        return data, errs
    }
    return data, nil
}
pontusntengnas commented 1 year ago

Thank you for your feedback, it sounds great. I will come back with a more proper implementation according to your proposal.