Create Go SDKs from OpenAPI / Swagger
SDK Overview
Speakeasy's Go SDK is designed to build idiomatic Golang modules, using language standard features. We aim at being backwards compatible as well, currently requiring only Go 1.14. Being Go, the code continues to work with all newer compilers as well.
The SDK is strongly typed, makes minimal use of third party modules, and is straight-forward to to debug. It should feel familiar to Golang developers when they use SDKs we generate. We make opinionated choices in some places, but we do so in a thoughtful and deliberate way.
For example, many Golang developers would rather rely upon zero values rather than pointers for unset optional values. However, so many REST APIs have nuanced distinctions between zero values and null values that this is not generally practical for interoperability. So we accept the compromise of using pointers, but also provide nil
-safe getters to help offset the increased risk of panic from mishandled pointers.
The core features of the Go SDK include:
- Using struct field tags and reflection-based marshaling and unmarshalling of data
- Pointers are used for optional fields
- Automatic getters that provide
nil
-safety and hooks for building interfaces - Context-aware method calls for programmatic timeouts and cancellation
- A
utils
package that segments off common operations, making generated code easier to follow and understand - Use of variadic options functions are provided to ease construction whether you have many options or none
- Authentication support for OAuth flows as well as support for standard security mechanisms (HTTP Basic, application tokens, etc.)
- Optional pagination support for supported APIs
- Optional support for retries in every operation
- Complex number types
"github.com/ericlager/decimal".Big
"math/big".Int
- Date and date/time types using RFC3339 date formats
- Custom type enums using strings and ints
- Union types and combined types
The SDK includes minimal dependencies, with the only third party dependencies being:
github.com/ericlagergren/decimal
- provides big decimal support featuresgithub.com/cenkalti/backoff/v4
- implements automatic retry supportgithub.com/spyzhov/ajson
- is used to help implement pagination
Go Package Structure
HTTP Client
The Go SDK makes API calls that wrap an internal HTTP client. The requirements for the HTTP client are very simple. It must match this interface:
type HTTPClient interface { Do(req *http.Request) (*http.Response, error)}
The built-in net/http
client satisfies this interface and a default client based on the built-in is provided by default. To replace this default with a client of your own, you can implement this interface yourself or provide your own client configured as desired. Here's a simple example, which adds a client with a 30 second timeout.
import ( "net/http" "time" "github.com/myorg/your-go-sdk")var ( httpClient = &http.Client{Timeout: 30 * time.Second} sdkClient = sdk.New(sdk.WithClient(httpClient)))
This can be a convenient way to configure timeouts, cookies, proxies, custom headers, and other low-level configuration.
Go Client Data Types & Enums
The Speakeasy Go SDK has a strong preference for familiar built-in types. The Go language has a rich built-in type system, so we are able to rely almost completely on it. Here is a list of types we use:
string
time.Time
int
int64
big.Int
float32
float64
bool
- etc.
We do provide a few custom types in the types
package, which are used to aid with marshaling and unmarshalling data exchanged with the server-side API. For example, types.Date
is a thin wrapper around time.Time
, which is able to decode and encode dates in "2006-01-02"
format.
We also have made use of the decimal.Big
class provided by github.com/ericlagergren/decimal
. We feel this is a better alternative to big.Float
as it provides high precision floating point math that lacks the rounding errors that can sometimes occur with big.Float
.
Enumeration types are built by employing the typical Golang practice. Speakeasy defines a type alias to string
or int
or int64
, as appropriate. Then constants of this type are defined for the predefined values.
Go SDK Generated Classes
The Go SDK generates a struct
for each request and response object as well as for each component object. All fields in the struct
objects are public. Optional fields will be given pointer types and may be set to nil
. A getter method is also defined for each public field. The Get
prefix distinguishes the getters from the public field names which remain directly accessible. The getters work correctly even when called on a nil
value, in which case they return the zero value of the field.
For example, the following code shows a nested component object where the inner object is optional. The following code is safe from nil
pointer related panics.
var outer *shared.Outervar safe string = outer.GetInner().GetName()if safe == "" { fmt.Println("Don't Panic")}// output: Don't Panic
The getters also provide useful hooks for defining interfaces.
Parameters
As described above, the Speakeasy SDK will generate a class with public fields for each request and response object. Each field will be tagged to control marshaling and unmarshalling into other data formats while interacting with the underlying API. However, if the maxMethodParams
value is set in gen.yaml
, we will remove up to that number of parameters from the generated struct. They will be placed as positional parameters in the operation method after the context object and before the request object.
// maxMethodParams: 1res, err := sdk.GetDrink(ctx, "Sangria")if err != nil { return err}// work with res...
Compare this with the example in the next section where maxMethodParams
is 0
.
Errors
Following Golang best practices, all operation methods in the Speakeasy SDK will return a response object and an error. Callers should always check for the presence of the error. The object used for errors is configurable per request. Any error response may return a custom error object. A generic error will be provided when any sort of communication failure is detected during an operation.
Here's an example of custom error handling in a theoretical SDK:
longTea := operations.GetDrinkRequest{Name: "Long Island Iced Tea"}res, err := sdk.GetDrink(ctx, &longTea)var apiErr sdkerrors.APIErrorif errors.As(err, &apiErr) { return fmt.Errorf("failed to get drink (%d): %s", apiErr.GetCode(), apiErr.GetMessage())} else if err != nil { return fmt.Errorf("unknown error getting drink: %w", err)}// work with res...