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")
```
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

30
base.go
View File

@@ -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
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)

2
go.mod
View File

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

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}