From 7ef8b42b8d5404f65cdb76bfc19bacc637241304 Mon Sep 17 00:00:00 2001 From: Mike Berezin Date: Thu, 12 Jan 2023 12:54:18 +0500 Subject: [PATCH] impr: add custom context and custom client posibility #15 --- README.md | 8 ++++++++ base.go | 30 ++++++++++++++++++++++++------ base_test.go | 2 +- client.go | 29 +++++++++++++++++------------ errors.go | 4 ++-- go.mod | 2 +- mock-response_test.go | 4 ++-- record.go | 29 +++++++++++++++++++++++++---- table.go | 39 ++++++++++++++++++++++++++++++++++----- 9 files changed, 114 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index c6386bf..d13c8ef 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,14 @@ You should get `your_api_token` in the airtable [account page](https://airtable. client := airtable.NewClient("your_api_token") ``` +You can use custom http client here +```Go +client.SetCustomClient(http.DefaultClient) +``` + +### Custom context +Each method below can be used with custom context. Simply use `MethodNameContext` call and provide context as first argument. + ### List bases ```Go diff --git a/base.go b/base.go index 6f99ab4..e31604e 100644 --- a/base.go +++ b/base.go @@ -1,6 +1,7 @@ package airtable import ( + "context" "net/url" ) @@ -23,6 +24,7 @@ type Field struct { Name string `json:"name"` Description string `json:"description"` } + type View struct { ID string `json:"id"` Type string `json:"type"` @@ -45,9 +47,15 @@ type Tables struct { // GetBasesWithParams get bases with url values params // https://airtable.com/developers/web/api/list-bases func (at *Client) GetBasesWithParams(params url.Values) (*Bases, error) { + return at.GetBasesWithParamsContext(context.Background(), params) +} + +// getBasesWithParamsContext get bases with url values params +// with custom context +func (at *Client) GetBasesWithParamsContext(ctx context.Context, params url.Values) (*Bases, error) { bases := new(Bases) - err := at.get("meta", "bases", "", params, bases) + err := at.get(ctx, "meta", "bases", "", params, bases) if err != nil { return nil, err } @@ -59,7 +67,6 @@ func (at *Client) GetBasesWithParams(params url.Values) (*Bases, error) { type BaseConfig struct { client *Client dbId string - params url.Values } // GetBase return Base object. @@ -72,15 +79,26 @@ func (c *Client) GetBaseSchema(dbId string) *BaseConfig { // Do send the prepared func (b *BaseConfig) Do() (*Tables, error) { - return b.GetTablesWithParams() + return b.GetTables() } -// GetTablesWithParams get tables from a base with url values params +// Do send the prepared with custom context +func (b *BaseConfig) DoContext(ctx context.Context) (*Tables, error) { + return b.GetTablesContext(ctx) +} + +// GetTables get tables from a base with url values params // https://airtable.com/developers/web/api/get-base-schema -func (b *BaseConfig) GetTablesWithParams() (*Tables, error) { +func (b *BaseConfig) GetTables() (*Tables, error) { + return b.GetTablesContext(context.Background()) +} + +// getTablesContext get tables from a base with url values params +// with custom context +func (b *BaseConfig) GetTablesContext(ctx context.Context) (*Tables, error) { tables := new(Tables) - err := b.client.get("meta/bases", b.dbId, "tables", nil, tables) + err := b.client.get(ctx, "meta/bases", b.dbId, "tables", nil, tables) if err != nil { return nil, err } diff --git a/base_test.go b/base_test.go index 8b5d828..70e8fb7 100644 --- a/base_test.go +++ b/base_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -func TestGetBaseSchema(t *testing.T) { +func TestGetBases(t *testing.T) { client := testClient(t) baseschema := client.GetBaseSchema("test") baseschema.client.baseURL = mockResponse("base_schema.json").URL diff --git a/client.go b/client.go index c5174d0..54b45ec 100644 --- a/client.go +++ b/client.go @@ -11,7 +11,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "time" @@ -42,6 +42,11 @@ func NewClient(apiKey string) *Client { } } +// Set custom http client for custom usage +func (at *Client) SetCustomClient(client *http.Client) { + at.client = client +} + // SetRateLimit rate limit setter for custom usage // Airtable limit is 5 requests per second (we use 4) // https://airtable.com/{yourDatabaseID}/api/docs#curl/ratelimits @@ -72,7 +77,7 @@ func (at *Client) rateLimit() { <-at.rateLimiter } -func (at *Client) get(db, table, recordID string, params url.Values, target interface{}) error { +func (at *Client) get(ctx context.Context, db, table, recordID string, params url.Values, target interface{}) error { at.rateLimit() url := fmt.Sprintf("%s/%s/%s", at.baseURL, db, table) @@ -80,7 +85,7 @@ func (at *Client) get(db, table, recordID string, params url.Values, target inte url += fmt.Sprintf("/%s", recordID) } - req, err := http.NewRequestWithContext(context.Background(), "GET", url, nil) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return fmt.Errorf("cannot create request: %w", err) } @@ -98,7 +103,7 @@ func (at *Client) get(db, table, recordID string, params url.Values, target inte return nil } -func (at *Client) post(db, table string, data, response interface{}) error { +func (at *Client) post(ctx context.Context, db, table string, data, response interface{}) error { at.rateLimit() url := fmt.Sprintf("%s/%s/%s", at.baseURL, db, table) @@ -108,7 +113,7 @@ func (at *Client) post(db, table string, data, response interface{}) error { return fmt.Errorf("cannot marshal body: %w", err) } - req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewReader(body)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body)) if err != nil { return fmt.Errorf("cannot create request: %w", err) } @@ -119,7 +124,7 @@ func (at *Client) post(db, table string, data, response interface{}) error { return at.do(req, response) } -func (at *Client) delete(db, table string, recordIDs []string, target interface{}) error { +func (at *Client) delete(ctx context.Context, db, table string, recordIDs []string, target interface{}) error { at.rateLimit() rawURL := fmt.Sprintf("%s/%s/%s", at.baseURL, db, table) @@ -129,7 +134,7 @@ func (at *Client) delete(db, table string, recordIDs []string, target interface{ params.Add("records[]", recordID) } - req, err := http.NewRequestWithContext(context.Background(), "DELETE", rawURL, nil) + req, err := http.NewRequestWithContext(ctx, "DELETE", rawURL, nil) if err != nil { return fmt.Errorf("cannot create request: %w", err) } @@ -147,7 +152,7 @@ func (at *Client) delete(db, table string, recordIDs []string, target interface{ return nil } -func (at *Client) patch(db, table, data, response interface{}) error { +func (at *Client) patch(ctx context.Context, db, table, data, response interface{}) error { at.rateLimit() url := fmt.Sprintf("%s/%s/%s", at.baseURL, db, table) @@ -157,7 +162,7 @@ func (at *Client) patch(db, table, data, response interface{}) error { return fmt.Errorf("cannot marshal body: %w", err) } - req, err := http.NewRequestWithContext(context.Background(), "PATCH", url, bytes.NewReader(body)) + req, err := http.NewRequestWithContext(ctx, "PATCH", url, bytes.NewReader(body)) if err != nil { return fmt.Errorf("cannot create request: %w", err) } @@ -168,7 +173,7 @@ func (at *Client) patch(db, table, data, response interface{}) error { return at.do(req, response) } -func (at *Client) put(db, table, data, response interface{}) error { +func (at *Client) put(ctx context.Context, db, table, data, response interface{}) error { at.rateLimit() url := fmt.Sprintf("%s/%s/%s", at.baseURL, db, table) @@ -178,7 +183,7 @@ func (at *Client) put(db, table, data, response interface{}) error { return fmt.Errorf("cannot marshal body: %w", err) } - req, err := http.NewRequestWithContext(context.Background(), "PUT", url, bytes.NewReader(body)) + req, err := http.NewRequestWithContext(ctx, "PUT", url, bytes.NewReader(body)) if err != nil { return fmt.Errorf("cannot create request: %w", err) } @@ -207,7 +212,7 @@ func (at *Client) do(req *http.Request, response interface{}) error { return makeHTTPClientError(url, resp) } - b, err := ioutil.ReadAll(resp.Body) + b, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("HTTP Read error on response for %s: %w", url, err) } diff --git a/errors.go b/errors.go index 7446ab1..a3fcc3d 100644 --- a/errors.go +++ b/errors.go @@ -7,7 +7,7 @@ package airtable import ( "fmt" - "io/ioutil" + "io" "net/http" ) @@ -48,7 +48,7 @@ func makeHTTPClientError(url string, resp *http.Response) error { respStatusText = "The server could not process your request in time. The server could be temporarily unavailable, or it could have timed out processing your request. You should retry the request with backoffs." } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { resError = fmt.Errorf("HTTP request failure on %s:\n%d %s\n%s\n\nCannot parse body with err: %w", url, resp.StatusCode, resp.Status, respStatusText, err) diff --git a/go.mod b/go.mod index 2eb8607..82a0ec0 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/mehanizm/airtable -go 1.18 +go 1.19 diff --git a/mock-response_test.go b/mock-response_test.go index 385fba1..b055446 100644 --- a/mock-response_test.go +++ b/mock-response_test.go @@ -6,10 +6,10 @@ package airtable import ( - "io/ioutil" "log" "net/http" "net/http/httptest" + "os" "path/filepath" ) @@ -17,7 +17,7 @@ func mockResponse(paths ...string) *httptest.Server { parts := []string{".", "testdata"} filename := filepath.Join(append(parts, paths...)...) - mockData, err := ioutil.ReadFile(filename) + mockData, err := os.ReadFile(filename) if err != nil { log.Fatal(err) } diff --git a/record.go b/record.go index 3927986..a344921 100644 --- a/record.go +++ b/record.go @@ -5,7 +5,10 @@ package airtable -import "net/url" +import ( + "context" + "net/url" +) // Record base time of airtable record fields. type Record struct { @@ -26,9 +29,15 @@ type Record struct { // GetRecord get record from table // https://airtable.com/{yourDatabaseID}/api/docs#curl/table:{yourTableName}:retrieve func (t *Table) GetRecord(recordID string) (*Record, error) { + return t.GetRecordContext(context.Background(), recordID) +} + +// GetRecordContext get record from table +// with custom context +func (t *Table) GetRecordContext(ctx context.Context, recordID string) (*Record, error) { result := new(Record) - err := t.client.get(t.dbName, t.tableName, recordID, url.Values{}, result) + err := t.client.get(ctx, t.dbName, t.tableName, recordID, url.Values{}, result) if err != nil { return nil, err } @@ -41,6 +50,12 @@ func (t *Table) GetRecord(recordID string) (*Record, error) { // UpdateRecordPartial updates partial info on record. func (r *Record) UpdateRecordPartial(changedFields map[string]interface{}) (*Record, error) { + return r.UpdateRecordPartialContext(context.Background(), changedFields) +} + +// UpdateRecordPartialContext updates partial info on record +// with custom context +func (r *Record) UpdateRecordPartialContext(ctx context.Context, changedFields map[string]interface{}) (*Record, error) { data := &Records{ Records: []*Record{ { @@ -51,7 +66,7 @@ func (r *Record) UpdateRecordPartial(changedFields map[string]interface{}) (*Rec } response := new(Records) - err := r.client.patch(r.table.dbName, r.table.tableName, data, response) + err := r.client.patch(ctx, r.table.dbName, r.table.tableName, data, response) if err != nil { return nil, err } @@ -66,9 +81,15 @@ func (r *Record) UpdateRecordPartial(changedFields map[string]interface{}) (*Rec // DeleteRecord delete one record. func (r *Record) DeleteRecord() (*Record, error) { + return r.DeleteRecordContext(context.Background()) +} + +// DeleteRecordContext delete one record +// with custom context +func (r *Record) DeleteRecordContext(ctx context.Context) (*Record, error) { response := new(Records) - err := r.client.delete(r.table.dbName, r.table.tableName, []string{r.ID}, response) + err := r.client.delete(ctx, r.table.dbName, r.table.tableName, []string{r.ID}, response) if err != nil { return nil, err } diff --git a/table.go b/table.go index 199ecf4..08a8092 100644 --- a/table.go +++ b/table.go @@ -6,6 +6,7 @@ package airtable import ( + "context" "net/url" ) @@ -40,9 +41,15 @@ func (c *Client) GetTable(dbName, tableName string) *Table { // GetRecordsWithParams get records with url values params // https://airtable.com/{yourDatabaseID}/api/docs#curl/table:{yourTableName}:list func (t *Table) GetRecordsWithParams(params url.Values) (*Records, error) { + return t.GetRecordsWithParamsContext(context.Background(), params) +} + +// GetRecordsWithParamsContext get records with url values params +// with custom context +func (t *Table) GetRecordsWithParamsContext(ctx context.Context, params url.Values) (*Records, error) { records := new(Records) - err := t.client.get(t.dbName, t.tableName, "", params, records) + err := t.client.get(ctx, t.dbName, t.tableName, "", params, records) if err != nil { return nil, err } @@ -58,9 +65,15 @@ func (t *Table) GetRecordsWithParams(params url.Values) (*Records, error) { // AddRecords method to add lines to table (up to 10 in one request) // https://airtable.com/{yourDatabaseID}/api/docs#curl/table:{yourTableName}:create func (t *Table) AddRecords(records *Records) (*Records, error) { + return t.AddRecordsContext(context.Background(), records) +} + +// AddRecordsContext method to add lines to table (up to 10 in one request) +// with custom context +func (t *Table) AddRecordsContext(ctx context.Context, records *Records) (*Records, error) { result := new(Records) - err := t.client.post(t.dbName, t.tableName, records, result) + err := t.client.post(ctx, t.dbName, t.tableName, records, result) if err != nil { return nil, err } @@ -75,9 +88,14 @@ func (t *Table) AddRecords(records *Records) (*Records, error) { // UpdateRecords full update records. func (t *Table) UpdateRecords(records *Records) (*Records, error) { + return t.UpdateRecordsContext(context.Background(), records) +} + +// UpdateRecordsContext full update records with custom context. +func (t *Table) UpdateRecordsContext(ctx context.Context, records *Records) (*Records, error) { response := new(Records) - err := t.client.put(t.dbName, t.tableName, records, response) + err := t.client.put(ctx, t.dbName, t.tableName, records, response) if err != nil { return nil, err } @@ -92,9 +110,14 @@ func (t *Table) UpdateRecords(records *Records) (*Records, error) { // UpdateRecordsPartial partial update records. func (t *Table) UpdateRecordsPartial(records *Records) (*Records, error) { + return t.UpdateRecordsPartialContext(context.Background(), records) +} + +// UpdateRecordsPartialContext partial update records with custom context. +func (t *Table) UpdateRecordsPartialContext(ctx context.Context, records *Records) (*Records, error) { response := new(Records) - err := t.client.patch(t.dbName, t.tableName, records, response) + err := t.client.patch(ctx, t.dbName, t.tableName, records, response) if err != nil { return nil, err } @@ -110,9 +133,15 @@ func (t *Table) UpdateRecordsPartial(records *Records) (*Records, error) { // DeleteRecords delete records by recordID // up to 10 ids in one request. func (t *Table) DeleteRecords(recordIDs []string) (*Records, error) { + return t.DeleteRecordsContext(context.Background(), recordIDs) +} + +// DeleteRecordsContext delete records by recordID +// with custom context +func (t *Table) DeleteRecordsContext(ctx context.Context, recordIDs []string) (*Records, error) { response := new(Records) - err := t.client.delete(t.dbName, t.tableName, recordIDs, response) + err := t.client.delete(ctx, t.dbName, t.tableName, recordIDs, response) if err != nil { return nil, err }