From 6abf2d051e4d345190f96113e1136eb2c80b0196 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sat, 22 Jun 2024 21:46:58 -0700 Subject: [PATCH] Working field fetch --- base.go | 106 ++++++----------------------- client.go | 126 +++------------------------------- errors.go | 64 ------------------ field-converter.go | 25 ------- get-bases.go | 37 ---------- get-records.go | 106 ----------------------------- http.go | 125 ++++++++++++++++++++++++++++++++++ record.go | 97 ++++----------------------- table.go | 164 ++++++++++----------------------------------- 9 files changed, 205 insertions(+), 645 deletions(-) delete mode 100644 errors.go delete mode 100644 field-converter.go delete mode 100644 get-bases.go delete mode 100644 get-records.go create mode 100644 http.go diff --git a/base.go b/base.go index 2419ee3..67b75d0 100644 --- a/base.go +++ b/base.go @@ -2,107 +2,39 @@ package airtable import ( "context" - "net/url" + "fmt" ) -// Base type of airtable base. type Base struct { ID string `json:"id"` Name string `json:"name"` PermissionLevel string `json:"permissionLevel"` + + c *Client } -// Base type of airtable bases. -type Bases struct { - Bases []*Base `json:"bases"` - Offset string `json:"offset,omitempty"` +func (c *Client) ListBases(ctx context.Context) ([]*Base, error) { + return listAll[Base](ctx, c, "meta/bases", nil, "bases", func(b *Base) error { + b.c = c + return nil + }) } -type Field struct { - ID string `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - Description string `json:"description"` - Options map[string]any `json:"options"` -} - -type View struct { - ID string `json:"id"` - Type string `json:"type"` - Name string `json:"name"` -} - -type TableSchema struct { - ID string `json:"id"` - PrimaryFieldID string `json:"primaryFieldId"` - Name string `json:"name"` - Description string `json:"description"` - Fields []*Field `json:"fields"` - Views []*View `json:"views"` -} - -type Tables struct { - Tables []*TableSchema `json:"tables"` -} - -// 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(ctx, "meta", "bases", "", params, bases) +func (c *Client) GetBaseByName(ctx context.Context, name string) (*Base, error) { + bases, err := c.ListBases(ctx) if err != nil { return nil, err } - return bases, nil -} - -// Table represents table object. -type BaseConfig struct { - client *Client - dbId string -} - -// GetBase return Base object. -func (c *Client) GetBaseSchema(dbId string) *BaseConfig { - return &BaseConfig{ - client: c, - dbId: dbId, - } -} - -// Do send the prepared -func (b *BaseConfig) Do() (*Tables, error) { - return b.GetTables() -} - -// 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) 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(ctx, "meta/bases", b.dbId, "tables", nil, tables) - if err != nil { - return nil, err + for _, base := range bases { + if base.Name == name { + return base, nil + } } - return tables, nil + return nil, fmt.Errorf("base '%s' not found", name) +} + +func (b Base) String() string { + return b.Name } diff --git a/client.go b/client.go index 6612495..0972582 100644 --- a/client.go +++ b/client.go @@ -1,12 +1,9 @@ package airtable import ( - "bytes" - "context" - "encoding/json" "fmt" "net/http" - "net/url" + "os" "time" ) @@ -34,122 +31,19 @@ func New(apiKey string) *Client { return c } -func (at *Client) SetRateLimit(rateLimit int) { - at.rateLimiter = time.Tick(time.Second / time.Duration(rateLimit)) -} - -func (at *Client) waitForRateLimit() { - <-at.rateLimiter -} - -func (at *Client) get(ctx context.Context, db, table, recordID string, params url.Values, target any) error { - return at.do(ctx, "GET", db, table, recordID, params, nil, target) -} - -func (at *Client) post(ctx context.Context, db, table string, data, target any) error { - return at.do(ctx, "POST", db, table, "", nil, data, target) -} - -func (at *Client) delete(ctx context.Context, db, table string, recordIDs []string, target any) error { - params := url.Values{} - - for _, recordID := range recordIDs { - params.Add("records[]", recordID) +func NewFromEnv() (*Client, error) { + apiKey := os.Getenv("AIRTABLE_TOKEN") + if apiKey == "" { + return nil, fmt.Errorf("please set $AIRTABLE_TOKEN") } - return at.do(ctx, "DELETE", db, table, "", params, nil, target) + return New(apiKey), nil } -func (at *Client) patch(ctx context.Context, db, table string, data, target any) error { - return at.do(ctx, "PATCH", db, table, "", nil, data, target) +func (c *Client) SetRateLimit(rateLimit int) { + c.rateLimiter = time.Tick(time.Second / time.Duration(rateLimit)) } -func (at *Client) put(ctx context.Context, db, table string, data, target any) error { - return at.do(ctx, "PUT", db, table, "", nil, data, target) -} - -func (at *Client) do(ctx context.Context, method, db, table, recordID string, params url.Values, data, target any) error { - var err error - - at.waitForRateLimit() - - url := fmt.Sprintf("%s/%s/%s", at.BaseURL, db, table) - - if recordID != "" { - url += fmt.Sprintf("/%s", recordID) - } - - body := []byte{} - - if data != nil { - body, err = json.Marshal(data) - if err != nil { - return fmt.Errorf("marshalling message body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(body)) - if err != nil { - return fmt.Errorf("cannot create request: %w", err) - } - - req.URL.RawQuery = params.Encode() - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", at.apiKey)) - - resp, err := at.Client.Do(req) - if err != nil { - return fmt.Errorf("http request failed: %w", err) - } - - defer resp.Body.Close() - - if resp.StatusCode < 200 || resp.StatusCode > 299 { - return makeHTTPClientError(url, resp) - } - - dec := json.NewDecoder(resp.Body) - - err = dec.Decode(target) - if err != nil { - return fmt.Errorf("json decode failed: %w", err) - } - - return nil -} - -func listAll[T any](ctx context.Context, c *Client, db, table string, params url.Values, key string) ([]*T, error) { - ret := []*T{} - - for { - resp := map[string]any{} - - err := c.get(ctx, db, table, "", params, &resp) - if err != nil { - return nil, err - } - - subresp, err := json.Marshal(resp[key]) - if err != nil { - return nil, err - } - - obj := []*T{} - - err = json.Unmarshal(subresp, &obj) - if err != nil { - return nil, err - } - - ret = append(ret, obj...) - - off, found := resp["offset"] - if !found { - return ret, nil - } - - params.Set("offset", off.(string)) - - } +func (c *Client) waitForRateLimit() { + <-c.rateLimiter } diff --git a/errors.go b/errors.go deleted file mode 100644 index a3fcc3d..0000000 --- a/errors.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright © 2020 Mike Berezin -// -// Use of this source code is governed by an MIT license. -// Details in the LICENSE file. - -package airtable - -import ( - "fmt" - "io" - "net/http" -) - -// HTTPClientError custom error to handle with response status. -type HTTPClientError struct { - StatusCode int - Err error -} - -func (e *HTTPClientError) Error() string { - return fmt.Sprintf("status %d, err: %v", e.StatusCode, e.Err) -} - -func makeHTTPClientError(url string, resp *http.Response) error { - var resError error - - respStatusText := "Unknown status text" - switch resp.StatusCode { - case 400: - respStatusText = "The request encoding is invalid; the request can't be parsed as a valid JSON." - case 401: - respStatusText = "Accessinga protected resource without authorization or with invalid credentials." - case 402: - respStatusText = "The account associated with the API key making requests hits a quota that can be increased by upgrading the Airtable account plan." - case 403: - respStatusText = "Accessing a protected resource with API credentials that don't have access to that resource." - case 404: - respStatusText = "Route or resource is not found. This error is returned when the request hits an undefined route, or if the resource doesn't exist (e.g. has been deleted)." - case 413: - respStatusText = "Too Large The request exceeded the maximum allowed payload size. You shouldn't encounter this under normal use." - case 422: - respStatusText = "The request data is invalid. This includes most of the base-specific validations. You will receive a detailed error message and code pointing to the exact issue." - case 500: - respStatusText = "Error The server encountered an unexpected condition." - case 502: - respStatusText = "Airtable's servers are restarting or an unexpected outage is in progress. You should generally not receive this error, and requests are safe to retry." - case 503: - 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 := 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) - } else { - resError = fmt.Errorf("HTTP request failure on %s:\n%d %s\n%s\n\nBody: %v", - url, resp.StatusCode, resp.Status, respStatusText, string(body)) - } - - return &HTTPClientError{ - StatusCode: resp.StatusCode, - Err: resError, - } -} diff --git a/field-converter.go b/field-converter.go deleted file mode 100644 index f2af9bc..0000000 --- a/field-converter.go +++ /dev/null @@ -1,25 +0,0 @@ - -package airtable - -import ( - "errors" - "time" -) - -const ( - dateTimeFormat = "2006-01-02T15:04:05.000Z" -) - -var ErrNotDateTime = errors.New("field is not date time") - -func ToDateTime(field any) (time.Time, error) { - fS, err := field.(string) - if !err { - return time.Time{}, ErrNotDateTime - } - return time.Parse(dateTimeFormat, fS) -} - -func FromDateTime(t time.Time) any { - return t.Format(dateTimeFormat) -} diff --git a/get-bases.go b/get-bases.go deleted file mode 100644 index 3050950..0000000 --- a/get-bases.go +++ /dev/null @@ -1,37 +0,0 @@ -package airtable - -import ( - "net/url" -) - -// GetBasesConfig helper type to use in. -// step by step get bases. -type GetBasesConfig struct { - client *Client - params url.Values -} - -// GetBases prepare step to get bases. -func (c *Client) GetBases() *GetBasesConfig { - return &GetBasesConfig{ - client: c, - params: url.Values{}, - } -} - -// Pagination -// The server returns one page of bases at a time. - -// If there are more records, the response will contain an offset. -// To fetch the next page of records, include offset in the next request's parameters. - -// WithOffset Pagination will stop when you've reached the end of your bases. -func (gbc *GetBasesConfig) WithOffset(offset string) *GetBasesConfig { - gbc.params.Set("offset", offset) - return gbc -} - -// Do send the prepared get records request. -func (gbc *GetBasesConfig) Do() (*Bases, error) { - return gbc.client.GetBasesWithParams(gbc.params) -} diff --git a/get-records.go b/get-records.go deleted file mode 100644 index ddfc459..0000000 --- a/get-records.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright © 2020 Mike Berezin -// -// Use of this source code is governed by an MIT license. -// Details in the LICENSE file. - -package airtable - -import ( - "fmt" - "net/url" - "strconv" -) - -// GetRecordsConfig helper type to use in. -// step by step get records. -type GetRecordsConfig struct { - table *Table - params url.Values -} - -// GetRecords prepare step to get records. -func (t *Table) GetRecords() *GetRecordsConfig { - return &GetRecordsConfig{ - table: t, - params: url.Values{}, - } -} - -// ReturnFields set returning field names. -func (grc *GetRecordsConfig) ReturnFields(fieldNames ...string) *GetRecordsConfig { - for _, fieldName := range fieldNames { - grc.params.Add("fields[]", fieldName) - } - return grc -} - -// WithFilterFormula add filter to request. -func (grc *GetRecordsConfig) WithFilterFormula(filterFormula string) *GetRecordsConfig { - grc.params.Set("filterByFormula", filterFormula) - return grc -} - -// WithSort add sorting to request. -func (grc *GetRecordsConfig) WithSort(sortQueries ...struct { - FieldName string - Direction string -}) *GetRecordsConfig { - for queryNum, sortQuery := range sortQueries { - grc.params.Set(fmt.Sprintf("sort[%v][field]", queryNum), sortQuery.FieldName) - grc.params.Set(fmt.Sprintf("sort[%v][direction]", queryNum), sortQuery.Direction) - } - return grc -} - -// FromView add view parameter to get records. -func (grc *GetRecordsConfig) FromView(viewNameOrID string) *GetRecordsConfig { - grc.params.Set("view", viewNameOrID) - return grc -} - -// MaxRecords The maximum total number of records that will be returned in your requests. -// If this value is larger than pageSize (which is 100 by default), -// you may have to load multiple pages to reach this total. -// See the Pagination section below for more. -func (grc *GetRecordsConfig) MaxRecords(maxRecords int) *GetRecordsConfig { - grc.params.Set("maxRecords", strconv.Itoa(maxRecords)) - return grc -} - -// PageSize The number of records returned in each request. -// Must be less than or equal to 100. Default is 100. -// See the Pagination section below for more. -func (grc *GetRecordsConfig) PageSize(pageSize int) *GetRecordsConfig { - grc.params.Set("pageSize", strconv.Itoa(pageSize)) - return grc -} - -// Pagination -// The server returns one page of records at a time. -// Each page will contain pageSize records, which is 100 by default. - -// If there are more records, the response will contain an offset. -// To fetch the next page of records, include offset in the next request's parameters. - -// WithOffset Pagination will stop when you've reached the end of your table. -// If the maxRecords parameter is passed, pagination will stop once you've reached this maximum. -func (grc *GetRecordsConfig) WithOffset(offset string) *GetRecordsConfig { - grc.params.Set("offset", offset) - return grc -} - -// InStringFormat add parameter to get records in string format. -// it require timezone -// https://support.airtable.com/hc/en-us/articles/216141558-Supported-timezones-for-SET-TIMEZONE -// and user locale data -// https://support.airtable.com/hc/en-us/articles/220340268-Supported-locale-modifiers-for-SET-LOCALE -func (grc *GetRecordsConfig) InStringFormat(timeZone, userLocale string) *GetRecordsConfig { - grc.params.Set("cellFormat", "string") - grc.params.Set("timeZone", timeZone) - grc.params.Set("userLocale", userLocale) - return grc -} - -func (grc *GetRecordsConfig) Do() ([]*Record, error) { - return grc.table.GetRecordsWithParams(grc.params) -} diff --git a/http.go b/http.go new file mode 100644 index 0000000..935c5da --- /dev/null +++ b/http.go @@ -0,0 +1,125 @@ +package airtable + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" +) + +func get[T any](ctx context.Context, c *Client, path string, params url.Values) (*T, error) { + return do[T](ctx, c, "GET", path, params, nil) +} + +func post[T any](ctx context.Context, c *Client, path string, data any) (*T, error) { + return do[T](ctx, c, "POST", path, nil, data) +} + +func del[T any](ctx context.Context, c *Client, path string, params url.Values) (*T, error) { + return do[T](ctx, c, "DELETE", path, params, nil) +} + +func patch[T any](ctx context.Context, c *Client, path string, data any) (*T, error) { + return do[T](ctx, c, "PATCH", path, nil, data) +} + +func put[T any](ctx context.Context, c *Client, path string, data any) (*T, error) { + return do[T](ctx, c, "PUT", path, nil, data) +} + +func do[T any](ctx context.Context, c *Client, method, path string, params url.Values, data any) (*T, error) { + var err error + + c.waitForRateLimit() + + url := fmt.Sprintf("%s/%s", c.BaseURL, path) + + body := []byte{} + + if data != nil { + body, err = json.Marshal(data) + if err != nil { + return nil, fmt.Errorf("marshalling message body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(body)) + if err != nil { + return nil, fmt.Errorf("cannot create request: %w", err) + } + + req.URL.RawQuery = params.Encode() + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey)) + + resp, err := c.Client.Do(req) + if err != nil { + return nil, fmt.Errorf("http request failed: %w", err) + } + + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return nil, fmt.Errorf("http error: %d %s", resp.StatusCode, http.StatusText(resp.StatusCode)) + } + + dec := json.NewDecoder(resp.Body) + + target := new(T) + + err = dec.Decode(target) + if err != nil { + return nil, fmt.Errorf("json decode failed: %w", err) + } + + return target, nil +} + +func listAll[T any](ctx context.Context, c *Client, path string, params url.Values, key string, cb func(*T) error) ([]*T, error) { + ret := []*T{} + + if params == nil { + params = url.Values{} + } + + for { + resp, err := get[map[string]any](ctx, c, path, params) + if err != nil { + return nil, err + } + + subresp, err := json.Marshal((*resp)[key]) + if err != nil { + return nil, err + } + + objs := []*T{} + + err = json.Unmarshal(subresp, &objs) + if err != nil { + return nil, err + } + + if cb != nil { + for _, obj := range objs { + err = cb(obj) + if err != nil { + return nil, err + } + } + } + + ret = append(ret, objs...) + + off, found := (*resp)["offset"] + if !found { + return ret, nil + } + + params.Set("offset", off.(string)) + + } +} diff --git a/record.go b/record.go index 63cab33..a52c6ab 100644 --- a/record.go +++ b/record.go @@ -1,102 +1,33 @@ -// Copyright © 2020 Mike Berezin -// -// Use of this source code is governed by an MIT license. -// Details in the LICENSE file. - package airtable import ( "context" - "net/url" + "fmt" ) -// Record base time of airtable record fields. type Record struct { - client *Client - table *Table ID string `json:"id,omitempty"` Fields map[string]any `json:"fields"` CreatedTime string `json:"createdTime,omitempty"` Deleted bool `json:"deleted,omitempty"` - // The Airtable API will perform best-effort automatic data conversion - // from string values if the typecast parameter is passed in. - // Automatic conversion is disabled by default to ensure data integrity, - // but it may be helpful for integrating with 3rd party data sources. - Typecast bool `json:"typecast,omitempty"` + c *Client + b *Base + t *Table } -// 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) +type ListRecordOptions struct { } -// 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(ctx, t.dbName, t.tableName, recordID, url.Values{}, result) - if err != nil { - return nil, err - } - - result.client = t.client - result.table = t - - return result, nil +func (t *Table) ListRecords(ctx context.Context, opts *ListRecordOptions) ([]*Record, error) { + return listAll[Record](ctx, t.c, fmt.Sprintf("%s/%s", t.b.ID, t.ID), nil, "records", func(r *Record) error { + r.c = t.c + r.b = t.b + r.t = t + return nil + }) } -// UpdateRecordPartial updates partial info on record. -func (r *Record) UpdateRecordPartial(changedFields map[string]any) (*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]any) (*Record, error) { - data := &Records{ - Records: []*Record{ - { - ID: r.ID, - Fields: changedFields, - }, - }, - } - response := new(Records) - - err := r.client.patch(ctx, r.table.dbName, r.table.tableName, data, response) - if err != nil { - return nil, err - } - - result := response.Records[0] - - result.client = r.client - result.table = r.table - - return result, nil -} - -// 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(ctx, r.table.dbName, r.table.tableName, []string{r.ID}, response) - if err != nil { - return nil, err - } - - result := response.Records[0] - result.client = r.client - result.table = r.table - - return result, nil +func (r Record) String() string { + return r.Fields[r.t.Fields[0].Name].(string) } diff --git a/table.go b/table.go index 7445a37..f6bef56 100644 --- a/table.go +++ b/table.go @@ -1,149 +1,59 @@ -// Copyright © 2020 Mike Berezin -// -// Use of this source code is governed by an MIT license. -// Details in the LICENSE file. - package airtable import ( "context" - "net/url" + "fmt" ) -// Records base type of airtable records. -type Records struct { - Records []*Record `json:"records"` - Offset string `json:"offset,omitempty"` - - // The Airtable API will perform best-effort automatic data conversion - // from string values if the typecast parameter is passed in. - // Automatic conversion is disabled by default to ensure data integrity, - // but it may be helpful for integrating with 3rd party data sources. - Typecast bool `json:"typecast,omitempty"` -} - -// Table represents table object. type Table struct { - client *Client - dbName string - tableName string + ID string `json:"id"` + PrimaryFieldID string `json:"primaryFieldId"` + Name string `json:"name"` + Description string `json:"description"` + Fields []*Field `json:"fields"` + Views []*View `json:"views"` + + c *Client + b *Base } -// GetTable return table object. -func (c *Client) GetTable(dbName, tableName string) *Table { - return &Table{ - client: c, - dbName: dbName, - tableName: tableName, - } +type Field struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description"` + Options map[string]any `json:"options"` } -func (t *Table) GetRecordsWithParams(params url.Values) ([]*Record, error) { - return t.GetRecordsWithParamsContext(context.Background(), params) +type View struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` } -func (t *Table) GetRecordsWithParamsContext(ctx context.Context, params url.Values) ([]*Record, error) { - records, err := listAll[Record](ctx, t.client, t.dbName, t.tableName, params, "records") +func (b *Base) ListTables(ctx context.Context) ([]*Table, error) { + return listAll[Table](ctx, b.c, fmt.Sprintf("meta/bases/%s/tables", b.ID), nil, "tables", func(t *Table) error { + t.c = b.c + t.b = b + return nil + }) +} + +func (b *Base) GetTableByName(ctx context.Context, name string) (*Table, error) { + tables, err := b.ListTables(ctx) if err != nil { return nil, err } - for _, record := range records { - record.client = t.client - record.table = t + for _, table := range tables { + if table.Name == name { + return table, nil + } } - return records, nil + return nil, fmt.Errorf("table '%s' not found", name) } -// 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(ctx, t.dbName, t.tableName, records, result) - if err != nil { - return nil, err - } - - for _, record := range result.Records { - record.client = t.client - record.table = t - } - - return result, err -} - -// 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(ctx, t.dbName, t.tableName, records, response) - if err != nil { - return nil, err - } - - for _, record := range response.Records { - record.client = t.client - record.table = t - } - - return response, nil -} - -// 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(ctx, t.dbName, t.tableName, records, response) - if err != nil { - return nil, err - } - - for _, record := range response.Records { - record.client = t.client - record.table = t - } - - return response, nil -} - -// 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(ctx, t.dbName, t.tableName, recordIDs, response) - if err != nil { - return nil, err - } - - for _, record := range response.Records { - record.client = t.client - record.table = t - } - - return response, nil +func (t Table) String() string { + return fmt.Sprintf("%s.%s", t.b, t.Name) }