graphql

package module
v0.14.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 13, 2025 License: MIT Imports: 22 Imported by: 275

README

go-graphql-client

Unit tests

Preface: This is a fork of https://github.com/shurcooL/graphql with extended features (subscription client, named operation)

The subscription client follows Apollo client specification https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md, using WebSocket protocol with https://github.com/coder/websocket, a minimal and idiomatic WebSocket library for Go.

Package graphql provides a GraphQL client implementation.

Note: Before v0.8.0, QueryRaw, MutateRaw, and Subscribe methods return *json.RawMessage. This output type is redundant to be decoded. From v0.8.0, the output type is changed to []byte.

Installation

go-graphql-client requires Go version 1.20 or later. For older Go versions:

  • >= 1.16 < 1.20: downgrade the library to version v0.9.x
  • < 1.16: downgrade the library version below v0.7.1.
go get -u github.com/hasura/go-graphql-client

Usage

Construct a GraphQL client, specifying the GraphQL server URL. Then, you can use it to make GraphQL queries and mutations.

client := graphql.NewClient("https://example.com/graphql", nil)
// Use client...
Authentication

Some GraphQL servers may require authentication. The graphql package does not directly handle authentication. Instead, when creating a new client, you're expected to pass an http.Client that performs authentication.

WithRequestModifier

Use WithRequestModifier method to inject headers into the request before sending to the GraphQL server.

client := graphql.NewClient(endpoint, http.DefaultClient).
  WithRequestModifier(func(r *http.Request) {
	  r.Header.Set("Authorization", "random-token")
  })
OAuth2

The easiest and recommended way to do this is to use the golang.org/x/oauth2 package. You'll need an OAuth token with the right scopes. Then:

import "golang.org/x/oauth2"

func main() {
	src := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: os.Getenv("GRAPHQL_TOKEN")},
	)
	httpClient := oauth2.NewClient(context.Background(), src)

	client := graphql.NewClient("https://example.com/graphql", httpClient)
	// Use client...
Simple Query

To make a GraphQL query, you need to define a corresponding Go type. Variable names must be upper case, see here

For example, to make the following GraphQL query:

query {
	me {
		name
	}
}

You can define this variable:

var query struct {
	Me struct {
		Name string
	}
}

Then call client.Query, passing a pointer to it:

err := client.Query(context.Background(), &query, nil)
if err != nil {
	// Handle error.
}
fmt.Println(query.Me.Name)

// Output: Luke Skywalker
Arguments and Variables

Often, you'll want to specify arguments on some fields. You can use the graphql struct field tag for this.

For example, to make the following GraphQL query:

{
	human(id: "1000") {
		name
		height(unit: METER)
	}
}

You can define this variable:

var q struct {
	Human struct {
		Name   string
		Height float64 `graphql:"height(unit: METER)"`
	} `graphql:"human(id: \"1000\")"`
}

Then call client.Query:

err := client.Query(context.Background(), &q, nil)
if err != nil {
	// Handle error.
}
fmt.Println(q.Human.Name)
fmt.Println(q.Human.Height)

// Output:
// Luke Skywalker
// 1.72

However, that'll only work if the arguments are constant and known in advance. Otherwise, you will need to make use of variables. Replace the constants in the struct field tag with variable names:

var q struct {
	Human struct {
		Name   string
		Height float64 `graphql:"height(unit: $unit)"`
	} `graphql:"human(id: $id)"`
}

Then, define a variables map with their values:

variables := map[string]interface{}{
	"id":   graphql.ID(id),
	"unit": starwars.LengthUnit("METER"),
}

Finally, call client.Query providing variables:

err := client.Query(context.Background(), &q, variables)
if err != nil {
	// Handle error.
}

Variables get encoded as normal JSON. So if you supply a struct for a variable and want to rename fields, you can do this like this:

type Dimensions struct {
	Width int `json:"ship_width"`,
	Height int `json:"ship_height"`
}

var myDimensions = Dimensions{
	Width : 10,
	Height : 6,
}

var mutation struct {
  CreateDimensions struct {
     ID string `graphql:"id"`
  } `graphql:"create_dimensions(ship_dimensions: $ship_dimensions)"`
}

variables := map[string]interface{}{
	"ship_dimensions":  myDimensions,
}

err := client.Mutate(context.TODO(), &mutation, variables)

which will set ship_dimensions to an object with the properties ship_width and ship_height.

Custom scalar tag

Because the generator reflects recursively struct objects, it can't know if the struct is a custom scalar such as JSON. To avoid expansion of the field during query generation, let's add the tag scalar:"true" to the custom scalar. If the scalar implements the JSON decoder interface, it will be automatically decoded.

struct {
	Viewer struct {
		ID         interface{}
		Login      string
		CreatedAt  time.Time
		DatabaseID int
	}
}

// Output:
// {
//   viewer {
//	   id
//		 login
//		 createdAt
//		 databaseId
//   }
// }

struct {
	Viewer struct {
		ID         interface{}
		Login      string
		CreatedAt  time.Time
		DatabaseID int
	} `scalar:"true"`
}

// Output
// { viewer }
Skip GraphQL field
struct {
  Viewer struct {
		ID         interface{} `graphql:"-"`
		Login      string
		CreatedAt  time.Time `graphql:"-"`
		DatabaseID int
  }
}

// Output
// {viewer{login,databaseId}}
Inline Fragments

Some GraphQL queries contain inline fragments. You can use the graphql struct field tag to express them.

For example, to make the following GraphQL query:

{
	hero(episode: "JEDI") {
		name
		... on Droid {
			primaryFunction
		}
		... on Human {
			height
		}
	}
}

You can define this variable:

var q struct {
	Hero struct {
		Name  string
		Droid struct {
			PrimaryFunction string
		} `graphql:"... on Droid"`
		Human struct {
			Height float64
		} `graphql:"... on Human"`
	} `graphql:"hero(episode: \"JEDI\")"`
}

Alternatively, you can define the struct types corresponding to inline fragments, and use them as embedded fields in your query:

type (
	DroidFragment struct {
		PrimaryFunction string
	}
	HumanFragment struct {
		Height float64
	}
)

var q struct {
	Hero struct {
		Name          string
		DroidFragment `graphql:"... on Droid"`
		HumanFragment `graphql:"... on Human"`
	} `graphql:"hero(episode: \"JEDI\")"`
}

Then call client.Query:

err := client.Query(context.Background(), &q, nil)
if err != nil {
	// Handle error.
}
fmt.Println(q.Hero.Name)
fmt.Println(q.Hero.PrimaryFunction)
fmt.Println(q.Hero.Height)

// Output:
// R2-D2
// Astromech
// 0
Specify GraphQL type name

The GraphQL type is automatically inferred from Go type by reflection. However, it's cumbersome in some use cases, e.g. lowercase names. In Go, a type name with a first lowercase letter is considered private. If we need to reuse it for other packages, there are 2 approaches: type alias or implement GetGraphQLType method.

type UserReviewInput struct {
	Review string
	UserID string
}

// type alias
type user_review_input UserReviewInput
// or implement GetGraphQLType method
func (u UserReviewInput) GetGraphQLType() string { return "user_review_input" }

variables := map[string]interface{}{
  "input": UserReviewInput{}
}

//query arguments without GetGraphQLType() defined
//($input: UserReviewInput!)
//query arguments with GetGraphQLType() defined:w
//($input: user_review_input!)
Mutations

Mutations often require information that you can only find out by performing a query first. Let's suppose you've already done that.

For example, to make the following GraphQL mutation:

mutation($ep: Episode!, $review: ReviewInput!) {
	createReview(episode: $ep, review: $review) {
		stars
		commentary
	}
}
variables {
	"ep": "JEDI",
	"review": {
		"stars": 5,
		"commentary": "This is a great movie!"
	}
}

You can define:

var m struct {
	CreateReview struct {
		Stars      int
		Commentary string
	} `graphql:"createReview(episode: $ep, review: $review)"`
}
variables := map[string]interface{}{
	"ep": starwars.Episode("JEDI"),
	"review": starwars.ReviewInput{
		Stars:      5,
		Commentary: "This is a great movie!",
	},
}

Then call client.Mutate:

err := client.Mutate(context.Background(), &m, variables)
if err != nil {
	// Handle error.
}
fmt.Printf("Created a %v star review: %v\n", m.CreateReview.Stars, m.CreateReview.Commentary)

// Output:
// Created a 5 star review: This is a great movie!
Mutations Without Fields

Sometimes, you don't need any fields returned from a mutation. Doing that is easy.

For example, to make the following GraphQL mutation:

mutation($ep: Episode!, $review: ReviewInput!) {
	createReview(episode: $ep, review: $review)
}
variables {
	"ep": "JEDI",
	"review": {
		"stars": 5,
		"commentary": "This is a great movie!"
	}
}

You can define:

var m struct {
	CreateReview string `graphql:"createReview(episode: $ep, review: $review)"`
}
variables := map[string]interface{}{
	"ep": starwars.Episode("JEDI"),
	"review": starwars.ReviewInput{
		Stars:      5,
		Commentary: "This is a great movie!",
	},
}

Then call client.Mutate:

err := client.Mutate(context.Background(), &m, variables)
if err != nil {
	// Handle error.
}
fmt.Printf("Created a review: %s.\n", m.CreateReview)

// Output:
// Created a review: .
Retry Options

Construct the client with the following options:

client := graphql.NewClient("/graphql", http.DefaultClient,
	// number of retries
	graphql.WithRetry(3),
	// base backoff interval. Optional, default 1 second.
	// Prioritize the Retry-After header if it exists in the response.
	graphql.WithRetryBaseDelay(time.Second),
	// exponential rate. Optional, default 2.0
	graphql.WithRetryExponentialRate(2),
	// retry on http statuses. Optional, default: 429, 502, 503, 504
	graphql.WithRetryHTTPStatus([]int{http.StatusServiceUnavailable}),
	// if the http status is 200 but the graphql response is error, 
	// use this option to check if the error is retryable.
	graphql.WithRetryOnGraphQLError(func(errs graphql.Errors) bool {
		return len(errs) == 1 && errs[0].Message == "Field 'user' is missing required arguments: login"
	}),
)
Subscription
Usage

Construct a Subscription client, specifying the GraphQL server URL.

client := graphql.NewSubscriptionClient("wss://example.com/graphql")
defer client.Close()

// Subscribe subscriptions
// ...
// finally run the client
client.Run()
Subscribe

To make a GraphQL subscription, you need to define a corresponding Go type.

For example, to make the following GraphQL query:

subscription {
	me {
		name
	}
}

You can define this variable:

var subscription struct {
	Me struct {
		Name string
	}
}

Then call client.Subscribe, passing a pointer to it:

subscriptionId, err := client.Subscribe(&query, nil, func(dataValue []byte, errValue error) error {
	if errValue != nil {
		// handle error
		// if returns error, it will failback to `onError` event
		return nil
	}
	data := query{}
	// use the github.com/hasura/go-graphql-client/pkg/jsonutil package
	err := jsonutil.UnmarshalGraphQL(dataValue, &data)

	fmt.Println(query.Me.Name)

	// Output: Luke Skywalker
	return nil
})

if err != nil {
	// Handle error.
}
Stop the subscription

You can programmatically stop the subscription while the client is running by using the Unsubscribe method or returning a special error to stop it in the callback.

subscriptionId, err := client.Subscribe(&query, nil, func(dataValue []byte, errValue error) error {
	// ...
	// return this error to stop the subscription in the callback
	return graphql.ErrSubscriptionStopped
})

if err != nil {
	// Handle error.
}

// unsubscribe the subscription while the client is running with the subscription ID
client.Unsubscribe(subscriptionId)
Authentication

The subscription client is authenticated with GraphQL server through connection params:

client := graphql.NewSubscriptionClient("wss://example.com/graphql").
	WithConnectionParams(map[string]interface{}{
		"headers": map[string]string{
				"authentication": "...",
		},
	}).
	// or lazy parameters with function
  WithConnectionParamsFn(func () map[string]interface{} {
		return map[string]interface{} {
			"headers": map[string]string{
  				"authentication": "...",
  		},
		}
	})

Some servers validate custom auth tokens on the header instead. To authenticate with headers, use WebsocketOptions:

client := graphql.NewSubscriptionClient(serverEndpoint).
    WithWebSocketOptions(graphql.WebsocketOptions{
        HTTPHeader: http.Header{
            "Authorization": []string{"Bearer random-secret"},
        },
    })
Options
client.
	//  write timeout of websocket client
	WithTimeout(time.Minute).
	// When the websocket server was stopped, the client will retry connecting every second until timeout
	WithRetryTimeout(time.Minute).
	// sets loging function to print out received messages. By default, nothing is printed
	WithLog(log.Println).
	// max size of response message
	WithReadLimit(10*1024*1024).
	// these operation event logs won't be printed
	WithoutLogTypes(graphql.GQLData, graphql.GQLConnectionKeepAlive).
	// the client should exit when all subscriptions were closed, default true
	WithExitWhenNoSubscription(false).
	// WithRetryStatusCodes allow retry the subscription connection when receiving one of these codes
	// the input parameter can be number string or range, e.g 4000-5000
	WithRetryStatusCodes("4000", "4000-4050").
	// WithSyncMode subscription messages are executed in sequence (without goroutine)
	WithSyncMode(true)
Subscription Protocols

The subscription client supports 2 protocols:

The protocol can be switchable by the WithProtocol function.

client.WithProtocol(graphql.GraphQLWS)
Handle connection error

GraphQL servers can define custom WebSocket error codes in the 3000-4999 range. For example, in the graphql-ws protocol, the server sends the invalid message error with status 4400. In this case, the subscription client should let the user handle the error through the OnError event.

client := graphql.NewSubscriptionClient(serverEndpoint).
  OnError(func(sc *graphql.SubscriptionClient, err error) error {
  	if sc.IsUnauthorized(err) || strings.Contains(err.Error(), "invalid x-hasura-admin-secret/x-hasura-access-key") {
			// exit the subscription client due to unauthorized error
  		return err
  	}

		if sc.IsInternalConnectionError(err) {
			return err
		}

		// otherwise ignore the error and the client will restart.
  	return nil
  })
Connection Initialisation Timeout

The connection initialisation timeout error happens when the subscription client emitted the ConnectionInit event but hasn't received any message for a long duration. The default timeout is a minute. You can adjust the timeout by calling the WithConnectionInitialisationTimeout method. This error is disabled if the timeout duration is 0.

client := graphql.NewSubscriptionClient(serverEndpoint).
	WithConnectionInitialisationTimeout(2*time.Minute).
  OnError(func(sc *graphql.SubscriptionClient, err error) error {
  	if sc.IsConnectionInitialisationTimeout(err) {
			// restart the client
  		return nil
  	}

		// catch other errors...

		return err
  })
WebSocket Connection Idle Timeout

This error happens if the websocket connection idle timeout duration is larger than 0 and the subscription client doesn't receive any message from the server, include keep-alive message for a long duration. The setting is disabled by default and can be configured by the WithWebsocketConnectionIdleTimeout method.

client := graphql.NewSubscriptionClient(serverEndpoint).
	WithWebsocketConnectionIdleTimeout(time.Minute).
  OnError(func(sc *graphql.SubscriptionClient, err error) error {
  	if sc.IsWebsocketConnectionIdleTimeout(err) {
			// restart the client
  		return nil
  	}

		// catch other errors...

		return err
  })
Events
// OnConnected event is triggered when the websocket connected to GraphQL server sucessfully
client.OnConnected(fn func())

// OnDisconnected event is triggered when the websocket client was disconnected
client.OnDisconnected(fn func())

// OnError event is triggered when there is any connection error. This is bottom exception handler level
// If this function is empty, or returns nil, the error is ignored
// If returns error, the websocket connection will be terminated
client.OnError(onError func(sc *SubscriptionClient, err error) error)

// OnConnectionAlive event is triggered when the websocket receive a connection alive message (differs per protocol)
client.OnConnectionAlive(fn func())

// OnSubscriptionComplete event is triggered when the subscription receives a terminated message from the server
client.OnSubscriptionComplete(fn func(sub Subscription))
Custom HTTP Client

Use WithWebSocketOptions to customize the HTTP client which is used by the subscription client.

client.WithWebSocketOptions(WebsocketOptions{
	HTTPClient: &http.Client{
		Transport: http.DefaultTransport,
		Timeout: time.Minute,
	}
})
Custom WebSocket client

By default, the subscription client uses coder WebSocket client. If you need to customize the client or prefer using Gorilla WebSocket, let's follow the WebSocket interface and replace the constructor with WithWebSocket method:

// WebsocketHandler abstracts WebSocket connection functions
// ReadJSON and WriteJSON data of a frame from the WebSocket connection.
// Close the WebSocket connection.
type WebsocketConn interface {
	ReadJSON(v interface{}) error
	WriteJSON(v interface{}) error
	Close() error
	// SetReadLimit sets the maximum size in bytes for a message read from the peer. If a
	// message exceeds the limit, the connection sends a close message to the peer
	// and returns ErrReadLimit to the application.
	SetReadLimit(limit int64)
}

// WithWebSocket replaces customized websocket client constructor
func (sc *SubscriptionClient) WithWebSocket(fn func(sc *SubscriptionClient) (WebsocketConn, error)) *SubscriptionClient

Example


// the default websocket constructor
func newWebsocketConn(sc *SubscriptionClient) (WebsocketConn, error) {
	options := &websocket.DialOptions{
		Subprotocols: []string{"graphql-ws"},
	}
	c, _, err := websocket.Dial(sc.GetContext(), sc.GetURL(), options)
	if err != nil {
		return nil, err
	}

	// The default WebsocketHandler implementation using coder's
	return &WebsocketHandler{
		ctx:     sc.GetContext(),
		Conn:    c,
		timeout: sc.GetTimeout(),
	}, nil
}

client := graphql.NewSubscriptionClient("wss://example.com/graphql")
defer client.Close()

client.WithWebSocket(newWebsocketConn)

client.Run()
Options

There are extensible parts in the GraphQL query that we sometimes use. They are optional so we shouldn't require them in the method. To make it flexible, we can abstract these options as optional arguments that follow this interface.

type Option interface {
	Type() OptionType
	String() string
}

client.Query(ctx context.Context, q interface{}, variables map[string]interface{}, options ...Option) error

Currently, there are 4 option types:

  • operation_name
  • operation_directive
  • bind_extensions
  • bind_response_headers

The operation name option is built-in because it is unique. We can use the option directly with OperationName.

// query MyQuery {
//	...
// }
client.Query(ctx, &q, variables, graphql.OperationName("MyQuery"))

In contrast, operation directives are various and customizable on different GraphQL servers. There isn't any built-in directive in the library. You need to define yourself. For example:

// define @cached directive for Hasura queries
// https://hasura.io/docs/latest/graphql/cloud/response-caching.html#enable-caching
type cachedDirective struct {
	ttl int
}

func (cd cachedDirective) Type() OptionType {
	// operation_directive
	return graphql.OptionTypeOperationDirective
}

func (cd cachedDirective) String() string {
	if cd.ttl <= 0 {
		return "@cached"
	}
	return fmt.Sprintf("@cached(ttl: %d)", cd.ttl)
}

// query MyQuery @cached {
//	...
// }
client.Query(ctx, &q, variables, graphql.OperationName("MyQuery"), cachedDirective{})
Execute pre-built query

The Exec function allows you to execute pre-built queries. While using reflection to build queries is convenient as you get some resemblance of type safety, it gets very cumbersome when you need to create queries semi-dynamically. For instance, imagine you are building a CLI tool to query data from a graphql endpoint and you want users to be able to narrow down the query by passing CLI flags or something.

// filters would be built dynamically somehow from the command line flags
filters := []string{
   `fieldA: {subfieldA: {_eq: "a"}}`,
   `fieldB: {_eq: "b"}`,
   ...
}

query := "query{something(where: {" + strings.Join(filters, ", ") + "}){id}}"
res := struct {
	Somethings []Something
}{}

if err := client.Exec(ctx, query, &res, map[string]any{}); err != nil {
	panic(err)
}

subscription := "subscription{something(where: {" + strings.Join(filters, ", ") + "}){id}}"
subscriptionId, err := subscriptionClient.Exec(subscription, nil, func(dataValue []byte, errValue error) error {
	if errValue != nil {
		// handle error
		// if returns error, it will failback to `onError` event
		return nil
	}
	data := query{}
	err := json.Unmarshal(dataValue, &data)
	// ...
})

If you prefer decoding JSON yourself, use ExecRaw instead.

query := `query{something(where: { foo: { _eq: "bar" }}){id}}`
var res struct {
	Somethings []Something `json:"something"`
}

raw, err := client.ExecRaw(ctx, query, map[string]any{})
if err != nil {
	panic(err)
}

err = json.Unmarshal(raw, &res)
Get extensions from response

The response map may also contain an entry with the extensions key. To decode this field you need to bind a struct or map pointer. The client will optionally unmarshal the field using JSON decoder.

var q struct {
	User struct {
		ID   string `graphql:"id"`
		Name string `graphql:"name"`
	}
}

var ext struct {
	ID     int    `json:"id"`
	Domain string `json:"domain"`
}

err := client.Query(context.Background(), &q, map[string]interface{}{}, graphql.BindExtensions(&ext))
if err != nil {
	t.Fatal(err)
}

Additionally, if you need information about the extensions returned in the response use ExecRawWithExtensions. This function returns a map with extensions as the second variable.

query := `query{something(where: { foo: { _eq: "bar" }}){id}}`

data, extensions, err := client.ExecRawWithExtensions(ctx, query, map[string]any{})
if err != nil {
	panic(err)
}

// You can now use the `extensions` variable to access the extensions data
fmt.Println("Extensions:", extensions)
Get headers from response

Use the BindResponseHeaders option to bind headers from the response.

headers := http.Header{}
err := client.Query(context.TODO(), &q, map[string]any{}, graphql.BindResponseHeaders(&headers))
if err != nil {
  panic(err)
}

fmt.Println(headers.Get("content-type"))
// application/json
With operation name (deprecated)
func (c *Client) NamedQuery(ctx context.Context, name string, q interface{}, variables map[string]interface{}) error

func (c *Client) NamedMutate(ctx context.Context, name string, q interface{}, variables map[string]interface{}) error

func (sc *SubscriptionClient) NamedSubscribe(name string, v interface{}, variables map[string]interface{}, handler func(message []byte, err error) error) (string, error)
Raw bytes response

In the case when we developers want to decode JSON response ourselves. Moreover, the default UnmarshalGraphQL function isn't ideal with complicated nested interfaces

func (c *Client) QueryRaw(ctx context.Context, q interface{}, variables map[string]interface{}) ([]byte, error)

func (c *Client) MutateRaw(ctx context.Context, q interface{}, variables map[string]interface{}) ([]byte, error)

func (c *Client) NamedQueryRaw(ctx context.Context, name string, q interface{}, variables map[string]interface{}) ([]byte, error)

func (c *Client) NamedMutateRaw(ctx context.Context, name string, q interface{}, variables map[string]interface{}) ([]byte, error)
Multiple mutations with ordered map

You might need to make multiple mutations in a single query. It's not very convenient with structs so you can use ordered map [][2]interface{} instead.

For example, to make the following GraphQL mutation:

mutation($login1: String!, $login2: String!, $login3: String!) {
	createUser(login: $login1) { login }
	createUser(login: $login2) { login }
	createUser(login: $login3) { login }
}
variables {
	"login1": "grihabor",
	"login2": "diman",
	"login3": "indigo"
}

You can define:

type CreateUser struct {
	Login string
}
m := [][2]interface{}{
	{"createUser(login: $login1)", &CreateUser{}},
	{"createUser(login: $login2)", &CreateUser{}},
	{"createUser(login: $login3)", &CreateUser{}},
}
variables := map[string]interface{}{
	"login1": "grihabor",
	"login2": "diman",
	"login3": "indigo",
}
Debugging and Unit test

Enable debug mode with the WithDebug function. If the request fails, the request and response information will be included in extensions[].internal property.

{
  "errors": [
    {
      "message": "Field 'user' is missing required arguments: login",
      "extensions": {
        "internal": {
          "request": {
            "body": "{\"query\":\"{user{name}}\"}",
            "headers": {
              "Content-Type": ["application/json"]
            }
          },
          "response": {
            "body": "{\"errors\": [{\"message\": \"Field 'user' is missing required arguments: login\",\"locations\": [{\"line\": 7,\"column\": 3}]}]}",
            "headers": {
              "Content-Type": ["application/json"]
            }
          }
        }
      },
      "locations": [
        {
          "line": 7,
          "column": 3
        }
      ]
    }
  ]
}

For debugging queries, you can use Construct* functions to see what the generated query looks like:

// ConstructQuery build GraphQL query string from struct and variables
func ConstructQuery(v interface{}, variables map[string]interface{}, options ...Option) (string, error)

// ConstructMutation build GraphQL mutation string from struct and variables
func ConstructMutation(v interface{}, variables map[string]interface{}, options ...Option) (string, error)

// ConstructSubscription build GraphQL subscription string from struct and variables
func ConstructSubscription(v interface{}, variables map[string]interface{}, options ...Option) (string, string, error)

// UnmarshalGraphQL parses the JSON-encoded GraphQL response data and stores
// the result in the GraphQL query data structure pointed to by v.
func UnmarshalGraphQL(data []byte, v interface{}) error

Because the GraphQL query string is generated in runtime using reflection, it isn't really safe. To ensure the GraphQL query is expected, it's necessary to write some unit tests for query construction.

Directories

Path Synopsis
example/graphqldev graphqldev is a test program currently being used for developing graphql package.
ident Package ident provides functions for parsing and converting identifier names between various naming conventions.
internal/jsonutil Package jsonutil provides a function for decoding JSON into a GraphQL query data structure.

References

License

Documentation

Overview

Package graphql provides a GraphQL client implementation.

For more information, see package github.com/hasura/go-graphql-client

For now, see README for more details.

Index

Constants

View Source
const (
	ErrRequestError            = "request_error"
	ErrJsonEncode              = "json_encode_error"
	ErrJsonDecode              = "json_decode_error"
	ErrGraphQLEncode           = "graphql_encode_error"
	ErrGraphQLDecode           = "graphql_decode_error"
	ErrGraphQLExtensionsDecode = "graphql_extensions_decode_error"
)
View Source
const (

	// SubscriptionWaiting the subscription hasn't been registered to the server.
	SubscriptionWaiting SubscriptionStatus = 0
	// SubscriptionRunning the subscription is up and running.
	SubscriptionRunning SubscriptionStatus = 1
	// SubscriptionUnsubscribed the subscription was manually unsubscribed by the user.
	SubscriptionUnsubscribed SubscriptionStatus = 2

	// SubscriptionsTransportWS the enum implements the subscription transport that follows Apollo's subscriptions-transport-ws protocol specification
	// https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md
	SubscriptionsTransportWS SubscriptionProtocolType = "subscriptions-transport-ws"

	// GraphQLWS enum implements GraphQL over WebSocket Protocol (graphql-ws)
	// https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md
	GraphQLWS SubscriptionProtocolType = "graphql-ws"

	// Receiving a message of a type or format which is not specified in this document
	// The <error-message> can be vaguely descriptive on why the received message is invalid.
	StatusInvalidMessage websocket.StatusCode = 4400
	// if the connection is not acknowledged, the socket will be closed immediately with the event 4401: Unauthorized.
	StatusUnauthorized websocket.StatusCode = 4401
	// if the connection is unauthorized and be rejected by the server.
	StatusForbidden websocket.StatusCode = 4403
	// Connection initialisation timeout.
	StatusConnectionInitialisationTimeout websocket.StatusCode = 4408
	// Subscriber for <generated-id> already exists.
	StatusSubscriberAlreadyExists websocket.StatusCode = 4409
	// Too many initialisation requests.
	StatusTooManyInitialisationRequests websocket.StatusCode = 4429
)
View Source
const (
	// Unknown operation type, for logging only.
	GQLUnknown OperationMessageType = "unknown"
	// Internal status, for logging only.
	GQLInternal OperationMessageType = "internal"

	// @deprecated: use GQLUnknown instead.
	GQL_UNKNOWN = GQLUnknown
	// @deprecated: use GQLInternal instead.
	GQL_INTERNAL = GQLInternal
)
View Source
const (
	// The server may responses with this message to the GQL_CONNECTION_INIT from client, indicates the server rejected the connection.
	GQLConnectionError OperationMessageType = "connection_error"
	// Client sends this message to execute GraphQL operation.
	GQLStart OperationMessageType = "start"
	// Client sends this message in order to stop a running GraphQL operation execution (for example: unsubscribe).
	GQLStop OperationMessageType = "stop"
	// Client sends this message in order to stop a running GraphQL operation execution (for example: unsubscribe).
	GQLData OperationMessageType = "data"
	// Server message that should be sent right after each GQL_CONNECTION_ACK processed and then periodically to keep the client connection alive.
	// The client starts to consider the keep alive message only upon the first received keep alive message from the server.
	GQLConnectionKeepAlive OperationMessageType = "ka"
	// Client sends this message to terminate the connection.
	GQLConnectionTerminate OperationMessageType = "connection_terminate"

	// @deprecated: use GQLConnectionInit instead.
	GQL_CONNECTION_INIT = GQLConnectionInit
	// @deprecated: use GQLConnectionError instead.
	GQL_CONNECTION_ERROR = GQLConnectionError
	// @deprecated: use GQLStart instead.
	GQL_START = GQLStart
	// @deprecated: use GQLStop instead.
	GQL_STOP = GQLStop
	// @deprecated: use GQLError instead.
	GQL_ERROR = GQLError
	// @deprecated: use GQLData instead.
	GQL_DATA = GQLData
	// @deprecated: use GQLComplete instead.
	GQL_COMPLETE = GQLComplete
	// @deprecated: use GQLConnectionKeepAlive instead.
	GQL_CONNECTION_KEEP_ALIVE = GQLConnectionKeepAlive
	// @deprecated: use GQLConnectionAck instead.
	GQL_CONNECTION_ACK = GQLConnectionAck
	// @deprecated: use GQLConnectionTerminate instead.
	GQL_CONNECTION_TERMINATE = GQLConnectionTerminate
)

Variables

View Source
var (
	// ErrSubscriptionStopped a special error which forces the subscription stop.
	ErrSubscriptionStopped = errors.New("subscription stopped")
	// ErrSubscriptionNotExists an error denoting that subscription does not exist.
	ErrSubscriptionNotExists = errors.New("subscription does not exist")
	// ErrWebsocketConnectionIdleTimeout indicates that the websocket connection has not received any new messages for a long interval.
	ErrWebsocketConnectionIdleTimeout = errors.New("websocket connection idle timeout")
)

Functions

func ConstructMutation added in v0.6.4

func ConstructMutation(
	v interface{},
	variables map[string]interface{},
	options ...Option,
) (string, error)

ConstructMutation build GraphQL mutation string from struct and variables.

func ConstructQuery added in v0.6.4

func ConstructQuery(
	v interface{},
	variables map[string]interface{},
	options ...Option,
) (string, error)

ConstructQuery build GraphQL query string from struct and variables.

func ConstructSubscription added in v0.6.4

func ConstructSubscription(
	v interface{},
	variables map[string]interface{},
	options ...Option,
) (string, string, error)

ConstructSubscription build GraphQL subscription string from struct and variables.

func ElemSafe added in v0.5.0

func ElemSafe(v reflect.Value) reflect.Value

func FieldSafe added in v0.5.0

func FieldSafe(valStruct reflect.Value, i int) reflect.Value

func IndexSafe added in v0.5.0

func IndexSafe(v reflect.Value, i int) reflect.Value

func UnmarshalGraphQL added in v0.6.4

func UnmarshalGraphQL(data []byte, v any) error

This function is re-exported from the internal package.

Types

type Boolean

type Boolean bool

Boolean represents true or false values. Deprecated.

func NewBoolean

func NewBoolean(v Boolean) *Boolean

NewBoolean is a helper to make a new *Boolean. Deprecated.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a GraphQL client.

func NewClient

func NewClient(url string, httpClient Doer, options ...ClientOption) *Client

NewClient creates a GraphQL client targeting the specified GraphQL server URL. If httpClient is nil, then http.DefaultClient is used.

func (*Client) Exec added in v0.7.0

func (c *Client) Exec(
	ctx context.Context,
	query string,
	v any,
	variables map[string]any,
	options ...Option,
) error

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 (*Client) ExecRaw added in v0.8.0

func (c *Client) ExecRaw(
	ctx context.Context,
	query string,
	variables map[string]any,
	options ...Option,
) ([]byte, error)

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 (*Client) ExecRawWithExtensions added in v0.13.0

func (c *Client) ExecRawWithExtensions(
	ctx context.Context,
	query string,
	variables map[string]any,
	options ...Option,
) ([]byte, []byte, error)

ExecRawWithExtensions execute a pre-built query and returns the raw json message and a map with extensions (values also as raw json objects). 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 (*Client) Mutate

func (c *Client) Mutate(
	ctx context.Context,
	m any,
	variables map[string]any,
	options ...Option,
) error

Mutate executes a single GraphQL mutation request, with a mutation derived from m, populating the response into it. m should be a pointer to struct that corresponds to the GraphQL schema.

func (*Client) MutateRaw added in v0.2.0

func (c *Client) MutateRaw(
	ctx context.Context,
	m any,
	variables map[string]any,
	options ...Option,
) ([]byte, error)

MutateRaw executes a single GraphQL mutation request, with a mutation derived from m, populating the response into it. m should be a pointer to struct that corresponds to the GraphQL schema. return raw bytes message.

func (*Client) NamedMutate deprecated

func (c *Client) NamedMutate(
	ctx context.Context,
	name string,
	m any,
	variables map[string]any,
	options ...Option,
) error

Deprecated: this is the shortcut of Mutate method, with NewOperationName option.

func (*Client) NamedMutateRaw added in v0.2.0

func (c *Client) NamedMutateRaw(
	ctx context.Context,
	name string,
	m any,
	variables map[string]any,
	options ...Option,
) ([]byte, error)

NamedMutateRaw executes a single GraphQL mutation request, with operation name return raw bytes message.

func (*Client) NamedQuery deprecated

func (c *Client) NamedQuery(
	ctx context.Context,
	name string,
	q any,
	variables map[string]any,
	options ...Option,
) error

Deprecated: this is the shortcut of Query method, with NewOperationName option.

func (*Client) NamedQueryRaw added in v0.2.0

func (c *Client) NamedQueryRaw(
	ctx context.Context,
	name string,
	q any,
	variables map[string]any,
	options ...Option,
) ([]byte, error)

NamedQueryRaw executes a single GraphQL query request, with operation name return raw bytes message.

func (*Client) Query

func (c *Client) Query(
	ctx context.Context,
	q any,
	variables map[string]any,
	options ...Option,
) error

Query executes a single GraphQL query request, with a query derived from q, populating the response into it. q should be a pointer to struct that corresponds to the GraphQL schema.

func (*Client) QueryRaw added in v0.2.0

func (c *Client) QueryRaw(
	ctx context.Context,
	q any,
	variables map[string]any,
	options ...Option,
) ([]byte, error)

Query executes a single GraphQL query request, with a query derived from q, populating the response into it. q should be a pointer to struct that corresponds to the GraphQL schema. return raw bytes message.

func (*Client) WithDebug added in v0.6.4

func (c *Client) WithDebug(debug bool) *Client

WithDebug enable debug mode to print internal error detail.

func (*Client) WithRequestModifier added in v0.6.0

func (c *Client) WithRequestModifier(f RequestModifier) *Client

(i.e. different authentication headers for multitenant applications).

type ClientOption added in v0.14.0

type ClientOption func(c *Client)

ClientOption is used to configure client with options.

func WithRetry added in v0.14.0

func WithRetry(maxRetries int) ClientOption

WithRetry creates an option to indicate the number of retries.

func WithRetryBaseDelay added in v0.14.0

func WithRetryBaseDelay(delay time.Duration) ClientOption

WithRetryBaseDelay creates an option to indicate the base delay factor of retries.

func WithRetryExponentialRate added in v0.14.0

func WithRetryExponentialRate(rate float64) ClientOption

WithRetryExponentialRate creates an option to indicate the exponential rate of retries.

func WithRetryHTTPStatus added in v0.14.0

func WithRetryHTTPStatus(status []int) ClientOption

WithRetryHTTPStatus creates an option to retry if the HTTP response status is in the status slice.

func WithRetryOnGraphQLError added in v0.14.0

func WithRetryOnGraphQLError(callback func(errs Errors) bool) ClientOption

WithRetryOnGraphQLError creates a callback option to check if the graphql error is retryable.

type CreateWebSocketConnFunc added in v0.14.0

type CreateWebSocketConnFunc func(ctx context.Context, endpoint string, options WebsocketOptions) (WebsocketConn, error)

CreateWebSocketConnFunc represents the function interface to create a WebSocket connection.

type Doer added in v0.9.0

type Doer interface {
	Do(req *http.Request) (*http.Response, error)
}

Doer interface has the method required to use a type as custom http client. The net/*http.Client type satisfies this interface.

type Error added in v0.6.4

type Error struct {
	Message    string         `json:"message"`
	Extensions map[string]any `json:"extensions"`
	Locations  []struct {
		Line   int `json:"line"`
		Column int `json:"column"`
	} `json:"locations"`
	Path []any `json:"path"`
	// contains filtered or unexported fields
}

func (Error) Error added in v0.6.4

func (e Error) Error() string

Error implements error interface.

func (Error) Unwrap added in v0.12.0

func (e Error) Unwrap() error

Unwrap implement the unwrap interface.

type Errors added in v0.5.2

type Errors []Error

errors represents the "errors" array in a response from a GraphQL server. If returned via error interface, the slice is expected to contain at least 1 element.

Specification: https://facebook.github.io/graphql/#sec-Errors.

func (Errors) Error added in v0.5.2

func (e Errors) Error() string

Error implements error interface.

func (Errors) Unwrap added in v0.12.0

func (e Errors) Unwrap() []error

Unwrap implements the error unwrap interface.

type Float

type Float float64

Float represents signed double-precision fractional values as specified by IEEE 754. Deprecated.

func NewFloat

func NewFloat(v Float) *Float

NewFloat is a helper to make a new *Float. Deprecated.

type GraphQLRequestPayload added in v0.9.0

type GraphQLRequestPayload struct {
	Query         string                 `json:"query"`
	Variables     map[string]interface{} `json:"variables,omitempty"`
	OperationName string                 `json:"operationName,omitempty"`
}

GraphQLRequestPayload represents the graphql JSON-encoded request body https://graphql.org/learn/serving-over-http/#post-request

type GraphQLType added in v0.7.0

type GraphQLType interface {
	GetGraphQLType() string
}

GraphQLType interface is used to specify the GraphQL type associated with a particular type. If a type implements this interface, the name of the variable used while creating the GraphQL query will be the output of the function defined below.

In the current implementation, the GetGraphQLType function is applied to the zero value of the type to get the GraphQL type. So those who are implementing the function should avoid referencing the value of the type inside the function. Further, by this design, the output of the GetGraphQLType function will be a constant.

type ID

type ID string

ID represents a unique identifier that is Base64 obfuscated. It is often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as "VXNlci0xMA==") or integer (such as 4) input value will be accepted as an ID.

func NewID

func NewID(v interface{}) *ID

NewID is a helper to make a new *ID.

func ToID added in v0.8.1

func ToID(v interface{}) ID

ToID is a helper for if you need to get the string version of an integer or a string for the id.

type Int

type Int int32

Int represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. Deprecated.

func NewInt

func NewInt(v Int) *Int

NewInt is a helper to make a new *Int. Deprecated.

type NetworkError added in v0.13.0

type NetworkError struct {
	// contains filtered or unexported fields
}

func (NetworkError) Body added in v0.13.0

func (e NetworkError) Body() string

func (NetworkError) Error added in v0.13.0

func (e NetworkError) Error() string

func (NetworkError) StatusCode added in v0.13.0

func (e NetworkError) StatusCode() int

type OperationMessage

type OperationMessage struct {
	ID      string               `json:"id,omitempty"`
	Type    OperationMessageType `json:"type"`
	Payload json.RawMessage      `json:"payload,omitempty"`
}

OperationMessage represents a subscription operation message.

func (OperationMessage) String

func (om OperationMessage) String() string

String overrides the default Stringer to return json string for debugging.

type OperationMessageType

type OperationMessageType string

OperationMessageType represents a subscription message enum type.

const (
	// Indicates that the client wants to establish a connection within the existing socket.
	// This connection is not the actual WebSocket communication channel, but is rather a frame within it asking the server to allow future operation requests.
	GQLConnectionInit OperationMessageType = "connection_init"
	// Expected response to the ConnectionInit message from the client acknowledging a successful connection with the server.
	GQLConnectionAck OperationMessageType = "connection_ack"
	// The Ping message can be sent at any time within the established socket.
	GQLPing OperationMessageType = "ping"
	// The response to the Ping message. Must be sent as soon as the Ping message is received.
	GQLPong OperationMessageType = "pong"
	// Requests an operation specified in the message payload. This message provides a unique ID field to connect published messages to the operation requested by this message.
	GQLSubscribe OperationMessageType = "subscribe"
	// Operation execution result(s) from the source stream created by the binding Subscribe message. After all results have been emitted, the Complete message will follow indicating stream completion.
	GQLNext OperationMessageType = "next"
	// Operation execution error(s) in response to the Subscribe message.
	// This can occur before execution starts, usually due to validation errors, or during the execution of the request.
	GQLError OperationMessageType = "error"
	// indicates that the requested operation execution has completed. If the server dispatched the Error message relative to the original Subscribe message, no Complete message will be emitted.
	GQLComplete OperationMessageType = "complete"
)

type Option added in v0.4.0

type Option interface {
	// Type returns the supported type of the renderer
	// available types: operation_name and operation_directive
	Type() OptionType
}

They are optional parts. By default GraphQL queries can request data without them.

func BindExtensions added in v0.13.0

func BindExtensions(value any) Option

BindExtensions bind the struct pointer to decode extensions from json response.

func BindResponseHeaders added in v0.14.0

func BindResponseHeaders(value *http.Header) Option

BindExtensionsBindResponseHeaders bind the header response to the pointer.

func OperationName added in v0.4.0

func OperationName(name string) Option

OperationName creates the operation name option.

type OptionType added in v0.4.0

type OptionType string

OptionType represents the logic of graphql query construction.

const (
	OptionTypeOperationDirective OptionType = "operation_directive"
)

type RequestModifier added in v0.6.0

type RequestModifier func(*http.Request)

headers amongst other things.

type String

type String string

String represents textual data as UTF-8 character sequences. This type is most often used by GraphQL to represent free-form human-readable text. Deprecated.

func NewString

func NewString(v String) *String

NewString is a helper to make a new *String. Deprecated.

type Subscription added in v0.9.0

type Subscription struct {
	// contains filtered or unexported fields
}

Subscription stores the subscription declaration and its state.

func (Subscription) Clone added in v0.14.0

func (s Subscription) Clone() Subscription

The ID is newly generated to avoid subscription id conflict errors from the server.

func (Subscription) GetHandler added in v0.9.0

func (s Subscription) GetHandler() func(data []byte, err error)

GetHandler a public getter for the subscription handler.

func (Subscription) GetID added in v0.9.2

func (s Subscription) GetID() string

GetID returns the subscription ID.

func (Subscription) GetKey added in v0.9.2

func (s Subscription) GetKey() string

It is used for searching because the subscription id is refreshed whenever the client reset.

func (Subscription) GetPayload added in v0.9.0

func (s Subscription) GetPayload() GraphQLRequestPayload

GetPayload returns the graphql request payload.

func (Subscription) GetStatus added in v0.9.2

func (s Subscription) GetStatus() SubscriptionStatus

GetStatus a public getter for the subscription status.

func (*Subscription) SetStatus added in v0.9.2

func (s *Subscription) SetStatus(status SubscriptionStatus)

SetStatus a public getter for the subscription status.

type SubscriptionClient

type SubscriptionClient struct {
	// contains filtered or unexported fields
}

SubscriptionClient is a GraphQL subscription client.

func NewSubscriptionClient

func NewSubscriptionClient(url string) *SubscriptionClient

NewSubscriptionClient constructs new subscription client.

func (*SubscriptionClient) Close

func (sc *SubscriptionClient) Close() error

Close closes all subscription channel and websocket as well.

func (*SubscriptionClient) Exec added in v0.7.1

func (sc *SubscriptionClient) Exec(
	query string,
	variables map[string]interface{},
	handler func(message []byte, err error) error,
) (string, error)

Exec sends start message to server and open a channel to receive data, with raw query.

func (*SubscriptionClient) GetContext

func (sc *SubscriptionClient) GetContext() context.Context

GetContext returns current context of subscription client.

func (*SubscriptionClient) GetReadTimeout added in v0.14.0

func (sc *SubscriptionClient) GetReadTimeout() time.Duration

GetReadTimeout returns read timeout of websocket client.

func (*SubscriptionClient) GetSubscription added in v0.12.0

func (sc *SubscriptionClient) GetSubscription(id string) *Subscription

GetSubscription get the subscription state by id.

func (*SubscriptionClient) GetSubscriptions added in v0.12.1

func (sc *SubscriptionClient) GetSubscriptions() map[string]Subscription

GetSubscriptions get the list of active subscriptions.

func (*SubscriptionClient) GetTimeout

func (sc *SubscriptionClient) GetTimeout() time.Duration

GetTimeout returns write timeout of websocket client. Deprecated: use GetWriteTimeout instead.

func (*SubscriptionClient) GetURL

func (sc *SubscriptionClient) GetURL() string

GetURL returns GraphQL server's URL.

func (*SubscriptionClient) GetWebSocketStatusCode added in v0.14.0

func (sc *SubscriptionClient) GetWebSocketStatusCode(err error) websocket.StatusCode

GetWebSocketStatusCode gets the status code of the Websocket error.

func (*SubscriptionClient) GetWriteTimeout added in v0.14.0

func (sc *SubscriptionClient) GetWriteTimeout() time.Duration

GetWriteTimeout returns write timeout of websocket client.

func (*SubscriptionClient) IsConnectionInitialisationTimeout added in v0.14.0

func (sc *SubscriptionClient) IsConnectionInitialisationTimeout(err error) bool

IsConnectionInitialisationTimeoutError checks if the input error is ConnectionInitialisationTimeout.

func (*SubscriptionClient) IsInternalConnectionError added in v0.14.0

func (sc *SubscriptionClient) IsInternalConnectionError(err error) bool

IsInternalConnectionError checks if the input error is an internal error status.

func (*SubscriptionClient) IsInvalidMessageError added in v0.14.0

func (sc *SubscriptionClient) IsInvalidMessageError(err error) bool

IsInvalidMessageError checks if the input error is an invalid message status.

func (*SubscriptionClient) IsStatusSubscriberAlreadyExists added in v0.14.0

func (sc *SubscriptionClient) IsStatusSubscriberAlreadyExists(err error) bool

IsStatusSubscriberAlreadyExists checks if the input error has a SubscriberAlreadyExists status.

func (*SubscriptionClient) IsTooManyInitialisationRequests added in v0.14.0

func (sc *SubscriptionClient) IsTooManyInitialisationRequests(err error) bool

IsTooManyInitialisationRequests checks if the input error has a TooManyInitialisationRequests status.

func (*SubscriptionClient) IsUnauthorized added in v0.14.0

func (sc *SubscriptionClient) IsUnauthorized(err error) bool

IsUnauthorized checks if the input error is unauthorized.

func (*SubscriptionClient) IsWebsocketConnectionIdleTimeout added in v0.14.0

func (sc *SubscriptionClient) IsWebsocketConnectionIdleTimeout(err error) bool

IsWebsocketConnectionIdleTimeout checks if the input error is ErrWebsocketConnectionIdleTimeout.

func (*SubscriptionClient) NamedSubscribe deprecated

func (sc *SubscriptionClient) NamedSubscribe(
	name string,
	v interface{},
	variables map[string]interface{},
	handler func(message []byte, err error) error,
	options ...Option,
) (string, error)

Deprecated: this is the shortcut of Subscribe method, with NewOperationName option.

func (*SubscriptionClient) OnConnected

func (sc *SubscriptionClient) OnConnected(fn func()) *SubscriptionClient

OnConnected event is triggered when the websocket connected to GraphQL server successfully.

func (*SubscriptionClient) OnConnectionAlive added in v0.9.2

func (sc *SubscriptionClient) OnConnectionAlive(fn func()) *SubscriptionClient

OnConnectionAlive event is triggered when the websocket receive a connection alive message (differs per protocol).

func (*SubscriptionClient) OnDisconnected

func (sc *SubscriptionClient) OnDisconnected(fn func()) *SubscriptionClient

OnDisconnected event is triggered when the websocket client was disconnected.

func (*SubscriptionClient) OnError

func (sc *SubscriptionClient) OnError(
	onError func(sc *SubscriptionClient, err error) error,
) *SubscriptionClient

If returns error, the websocket connection will be terminated.

func (*SubscriptionClient) OnSubscriptionComplete added in v0.9.2

func (sc *SubscriptionClient) OnSubscriptionComplete(
	fn func(sub Subscription),
) *SubscriptionClient

OnSubscriptionComplete event is triggered when the subscription receives a terminated message from the server.

func (*SubscriptionClient) Run

func (sc *SubscriptionClient) Run() error

Run start the WebSocket client and subscriptions. If the client is running, recalling this function will return errors. If this function is run with goroutine, it can be stopped after closed.

func (*SubscriptionClient) RunWithContext added in v0.14.0

func (sc *SubscriptionClient) RunWithContext(ctx context.Context) error

RunWithContext start the WebSocket client and subscriptions. If the client is running, recalling this function will return errors. If this function is run with goroutine, it can be stopped after closed.

func (*SubscriptionClient) Subscribe

func (sc *SubscriptionClient) Subscribe(
	v interface{},
	variables map[string]interface{},
	handler func(message []byte, err error) error,
	options ...Option,
) (string, error)

The function returns subscription ID and error. You can use subscription ID to unsubscribe the subscription.

func (*SubscriptionClient) SubscribeRaw deprecated added in v0.3.0

func (sc *SubscriptionClient) SubscribeRaw(
	query string,
	variables map[string]interface{},
	handler func(message []byte, err error) error,
) (string, error)

Deprecated: use Exec instead.

func (*SubscriptionClient) Unsubscribe

func (sc *SubscriptionClient) Unsubscribe(id string) error

The input parameter is subscription ID that is returned from Subscribe function.

func (*SubscriptionClient) WithConnectionInitialisationTimeout added in v0.14.0

func (sc *SubscriptionClient) WithConnectionInitialisationTimeout(
	timeout time.Duration,
) *SubscriptionClient

WithConnectionInitialisationTimeout updates timeout for the connection initialisation.

func (*SubscriptionClient) WithConnectionParams

func (sc *SubscriptionClient) WithConnectionParams(
	params map[string]interface{},
) *SubscriptionClient

It's usually used for authentication handshake.

func (*SubscriptionClient) WithConnectionParamsFn added in v0.9.0

func (sc *SubscriptionClient) WithConnectionParamsFn(
	fn func() map[string]interface{},
) *SubscriptionClient

It's suitable for short-lived access tokens that need to be refreshed frequently.

func (*SubscriptionClient) WithCustomProtocol added in v0.14.0

func (sc *SubscriptionClient) WithCustomProtocol(
	protocol SubscriptionProtocol,
) *SubscriptionClient

WithCustomProtocol changes the subscription protocol that implements the SubscriptionProtocol interface.

func (*SubscriptionClient) WithExitWhenNoSubscription added in v0.9.2

func (sc *SubscriptionClient) WithExitWhenNoSubscription(value bool) *SubscriptionClient

WithExitWhenNoSubscription the client should exit when all subscriptions were closed.

func (*SubscriptionClient) WithKeepAlive added in v0.10.1

func (sc *SubscriptionClient) WithKeepAlive(interval time.Duration) *SubscriptionClient

WithKeepAlive programs the websocket to ping on the specified interval. Deprecated: rename to WithWebSocketKeepAlive to avoid confusing with the keep-alive specification of the subscription protocol.

func (*SubscriptionClient) WithLog

func (sc *SubscriptionClient) WithLog(logger func(args ...interface{})) *SubscriptionClient

WithLog sets logging function to print out received messages. By default, nothing is printed.

func (*SubscriptionClient) WithProtocol added in v0.9.0

By default the subscription client uses the subscriptions-transport-ws protocol.

func (*SubscriptionClient) WithReadLimit

func (sc *SubscriptionClient) WithReadLimit(limit int64) *SubscriptionClient

WithReadLimit set max size of response message.

func (*SubscriptionClient) WithReadTimeout added in v0.14.0

func (sc *SubscriptionClient) WithReadTimeout(timeout time.Duration) *SubscriptionClient

WithReadTimeout updates read timeout of websocket client.

func (*SubscriptionClient) WithRetryDelay added in v0.10.1

func (sc *SubscriptionClient) WithRetryDelay(delay time.Duration) *SubscriptionClient

WithRetryDelay set the delay time before retrying the connection.

func (*SubscriptionClient) WithRetryStatusCodes added in v0.9.2

func (sc *SubscriptionClient) WithRetryStatusCodes(codes ...string) *SubscriptionClient

the input parameter can be number string or range, e.g 4000-5000.

func (*SubscriptionClient) WithRetryTimeout

func (sc *SubscriptionClient) WithRetryTimeout(timeout time.Duration) *SubscriptionClient

The zero value means unlimited timeout.

func (*SubscriptionClient) WithSyncMode added in v0.10.1

func (sc *SubscriptionClient) WithSyncMode(value bool) *SubscriptionClient

WithSyncMode subscription messages are executed in sequence (without goroutine).

func (*SubscriptionClient) WithTimeout

func (sc *SubscriptionClient) WithTimeout(timeout time.Duration) *SubscriptionClient

WithTimeout updates read and write timeout of websocket client.

func (*SubscriptionClient) WithWebSocket

WithWebSocket replaces customized websocket client constructor In default, subscription client uses https://github.com/coder/websocket

func (*SubscriptionClient) WithWebSocketKeepAlive added in v0.14.0

func (sc *SubscriptionClient) WithWebSocketKeepAlive(interval time.Duration) *SubscriptionClient

WithWebSocketKeepAlive programs the websocket to ping on the specified interval.

func (*SubscriptionClient) WithWebSocketOptions added in v0.6.4

func (sc *SubscriptionClient) WithWebSocketOptions(options WebsocketOptions) *SubscriptionClient

WithWebSocketOptions provides options to the websocket client.

func (*SubscriptionClient) WithWebsocketConnectionIdleTimeout added in v0.14.0

func (sc *SubscriptionClient) WithWebsocketConnectionIdleTimeout(
	timeout time.Duration,
) *SubscriptionClient

WithWebsocketConnectionIdleTimeout updates for the websocket connection idle timeout.

func (*SubscriptionClient) WithWriteTimeout added in v0.14.0

func (sc *SubscriptionClient) WithWriteTimeout(timeout time.Duration) *SubscriptionClient

WithWriteTimeout updates write timeout of websocket client.

func (*SubscriptionClient) WithoutLogTypes

func (sc *SubscriptionClient) WithoutLogTypes(types ...OperationMessageType) *SubscriptionClient

WithoutLogTypes these operation types won't be printed.

type SubscriptionContext added in v0.9.0

type SubscriptionContext struct {
	context.Context
	// contains filtered or unexported fields
}

SubscriptionContext represents a shared context for protocol implementations with the websocket connection inside.

func (*SubscriptionContext) Cancel added in v0.9.1

func (sc *SubscriptionContext) Cancel()

SetCancel set the cancel function of the inner context.

func (*SubscriptionContext) Close added in v0.9.0

func (sc *SubscriptionContext) Close() error

Close closes the context and the inner websocket connection if exists.

func (*SubscriptionContext) GetAcknowledge added in v0.9.0

func (sc *SubscriptionContext) GetAcknowledge() bool

GetAcknowledge get the acknowledge status.

func (*SubscriptionContext) GetSubscription added in v0.9.0

func (sc *SubscriptionContext) GetSubscription(id string) *Subscription

GetSubscription get the subscription state by id.

func (*SubscriptionContext) GetSubscriptions added in v0.9.0

func (sc *SubscriptionContext) GetSubscriptions() map[string]Subscription

GetSubscription get all available subscriptions in the context.

func (*SubscriptionContext) GetSubscriptionsLength added in v0.9.1

func (sc *SubscriptionContext) GetSubscriptionsLength(status []SubscriptionStatus) int

GetSubscriptionsLength returns the length of subscriptions by status.

func (*SubscriptionContext) GetWebsocketConn added in v0.9.0

func (sc *SubscriptionContext) GetWebsocketConn() WebsocketConn

GetWebsocketConn get the current websocket connection.

func (*SubscriptionContext) IsClosed added in v0.14.0

func (sc *SubscriptionContext) IsClosed() bool

IsClosed get the closed status.

func (*SubscriptionContext) Log added in v0.9.0

func (sc *SubscriptionContext) Log(
	message interface{},
	metadata map[string]any,
	opType OperationMessageType,
)

Log prints condition logging with message type filters.

func (*SubscriptionContext) OnConnected added in v0.9.0

func (sc *SubscriptionContext) OnConnected()

OnConnected executes the OnConnected callback if exists.

func (*SubscriptionContext) OnConnectionAlive added in v0.9.2

func (sc *SubscriptionContext) OnConnectionAlive()

OnConnectionAlive executes the OnConnectionAlive callback if exists.

func (*SubscriptionContext) OnDisconnected added in v0.9.2

func (sc *SubscriptionContext) OnDisconnected()

OnDisconnected executes the OnDisconnected callback if exists.

func (*SubscriptionContext) OnSubscriptionComplete added in v0.9.2

func (sc *SubscriptionContext) OnSubscriptionComplete(subscription Subscription)

OnSubscriptionComplete executes the OnSubscriptionComplete callback if exists.

func (*SubscriptionContext) Send added in v0.9.0

func (sc *SubscriptionContext) Send(message interface{}, opType OperationMessageType) error

Send emits a message to the graphql server.

func (*SubscriptionContext) SetAcknowledge added in v0.9.0

func (sc *SubscriptionContext) SetAcknowledge(value bool)

SetAcknowledge set the acknowledge status.

func (*SubscriptionContext) SetClosed added in v0.14.0

func (sc *SubscriptionContext) SetClosed(value bool)

SetAcknowledge set the acknowledge status.

func (*SubscriptionContext) SetSubscription added in v0.9.0

func (sc *SubscriptionContext) SetSubscription(key string, sub *Subscription)

if subscription is nil, removes the subscription from the map.

func (*SubscriptionContext) SetWebsocketConn added in v0.9.0

func (sc *SubscriptionContext) SetWebsocketConn(conn WebsocketConn)

SetWebsocketConn set the current websocket connection.

type SubscriptionProtocol added in v0.9.0

type SubscriptionProtocol interface {
	// GetSubprotocols returns subprotocol names of the subscription transport
	// The graphql server depends on the Sec-WebSocket-Protocol header to return the correct message specification
	GetSubprotocols() []string
	// ConnectionInit sends a initial request to establish a connection within the existing socket
	ConnectionInit(ctx *SubscriptionContext, connectionParams map[string]interface{}) error
	// Subscribe requests an graphql operation specified in the payload message
	Subscribe(ctx *SubscriptionContext, sub Subscription) error
	// Unsubscribe sends a request to stop listening and complete the subscription
	Unsubscribe(ctx *SubscriptionContext, sub Subscription) error
	// OnMessage listens ongoing messages from server
	OnMessage(ctx *SubscriptionContext, subscription Subscription, message OperationMessage) error
	// Close terminates all subscriptions of the current websocket
	Close(ctx *SubscriptionContext) error
}

SubscriptionProtocol abstracts the life-cycle of subscription protocol implementation for a specific transport protocol.

type SubscriptionProtocolType added in v0.9.0

type SubscriptionProtocolType string

SubscriptionProtocolType represents the protocol specification enum of the subscription.

type SubscriptionStatus added in v0.9.2

type SubscriptionStatus int32

internal subscription status.

type WebSocketStats added in v0.14.2

type WebSocketStats struct {
	TotalActiveConnections int
	TotalClosedConnections int
	ActiveConnectionIDs    []uuid.UUID
}

WebSocketStats hold statistic data of WebSocket connections for subscription.

func GetWebSocketStats added in v0.14.2

func GetWebSocketStats() WebSocketStats

GetWebSocketStats gets the websocket stats.

type WebsocketConn

type WebsocketConn interface {
	ReadJSON(v interface{}) error
	WriteJSON(v interface{}) error
	Ping() error
	Close() error
	// SetReadLimit sets the maximum size in bytes for a message read from the peer. If a
	// message exceeds the limit, the connection sends a close message to the peer
	// and returns ErrReadLimit to the application.
	SetReadLimit(limit int64)
	// GetCloseStatus tries to get WebSocket close status from error
	// return -1 if the error is unknown
	// https://www.iana.org/assignments/websocket/websocket.xhtml
	GetCloseStatus(err error) int32
}

WebsocketHandler abstracts WebSocket connection functions ReadJSON and WriteJSON data of a frame from the WebSocket connection. Close the WebSocket connection.

type WebsocketHandler added in v0.3.0

type WebsocketHandler struct {
	*websocket.Conn
	// contains filtered or unexported fields
}

default websocket handler implementation using https://github.com/coder/websocket

func (*WebsocketHandler) Close added in v0.3.0

func (wh *WebsocketHandler) Close() error

Close implements the function to close the websocket connection.

func (*WebsocketHandler) GetCloseStatus added in v0.9.2

func (wh *WebsocketHandler) GetCloseStatus(err error) int32

GetCloseStatus tries to get WebSocket close status from error https://www.iana.org/assignments/websocket/websocket.xhtml

func (WebsocketHandler) GetID added in v0.14.2

func (wh WebsocketHandler) GetID() uuid.UUID

GetID gets the identity of the Websocket connection.

func (*WebsocketHandler) Ping added in v0.10.1

func (wh *WebsocketHandler) Ping() error

Ping sends a ping to the peer and waits for a pong.

func (*WebsocketHandler) ReadJSON added in v0.3.0

func (wh *WebsocketHandler) ReadJSON(v interface{}) error

ReadJSON implements the function to decode the json message from the server.

func (*WebsocketHandler) WriteJSON added in v0.3.0

func (wh *WebsocketHandler) WriteJSON(v interface{}) error

WriteJSON implements the function to encode and send message in json format to the server.

type WebsocketOptions added in v0.6.4

type WebsocketOptions struct {
	// HTTPClient is used for the connection.
	// Its Transport must return writable bodies for WebSocket handshakes.
	// http.Transport does beginning with Go 1.12.
	HTTPClient *http.Client

	// HTTPHeader specifies the HTTP headers included in the handshake request.
	HTTPHeader http.Header

	// Host optionally overrides the Host HTTP header to send. If empty, the value
	// of URL.Host will be used.
	Host string

	// CompressionMode controls the compression mode.
	// Defaults to CompressionDisabled.
	//
	// See docs on CompressionMode for details.
	CompressionMode websocket.CompressionMode

	// CompressionThreshold controls the minimum size of a message before compression is applied.
	//
	// Defaults to 512 bytes for CompressionNoContextTakeover and 128 bytes
	// for CompressionContextTakeover.
	CompressionThreshold int

	// ReadTimeout controls the read timeout of the websocket connection.
	ReadTimeout time.Duration

	// WriteTimeout controls the read timeout of the websocket connection.
	WriteTimeout time.Duration

	// Subprotocols hold subprotocol names of the subscription transport
	// The graphql server depends on the Sec-WebSocket-Protocol header to return the correct message specification
	Subprotocols []string
}

WebsocketOptions allows implementation agnostic configuration of the websocket client.

Directories

Path Synopsis
example
graphql-ws-bc/client
subscription is a test program currently being used for developing graphql package.
subscription is a test program currently being used for developing graphql package.
graphqldev Module
subscription Module
Package ident provides functions for parsing and converting identifier names between various naming convention.
Package ident provides functions for parsing and converting identifier names between various naming convention.
pkg
jsonutil
Package jsonutil provides a function for decoding JSON into a GraphQL query data structure.
Package jsonutil provides a function for decoding JSON into a GraphQL query data structure.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL