2024-06-22 21:46:58 -07:00
|
|
|
package airtable
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
2024-06-23 16:32:28 -07:00
|
|
|
"io"
|
2024-06-22 21:46:58 -07:00
|
|
|
"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 {
|
2024-06-23 16:32:28 -07:00
|
|
|
errBody, _ := io.ReadAll(resp.Body)
|
|
|
|
|
return nil, fmt.Errorf("http error: %d %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), string(errBody))
|
2024-06-22 21:46:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|