impr: add custom context and custom client posibility #15

This commit is contained in:
Mike Berezin
2023-01-12 12:54:18 +05:00
parent 1b1e6e9e92
commit 7ef8b42b8d
9 changed files with 114 additions and 33 deletions

View File

@@ -43,6 +43,14 @@ You should get `your_api_token` in the airtable [account page](https://airtable.
client := airtable.NewClient("your_api_token") 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 ### List bases
```Go ```Go

30
base.go
View File

@@ -1,6 +1,7 @@
package airtable package airtable
import ( import (
"context"
"net/url" "net/url"
) )
@@ -23,6 +24,7 @@ type Field struct {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
} }
type View struct { type View struct {
ID string `json:"id"` ID string `json:"id"`
Type string `json:"type"` Type string `json:"type"`
@@ -45,9 +47,15 @@ type Tables struct {
// GetBasesWithParams get bases with url values params // GetBasesWithParams get bases with url values params
// https://airtable.com/developers/web/api/list-bases // https://airtable.com/developers/web/api/list-bases
func (at *Client) GetBasesWithParams(params url.Values) (*Bases, error) { 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) bases := new(Bases)
err := at.get("meta", "bases", "", params, bases) err := at.get(ctx, "meta", "bases", "", params, bases)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -59,7 +67,6 @@ func (at *Client) GetBasesWithParams(params url.Values) (*Bases, error) {
type BaseConfig struct { type BaseConfig struct {
client *Client client *Client
dbId string dbId string
params url.Values
} }
// GetBase return Base object. // GetBase return Base object.
@@ -72,15 +79,26 @@ func (c *Client) GetBaseSchema(dbId string) *BaseConfig {
// Do send the prepared // Do send the prepared
func (b *BaseConfig) Do() (*Tables, error) { 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 // 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) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@@ -4,7 +4,7 @@ import (
"testing" "testing"
) )
func TestGetBaseSchema(t *testing.T) { func TestGetBases(t *testing.T) {
client := testClient(t) client := testClient(t)
baseschema := client.GetBaseSchema("test") baseschema := client.GetBaseSchema("test")
baseschema.client.baseURL = mockResponse("base_schema.json").URL baseschema.client.baseURL = mockResponse("base_schema.json").URL

View File

@@ -11,7 +11,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "net/http"
"net/url" "net/url"
"time" "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 // SetRateLimit rate limit setter for custom usage
// Airtable limit is 5 requests per second (we use 4) // Airtable limit is 5 requests per second (we use 4)
// https://airtable.com/{yourDatabaseID}/api/docs#curl/ratelimits // https://airtable.com/{yourDatabaseID}/api/docs#curl/ratelimits
@@ -72,7 +77,7 @@ func (at *Client) rateLimit() {
<-at.rateLimiter <-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() at.rateLimit()
url := fmt.Sprintf("%s/%s/%s", at.baseURL, db, table) 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) 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 { if err != nil {
return fmt.Errorf("cannot create request: %w", err) 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 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() at.rateLimit()
url := fmt.Sprintf("%s/%s/%s", at.baseURL, db, table) 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) 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 { if err != nil {
return fmt.Errorf("cannot create request: %w", err) 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) 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() at.rateLimit()
rawURL := fmt.Sprintf("%s/%s/%s", at.baseURL, db, table) 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) params.Add("records[]", recordID)
} }
req, err := http.NewRequestWithContext(context.Background(), "DELETE", rawURL, nil) req, err := http.NewRequestWithContext(ctx, "DELETE", rawURL, nil)
if err != nil { if err != nil {
return fmt.Errorf("cannot create request: %w", err) 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 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() at.rateLimit()
url := fmt.Sprintf("%s/%s/%s", at.baseURL, db, table) 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) 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 { if err != nil {
return fmt.Errorf("cannot create request: %w", err) 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) 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() at.rateLimit()
url := fmt.Sprintf("%s/%s/%s", at.baseURL, db, table) 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) 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 { if err != nil {
return fmt.Errorf("cannot create request: %w", err) 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) return makeHTTPClientError(url, resp)
} }
b, err := ioutil.ReadAll(resp.Body) b, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return fmt.Errorf("HTTP Read error on response for %s: %w", url, err) return fmt.Errorf("HTTP Read error on response for %s: %w", url, err)
} }

View File

@@ -7,7 +7,7 @@ package airtable
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "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." 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 { if err != nil {
resError = fmt.Errorf("HTTP request failure on %s:\n%d %s\n%s\n\nCannot parse body with err: %w", 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) url, resp.StatusCode, resp.Status, respStatusText, err)

2
go.mod
View File

@@ -1,3 +1,3 @@
module github.com/mehanizm/airtable module github.com/mehanizm/airtable
go 1.18 go 1.19

View File

@@ -6,10 +6,10 @@
package airtable package airtable
import ( import (
"io/ioutil"
"log" "log"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"path/filepath" "path/filepath"
) )
@@ -17,7 +17,7 @@ func mockResponse(paths ...string) *httptest.Server {
parts := []string{".", "testdata"} parts := []string{".", "testdata"}
filename := filepath.Join(append(parts, paths...)...) filename := filepath.Join(append(parts, paths...)...)
mockData, err := ioutil.ReadFile(filename) mockData, err := os.ReadFile(filename)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@@ -5,7 +5,10 @@
package airtable package airtable
import "net/url" import (
"context"
"net/url"
)
// Record base time of airtable record fields. // Record base time of airtable record fields.
type Record struct { type Record struct {
@@ -26,9 +29,15 @@ type Record struct {
// GetRecord get record from table // GetRecord get record from table
// https://airtable.com/{yourDatabaseID}/api/docs#curl/table:{yourTableName}:retrieve // https://airtable.com/{yourDatabaseID}/api/docs#curl/table:{yourTableName}:retrieve
func (t *Table) GetRecord(recordID string) (*Record, error) { 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) 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 { if err != nil {
return nil, err return nil, err
} }
@@ -41,6 +50,12 @@ func (t *Table) GetRecord(recordID string) (*Record, error) {
// UpdateRecordPartial updates partial info on record. // UpdateRecordPartial updates partial info on record.
func (r *Record) UpdateRecordPartial(changedFields map[string]interface{}) (*Record, error) { 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{ data := &Records{
Records: []*Record{ Records: []*Record{
{ {
@@ -51,7 +66,7 @@ func (r *Record) UpdateRecordPartial(changedFields map[string]interface{}) (*Rec
} }
response := new(Records) 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 { if err != nil {
return nil, err return nil, err
} }
@@ -66,9 +81,15 @@ func (r *Record) UpdateRecordPartial(changedFields map[string]interface{}) (*Rec
// DeleteRecord delete one record. // DeleteRecord delete one record.
func (r *Record) DeleteRecord() (*Record, error) { 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) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@@ -6,6 +6,7 @@
package airtable package airtable
import ( import (
"context"
"net/url" "net/url"
) )
@@ -40,9 +41,15 @@ func (c *Client) GetTable(dbName, tableName string) *Table {
// GetRecordsWithParams get records with url values params // GetRecordsWithParams get records with url values params
// https://airtable.com/{yourDatabaseID}/api/docs#curl/table:{yourTableName}:list // https://airtable.com/{yourDatabaseID}/api/docs#curl/table:{yourTableName}:list
func (t *Table) GetRecordsWithParams(params url.Values) (*Records, error) { 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) 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 { if err != nil {
return nil, err 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) // AddRecords method to add lines to table (up to 10 in one request)
// https://airtable.com/{yourDatabaseID}/api/docs#curl/table:{yourTableName}:create // https://airtable.com/{yourDatabaseID}/api/docs#curl/table:{yourTableName}:create
func (t *Table) AddRecords(records *Records) (*Records, error) { 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) 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 { if err != nil {
return nil, err return nil, err
} }
@@ -75,9 +88,14 @@ func (t *Table) AddRecords(records *Records) (*Records, error) {
// UpdateRecords full update records. // UpdateRecords full update records.
func (t *Table) UpdateRecords(records *Records) (*Records, error) { 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) 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 { if err != nil {
return nil, err return nil, err
} }
@@ -92,9 +110,14 @@ func (t *Table) UpdateRecords(records *Records) (*Records, error) {
// UpdateRecordsPartial partial update records. // UpdateRecordsPartial partial update records.
func (t *Table) UpdateRecordsPartial(records *Records) (*Records, error) { 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) 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 { if err != nil {
return nil, err return nil, err
} }
@@ -110,9 +133,15 @@ func (t *Table) UpdateRecordsPartial(records *Records) (*Records, error) {
// DeleteRecords delete records by recordID // DeleteRecords delete records by recordID
// up to 10 ids in one request. // up to 10 ids in one request.
func (t *Table) DeleteRecords(recordIDs []string) (*Records, error) { 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) 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 { if err != nil {
return nil, err return nil, err
} }