Skip to content

Exposing context.Context in exported API without requiring major version bump #267

Closed
@theckman

Description

I'm raising this issue in anticipation of #266 being merged without many changes to the implementation.

Update: This proposal has been accepted and will be moving forward.

I'd like to provide consumers the ability to cancel outstanding requests using context.Context by creating methods within the package that take it as their first argument. We would not replace the current methods, but instead create a *WitContext() variant of each method so that you'd have GetUser() and GetUserWithContext(). We can accomplish this without code duplication, by having the non-context version call the *WithContext() variant and passing in a context.Background(). This should make the long-term maintenance minimal.

Progress Tracking

New Methods / Functions

For new additions / methods to the package, we SHOULD NOT have a *WithContext() variant and instead make the default implementation require a context.Context. For v2 I believe the *WithContext() variants should be dropped and the context.Context moved into the main method.

I think PR #260 is a good example of something we should update to have a context.Context as its first argument (no *WithContext() variant)

Why create such a large API to accomplish this?

Speaking intimately about the origins of this package, it was originally released as 1.0.0 versus a 0.x version which would have allowed us to more freely explore the API. Because we've already had a v1.0.0 and we use Go Modules, it would be a sizable amount of work for consumers to upgrade to v2+ (even if there aren't many changes they need to make to code).

As such, I think it's prudent for us to provide this functionality without breaking the API so we can better prepare for a 2.0.0 release that fixes any outstanding concerns we have with the API, so the cost that consumers need to pay is worth it.

Example Code

// GetUser gets details about an existing user. It's recommended to use
// GetUserWithContext instead.
func (c *Client) GetUser(id string, o GetUserOptions) (*User, error) {
	return c.GetUserWithContext(context.Background(), id, o)
}

// GetUserWithContext gets details about an existing user.
func (c *Client) GetUserWithContext(ctx context.Context, id string, o GetUserOptions) (*User, error) {
	v, err := query.Values(o)
	if err != nil {
		return nil, err
	}

	resp, err := c.get(ctx, "/users/"+id+"?"+v.Encode())
	return getUserFromResponse(c, resp, err)
}
Diff
diff --git a/user.go b/user.go
index d232b1c..daa999b 100644
--- a/user.go
+++ b/user.go
@@ -117,13 +117,19 @@ func (c *Client) DeleteUser(id string) error {
 	return err
 }
 
-// GetUser gets details about an existing user.
+// GetUser gets details about an existing user. It's recommended to use
+// GetUserWithContext instead.
 func (c *Client) GetUser(id string, o GetUserOptions) (*User, error) {
+	return c.GetUserWithContext(context.Background(), id, o)
+}
+
+// GetUserWithContext gets details about an existing user.
+func (c *Client) GetUserWithContext(ctx context.Context, id string, o GetUserOptions) (*User, error) {
 	v, err := query.Values(o)
 	if err != nil {
 		return nil, err
 	}
-	resp, err := c.get(context.TODO(), "/users/"+id+"?"+v.Encode())
+	resp, err := c.get(ctx, "/users/"+id+"?"+v.Encode())
 	return getUserFromResponse(c, resp, err)
 }

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions