Documentation
¶
Overview ¶
Package octoql provides reusable runtime APIs for generated GraphQL clients.
Index ¶
Examples ¶
Constants ¶
const DefaultResponseSizeLimit int64 = 10 * 1024 * 1024
DefaultResponseSizeLimit is the maximum GraphQL HTTP response body size a Client accepts for decoding unless configured otherwise. It is 10 MiB.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client executes GraphQL operations against an HTTP endpoint.
Configure bearer authentication with Client.SetBearerToken. Other request behavior, including other authentication schemes, belongs on the supplied http.Client or its http.RoundTripper. A Client may be used concurrently after construction.
func NewClient ¶
NewClient returns a client for endpoint. A nil httpClient uses http.DefaultClient.
Example ¶
package main
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"github.com/willabides/octoql"
)
type exampleViewerResponse struct {
Viewer struct {
Login string `json:"login"`
} `json:"viewer"`
}
type exampleViewerPartialDataError struct {
data *exampleViewerResponse
err error
}
func (e *exampleViewerPartialDataError) Error() string { return e.err.Error() }
func (e *exampleViewerPartialDataError) Unwrap() error { return e.err }
func (e *exampleViewerPartialDataError) PartialData() *exampleViewerResponse {
return e.data
}
func main() {
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
writer.Header().Set("Content-Type", "application/json")
_, err := io.WriteString(writer, `{"data":{"viewer":{"login":"octocat"}}}`)
if err != nil {
panic(err)
}
}))
defer server.Close()
client := octoql.NewClient(server.URL, server.Client())
response, err := getViewer(context.Background(), client)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(response.Viewer.Login)
}
func getViewer(
ctx context.Context,
client *octoql.Client,
) (*exampleViewerResponse, error) {
return executeExample[exampleViewerResponse](
ctx,
client,
"Viewer",
"query Viewer { viewer { login } }",
func(data *exampleViewerResponse, err error) error {
return &exampleViewerPartialDataError{data: data, err: err}
},
)
}
func executeExample[T any](
ctx context.Context,
client *octoql.Client,
operationName string,
query string,
newPartialDataError func(*T, error) error,
) (*T, error) {
response := new(T)
hasData, err := client.Execute(ctx, octoql.Payload{
OperationName: operationName,
Query: query,
}, response)
if !hasData {
return nil, err
}
if err != nil {
return nil, newPartialDataError(response, err)
}
return response, nil
}
Output: octocat
func (*Client) Execute ¶
Execute runs operation and decodes its response data into response.
Execute is a generated-code contract. Response must be a non-nil pointer. The returned boolean reports whether the GraphQL data field decoded successfully and response is usable, including when GraphQL errors are also returned. Every failure after the server returns an HTTP response includes ResponseError. GraphQL errors and rate limits remain discoverable in that error chain as Errors and RateLimitError.
func (*Client) RateLimit ¶
RateLimit returns the latest valid primary rate-limit snapshot observed by client. The boolean is false until a response includes a valid X-RateLimit-Remaining header.
The snapshot is advisory: other clients and processes can consume the same GitHub rate-limit budget after it is observed.
Example ¶
package main
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"github.com/willabides/octoql"
)
type exampleViewerResponse struct {
Viewer struct {
Login string `json:"login"`
} `json:"viewer"`
}
type exampleViewerPartialDataError struct {
data *exampleViewerResponse
err error
}
func (e *exampleViewerPartialDataError) Error() string { return e.err.Error() }
func (e *exampleViewerPartialDataError) Unwrap() error { return e.err }
func (e *exampleViewerPartialDataError) PartialData() *exampleViewerResponse {
return e.data
}
func main() {
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
writer.Header().Set("X-RateLimit-Limit", "5000")
writer.Header().Set("X-RateLimit-Remaining", "4999")
writer.Header().Set("X-RateLimit-Used", "1")
_, err := io.WriteString(writer, `{"data":{}}`)
if err != nil {
panic(err)
}
}))
defer server.Close()
client := octoql.NewClient(server.URL, server.Client())
_, err := getViewer(context.Background(), client)
if err != nil {
fmt.Println(err)
return
}
rateLimit, known := client.RateLimit()
fmt.Println(known)
fmt.Println(rateLimit.Limit)
fmt.Println(rateLimit.Remaining)
}
func getViewer(
ctx context.Context,
client *octoql.Client,
) (*exampleViewerResponse, error) {
return executeExample[exampleViewerResponse](
ctx,
client,
"Viewer",
"query Viewer { viewer { login } }",
func(data *exampleViewerResponse, err error) error {
return &exampleViewerPartialDataError{data: data, err: err}
},
)
}
func executeExample[T any](
ctx context.Context,
client *octoql.Client,
operationName string,
query string,
newPartialDataError func(*T, error) error,
) (*T, error) {
response := new(T)
hasData, err := client.Execute(ctx, octoql.Payload{
OperationName: operationName,
Query: query,
}, response)
if !hasData {
return nil, err
}
if err != nil {
return nil, newPartialDataError(response, err)
}
return response, nil
}
Output: true 5000 4999
func (*Client) ResponseSizeLimit ¶
ResponseSizeLimit returns the maximum HTTP response body size Client accepts for decoding. A newly constructed Client uses DefaultResponseSizeLimit.
func (*Client) SetBearerToken ¶
SetBearerToken configures the OAuth 2.0 bearer token sent with each request. token must use the RFC 6750 b64token syntax. It may be called concurrently with Client.Execute to rotate credentials.
func (*Client) SetResponseSizeLimit ¶
SetResponseSizeLimit configures the maximum HTTP response body size Client accepts for decoding. limit must be greater than zero. It may be called concurrently with Client.Execute.
type Error ¶
type Error struct {
Type ErrorType `json:"type,omitempty"`
Message string `json:"message"`
Path Path `json:"path,omitempty"`
Locations []Location `json:"locations,omitempty"`
Extensions map[string]any `json:"extensions,omitempty"`
}
Error describes an error returned in a GraphQL response.
type ErrorType ¶
type ErrorType string
ErrorType identifies a GitHub GraphQL error category. It is an open string type so values introduced by GitHub remain available to callers.
type Errors ¶
type Errors []*Error
Errors is the list of errors returned in a GraphQL response.
Example (PartialData) ¶
package main
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"github.com/willabides/octoql"
)
type exampleRepositoryResponse struct {
Repository struct {
Name string `json:"name"`
} `json:"repository"`
}
type exampleRepositoryPartialDataError struct {
data *exampleRepositoryResponse
err error
}
func (e *exampleRepositoryPartialDataError) Error() string { return e.err.Error() }
func (e *exampleRepositoryPartialDataError) Unwrap() error { return e.err }
func (e *exampleRepositoryPartialDataError) PartialData() *exampleRepositoryResponse {
return e.data
}
func main() {
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
writer.Header().Set("Content-Type", "application/json")
_, err := io.WriteString(writer, `{
"data":{"repository":{"name":"octoql"}},
"errors":[{"type":"FORBIDDEN","message":"owner unavailable","path":["repository","owner"]}]
}`)
if err != nil {
panic(err)
}
}))
defer server.Close()
client := octoql.NewClient(server.URL, server.Client())
response, err := getRepository(context.Background(), client)
var graphqlErrors octoql.Errors
if errors.As(err, &graphqlErrors) {
fmt.Println(response == nil)
partialErr, ok := errors.AsType[*exampleRepositoryPartialDataError](err)
if ok {
fmt.Println(partialErr.PartialData().Repository.Name)
}
fmt.Println(graphqlErrors[0].Type)
}
}
func getRepository(
ctx context.Context,
client *octoql.Client,
) (*exampleRepositoryResponse, error) {
return executeExample[exampleRepositoryResponse](
ctx,
client,
"Repository",
"query Repository { repository { name owner { login } } }",
func(data *exampleRepositoryResponse, err error) error {
return &exampleRepositoryPartialDataError{data: data, err: err}
},
)
}
func executeExample[T any](
ctx context.Context,
client *octoql.Client,
operationName string,
query string,
newPartialDataError func(*T, error) error,
) (*T, error) {
response := new(T)
hasData, err := client.Execute(ctx, octoql.Payload{
OperationName: operationName,
Query: query,
}, response)
if !hasData {
return nil, err
}
if err != nil {
return nil, newPartialDataError(response, err)
}
return response, nil
}
Output: true octoql FORBIDDEN
type NoMarshalJSON ¶
type NoMarshalJSON struct{}
NoMarshalJSON is for generated code only.
Embedding NoMarshalJSON alongside a type with a MarshalJSON method prevents that sibling method from being promoted.
func (NoMarshalJSON) MarshalJSON ¶
func (NoMarshalJSON) MarshalJSON() ([]byte, error)
MarshalJSON should never be called. It exists only to prevent a sibling MarshalJSON method from being promoted.
type NoUnmarshalJSON ¶
type NoUnmarshalJSON struct{}
NoUnmarshalJSON is for generated code only.
Embedding NoUnmarshalJSON alongside a type with an UnmarshalJSON method prevents that sibling method from being promoted.
func (NoUnmarshalJSON) UnmarshalJSON ¶
func (NoUnmarshalJSON) UnmarshalJSON([]byte) error
UnmarshalJSON should never be called. It exists only to prevent a sibling UnmarshalJSON method from being promoted.
type Path ¶
type Path []any
Path is a GraphQL response path. Each segment is either a string field name or an integer list index.
func (Path) MarshalJSON ¶
MarshalJSON encodes string and integer path segments in GraphQL wire format.
func (*Path) UnmarshalJSON ¶
UnmarshalJSON decodes GraphQL string and integer path segments.
type Payload ¶
type Payload struct {
Query string `json:"query"`
OperationName string `json:"operationName"`
Variables any `json:"variables,omitempty"`
}
Payload is the GraphQL request body used by generated clients.
type RateLimit ¶
type RateLimit struct {
Limit int
Remaining int
Used int
Reset time.Time
Resource string
RetryAfter time.Duration
RetryAt time.Time
}
RateLimit describes the rate-limit headers returned by GitHub.
Missing or malformed response headers leave their corresponding fields at their zero values.
type RateLimitError ¶
type RateLimitError struct {
Kind RateLimitKind
RateLimit RateLimit
Err error
}
RateLimitError describes a response rejected because of a GitHub rate limit.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"github.com/willabides/octoql"
)
type exampleViewerResponse struct {
Viewer struct {
Login string `json:"login"`
} `json:"viewer"`
}
type exampleViewerPartialDataError struct {
data *exampleViewerResponse
err error
}
func (e *exampleViewerPartialDataError) Error() string { return e.err.Error() }
func (e *exampleViewerPartialDataError) Unwrap() error { return e.err }
func (e *exampleViewerPartialDataError) PartialData() *exampleViewerResponse {
return e.data
}
func main() {
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
writer.Header().Set("Retry-After", "30")
_, err := io.WriteString(writer, `{"errors":[{"message":"slow down"}]}`)
if err != nil {
panic(err)
}
}))
defer server.Close()
client := octoql.NewClient(server.URL, server.Client())
_, err := getViewer(context.Background(), client)
rateLimitError, ok := errors.AsType[*octoql.RateLimitError](err)
fmt.Println(ok)
fmt.Println(rateLimitError.Kind)
}
func getViewer(
ctx context.Context,
client *octoql.Client,
) (*exampleViewerResponse, error) {
return executeExample[exampleViewerResponse](
ctx,
client,
"Viewer",
"query Viewer { viewer { login } }",
func(data *exampleViewerResponse, err error) error {
return &exampleViewerPartialDataError{data: data, err: err}
},
)
}
func executeExample[T any](
ctx context.Context,
client *octoql.Client,
operationName string,
query string,
newPartialDataError func(*T, error) error,
) (*T, error) {
response := new(T)
hasData, err := client.Execute(ctx, octoql.Payload{
OperationName: operationName,
Query: query,
}, response)
if !hasData {
return nil, err
}
if err != nil {
return nil, newPartialDataError(response, err)
}
return response, nil
}
Output: true secondary
func (*RateLimitError) Error ¶
func (e *RateLimitError) Error() string
Error returns a summary of the rate-limit failure.
func (*RateLimitError) Unwrap ¶
func (e *RateLimitError) Unwrap() error
Unwrap exposes the response failure and its GraphQL or processing causes.
type RateLimitKind ¶
type RateLimitKind string
RateLimitKind identifies the GitHub rate limit that rejected a request.
const ( // RateLimitPrimary identifies exhaustion of GitHub's primary rate limit. RateLimitPrimary RateLimitKind = "primary" // RateLimitSecondary identifies GitHub's secondary rate limit. RateLimitSecondary RateLimitKind = "secondary" )
type ResponseError ¶
type ResponseError struct {
// StatusCode is the HTTP response status.
StatusCode int
// RequestID is GitHub's X-GitHub-Request-ID value, when present.
RequestID string
// RawBody contains at most the first 64 KiB of a non-successful, over-limit,
// or undecodable response. It is omitted for ordinary GraphQL errors.
RawBody []byte
// RawBodyTruncated reports whether RawBody omits trailing response bytes.
RawBodyTruncated bool
// contains filtered or unexported fields
}
ResponseError describes a failed GraphQL HTTP response.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"github.com/willabides/octoql"
)
type exampleViewerResponse struct {
Viewer struct {
Login string `json:"login"`
} `json:"viewer"`
}
type exampleViewerPartialDataError struct {
data *exampleViewerResponse
err error
}
func (e *exampleViewerPartialDataError) Error() string { return e.err.Error() }
func (e *exampleViewerPartialDataError) Unwrap() error { return e.err }
func (e *exampleViewerPartialDataError) PartialData() *exampleViewerResponse {
return e.data
}
func main() {
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
writer.Header().Set("X-GitHub-Request-ID", "request-example")
writer.WriteHeader(http.StatusForbidden)
_, err := io.WriteString(
writer,
`{"errors":[{"type":"FORBIDDEN","message":"request rejected"}]}`,
)
if err != nil {
panic(err)
}
}))
defer server.Close()
client := octoql.NewClient(server.URL, server.Client())
_, err := getViewer(context.Background(), client)
responseError, ok := errors.AsType[*octoql.ResponseError](err)
if !ok {
fmt.Println("response error not found")
return
}
graphqlErrors, ok := errors.AsType[octoql.Errors](err)
if !ok {
fmt.Println("graphql errors not found")
return
}
fmt.Println(responseError != nil)
fmt.Println(responseError.StatusCode)
fmt.Println(responseError.RequestID)
fmt.Println(graphqlErrors[0].Type)
}
func getViewer(
ctx context.Context,
client *octoql.Client,
) (*exampleViewerResponse, error) {
return executeExample[exampleViewerResponse](
ctx,
client,
"Viewer",
"query Viewer { viewer { login } }",
func(data *exampleViewerResponse, err error) error {
return &exampleViewerPartialDataError{data: data, err: err}
},
)
}
func executeExample[T any](
ctx context.Context,
client *octoql.Client,
operationName string,
query string,
newPartialDataError func(*T, error) error,
) (*T, error) {
response := new(T)
hasData, err := client.Execute(ctx, octoql.Payload{
OperationName: operationName,
Query: query,
}, response)
if !hasData {
return nil, err
}
if err != nil {
return nil, newPartialDataError(response, err)
}
return response, nil
}
Output: true 403 request-example FORBIDDEN
func (*ResponseError) Error ¶
func (e *ResponseError) Error() string
Error returns a stable summary of the failed response.
func (*ResponseError) Unwrap ¶
func (e *ResponseError) Unwrap() error
Unwrap exposes decoded GraphQL errors and response processing failures to errors.Is, errors.As, and errors.AsType.
type ResponseSizeLimitError ¶
type ResponseSizeLimitError struct {
// Limit is the configured maximum response size in bytes.
Limit int64
}
ResponseSizeLimitError reports that a GraphQL HTTP response exceeded Client's configured response-size limit.
func (*ResponseSizeLimitError) Error ¶
func (e *ResponseSizeLimitError) Error() string
Error reports that the response exceeded its configured limit.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
octoqlgen
command
octoqlgen generates type-safe Go GraphQL clients.
|
octoqlgen generates type-safe Go GraphQL clients. |
|
octoqlgen/internal/cli
Package cli defines the octoqlgen command-line interface.
|
Package cli defines the octoqlgen command-line interface. |
|
octoqlgen/internal/config
Package config loads octoqlgen configuration files.
|
Package config loads octoqlgen configuration files. |
|
octoqlgen/internal/schema
Package schema verifies and materializes pinned GraphQL schemas.
|
Package schema verifies and materializes pinned GraphQL schemas. |
|
internal
|
|
|
generatefeatures/clientgetter
Package clientgetter exercises generated client getters and custom contexts.
|
Package clientgetter exercises generated client getters and custom contexts. |
|
generatefeatures/githubdefaults
Package githubdefaults exercises generated helpers that use GitHub's default scalar bindings.
|
Package githubdefaults exercises generated helpers that use GitHub's default scalar bindings. |
|
generatefeatures/nocontext
Package nocontext exercises generated helpers configured without a context parameter.
|
Package nocontext exercises generated helpers configured without a context parameter. |
|
handlertest
Package handlertest exercises generated clients and typed test handlers.
|
Package handlertest exercises generated clients and typed test handlers. |
|
handlertest/localfixture
Package localfixture generates a client and handler-local types for parity tests.
|
Package localfixture generates a client and handler-local types for parity tests. |