Releases: hasura/go-graphql-client
v0.7.2
This version focuses on subscription fixes and improvements (#39)
- Fix the deadlock issue that the subscription client can't receive error events from the channel.
- Close the connection when there is no running subscription
- add a particular error that stops the subscription inside the callback
subscriptionId, err := client.Subscribe(&query, nil, func(dataValue *json.RawMessage, errValue error) error {
// ...
// return this error to stop the subscription in the callback
return ErrSubscriptionStopped
})
v0.7.1
v0.7.0
Changelog
- Introduce
Exec
method to execute pre-built query string (#32) @dbarrosop - Allow specifying GraphQL type name by implementing
GetGraphQLType
method (#33) @nizar-m
Execute
The Exec
function allows you to executing 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)
}
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!)
v0.6.5
v0.6.4
Changelog
- support custom HTTP client for the subscription client (#29) @sermojohn
- add debug mode, expose GraphQL builder functions and UnmarshalGraphQL for unit tests (#30) @hgiasac
Custom HTTP Client for the subscription 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,
}
})
Debugging and Unit test
Enable debug mode with the WithDebug
function. If the request is failed, 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
}
]
}
]
}
Because the GraphQL query string is generated in runtime using reflection, it isn't really safe. To assure the GraphQL query is expected, it's necessary to write some unit test for query construction.
// ConstructQuery build GraphQL query string from struct and variables
func ConstructQuery(v interface{}, variables map[string]interface{}, options ...Option) (string, error)
// ConstructQuery 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, 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
v0.6.3
v0.6.2
v0.6.1
v0.6.0
Highlights
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 }
Add mechanism to modify outgoing HTTP request
This feature allows reusing the same client amongst slightly different "configurations". We are able to use one single HTTP client in multitenant applications with different authentication headers.
func setAuthHeader(secret string) func(req *http.Request) {
return func(req *http.Request) {
req.Header.Add("x-hasura-admin-secret", secret)
}
}
client := graphql.NewClient("http://localhost:8080/v1/graphql", nil)
// return new client wrapper with different authentication header
client2 := client.WithRequestModifier(setAuthHeader("hasura"))
Changelog
- feat: add mechanism to modify outgoing http request (#23) @dbarrosop
- feat: allow tagging of the field as having a scalar GraphQL type (#24) @nizar-m