Closed pontusntengnas closed 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
}
Thank you for your feedback, it sounds great. I will come back with a more proper implementation according to your proposal.
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.