All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
-
Feature: Extra properties decoded from response objects are retained and accessible via the
GetExtraProperties
method like so:user, err := client.Users.Get(...) if err != nil { return nil, err } for key, value := range user.GetExtraProperties() { fmt.Printf("Got extra property; key: %s, value: %v\n", key, value) }
- Internal: The generator now uses the latest FDR SDK.
- Fix: In-lined request body properties no longer include a non-empty
url
struct tag. This previously caused request body properties to be encoded in the URL alongside the rest of the query parameters.
-
Fix: The Go generator now escapes path parameters that would previously create invalid URLs (e.g. "\example").
-
Improvement: Refactor endpoint URL mapping with
core.EncodeURL
. All generated endpoints with path parameters now see use cases like the following:endpointURL := core.EncodeURL(baseURL+"/organizations/%v", orgID)
-
Feature: Add support for cursor and offset pagination.
For example, consider the following endpoint
/users
endpoint:types: User: properties: name: string ListUserResponse: properties: next: optional<string> data: list<User> service: auth: false base-path: /users endpoints: list: path: "" method: GET pagination: cursor: $request.starting_after next_cursor: $response.next results: $response.data request: name: ListUsersRequest query-parameters: starting_after: optional<string> response: ListUsersResponse
The generated
client.Users.List
method now returns a genericcore.Page[T]
that can be used to fetch the next page like so:page, err := client.Users.List( ctx, &acme.ListUsersRequest{ StartingAfter: acme.String("user_xyz"), }, ) if err != nil { return nil, err } for page != nil { for _, user := range page.Results { fmt.Printf("Got user: %v\n", user.Name) } page, err = page.GetNextPage() if errors.Is(err, core.ErrNoPages) { break } if err != nil { // Handle the error! } }
If you don't need to explicitly request every individual page, you can convert the
core.Page
into acore.PageIterator
and simply iterate over each element like so:page, err := client.Users.List( ctx, &acme.ListUsersRequest{ StartingAfter: acme.String("user_xyz"), }, ) if err != nil { return nil, err } iter := page.Iterator() for iter.Next() { user := iter.Current() fmt.Printf("Got user: %v\n", user.Name) } if err := iter.Err(); err != nil { // Handle the error! }
The iterator will automatically fetch the next page as needed and continue to iterate until all the pages are read.
-
Improvement: Enhance extra property serialization performance.
-
Improvement: Generate additional extra property tests into the SDK.
-
Fix: Resolve an non-deterministic key ordering issue for snippets of type
unknown
. -
Fix: Resolve an issue with discriminated union serialization. This only occurs when the union varaint requires its own custom JSON serialization strategy and the union variant contains the same properties as another object.
For example, consider the following union definition:
Circle: properties: created_at: datetime Square: properties: created_at: datetime Shape: union: circle: Circle square: Square
The generated
json.Marshaler
method now looks like the following, where the discriminant is added directly to the serialized JSON object:func (s Shape) MarshalJSON() ([]byte, error) { if s.Circle != nil { return core.MarshalJSONWithExtraProperty(s.Circle, "type", "circle") } if s.Square != nil { return core.MarshalJSONWithExtraProperty(s.Square, "type", "square") } return nil, fmt.Errorf("type %T does not define a non-empty union type", s) }
-
Fix: The
omitempty
struct tag is now only used for nil-able types. It was previously used for non-optional enums, which was never intended. For example, the followingRequestType
enum will no longer include anomitempty
tag:type Request struct { Type RequestType `json:"type" url:"type"` }
-
Fix: Update the query encoder to prevent unintentional errors whenever the
omitempty
is used for a non-optional field.
-
Feature: The Go generator now supports extra properties.
For example, consider the following type definition:
types: User: extra-properties: true properties: name: string
The generated
User
type will now have anExtraProperties
field like so:type User struct { Name string `json:"name" url:"name"` ExtraProperties map[string]interface{} `json:"-" url:"-"` }
If any extra properties are set, they will be sent alongside the rest of the defined properties, e.g. the
age
key in{"name": "alice", "age": 42}
.
-
Feature: The Go generator now supports environment variable scanning.
For example, consider the following
api.yml
definition:name: api auth: Bearer auth-schemes: Bearer: scheme: bearer token: name: apiKey env: ACME_API_KEY
The client reads this environment variable and sets the value in the
Authorization
header if theAPIKey
option is not explicitly specified like so:func NewClient(opts ...option.RequestOption) *Client { options := core.NewRequestOptions(opts...) if options.APIKey == "" { options.APIKey = os.Getenv("ACME_API_KEY") } return &Client{ baseURL: options.BaseURL, caller: core.NewCaller( &core.CallerParams{ Client: options.HTTPClient, MaxAttempts: options.MaxAttempts, }, ), // The header associated with the client will contain // the ACME_API_KEY value. // // It can still be overridden by endpoint-level request // options. header: options.ToHeader(), } }
-
Fix: Path parameters are now applied in the correct order. This is relevant for endpoints that specify more than one path parameter (e.g.
/organizations/{orgId}/users/{userId}
). Function signatures remain unchanged, such that they preserve the path parameter order specified by the API definition like so:func (c *Client) Get( ctx context.Context, userID string, orgID string, opts ...option.RequestOption, ) (*acme.User, error) { ... endpointURL := fmt.Sprintf(baseURL+"/"+"organizations/%v/users/%v", orgID, userID) ... }
- Fix: Custom authorization header schemes had their values overridden by request options, which required using the generated request option at every call-site.
- Fix: Go snippets correctly handle unknown examples.
response, err := client.CreateUser(
ctx,
&acme.CreateUserRequest{
Name: "alice",
Metadata: map[string]interface{}{
"address": "123 Market Street",
"age": 28,
},
},
)
- Feature: Add support for simpler unions, which is configurable with
union: v1
(if omitted, the defaultv0
version will be used). Withv0
, a separate constructor for each variant of the union was generated, but using these constructors is cumbersome for large production APIs due to the sheer length of the function name. This improves the experience by simply setting the discriminant at runtime, which prevents the need for constructors entirely.
- name: fernapi/fern-go-sdk
version: 0.18.0
config:
union: v1
// Before
union := acme.NewStatusFromCloudServerAlertStatus(
&acme.CloudServerAlertStatus{
Value: "WARNING",
},
)
// After
union := &acme.Status{
CloudServerAlertStatus: &acme.CloudServerAlertStatus{
Value: "WARNING",
},
)
- Feature: Add support for multiple files in upload endpoints. Endpoints that specify
multiple file parameters will include a
[]io.Reader
parameter, where each value is individually named. If theio.Reader
does not contain a name, a name is generated.
func (c *Client) Upload(
ctx context.Context,
fileList []io.Reader,
opts ...option.RequestOption,
) error {
...
}
- No changes since previous release candidate.
- Fix: Snippets for aliases to optional primitive values. With this, the generated snippet
will include the top-level pointer helpers (e.g.
acme.String(...)
).
- Fix: Package documentation is now generated into the correct package's
doc.go
. - Feature: Add support for generated endpoint snippets.
- The snippets will be available in the API reference documentation, as well as the snippets API.
- The snippets are not embedded in the SDK itself (yet).
import (
context "context"
time "time"
acme "github.com/acme/acme-go"
acmeclient "github.com/acme/acme-go/client"
option "github.com/acme/acme-go/option"
)
client := acmeclient.NewClient(
option.WithToken(
"<YOUR_AUTH_TOKEN>",
),
)
response, err := client.User.Create(
context.TODO(),
&acme.CreateUserRequest{
Username: "john",
Language: acme.LanguageEnglish,
Age: acme.Int(32),
Birthday: acme.MustParseDate(
"1980-06-01"
),
},
)
-
Feature: The generator now supports whitelabelling. When this is turned on, there will be no mention of Fern in the generated code.
Note: You must be on the enterprise tier to enable this mode.
- Feature: Enforce RFC3339 for date[time] serialization in request bodies.
- Fix: Query parameter supoort for optional
time.Time
types.
- Feature: Add support for
deepObject
query parameters. - Chore: Refactor query parameter serialization with
url
struct tags.
- Feature: Add
packageName
generator configuration. - Feature: Add support for
bytes
request bodies wrapped in an in-lined request.
- Fix:
text/plain
response handling.
- Feature: Add support for
bytes
request bodies withContent-Type
set toapplication/octet-stream
.
- Feature: Add automatic retry with exponential backoff.
- Feature: Refactor
ClientOption
asRequestOption
. - Feature: Add
includeLegacyClientOptions
generator configuration. - Feature: Support idempotency headers as a special
RequestOption
only available on idempotent endpoints. - Fix: Placement of path parameter documentation.
- Fix: Naming collision issue for undiscriminated unions that define more than one literal.
- Fix: File upload requests that specify query parameters.
- Fix: Optional query parameter dereferencing issue.
- Fix: Append version suffix for modules tagged with major versions greater than
1.X.X
.
- Fix: Support boolean literals.
- Fix: Union subt-ypes with no properties are now go 1.13 compatible.
- Feature: Add support for streaming endpoints.
- Feature: Add support for non-primitive file upload properties.
- Chore: Refactor
core.DoRequest
withcore.Caller
abstraction. - Chore: Update pinned dependencies in generated
go.mod
.