Skip to content

Commit e583078

Browse files
author
Baton Admin
committed
chore: update connector skills via baton-admin
1 parent cc885d8 commit e583078

File tree

1 file changed

+387
-0
lines changed

1 file changed

+387
-0
lines changed
Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
# ref-antipatterns
2+
3+
What NOT to do when building connectors.
4+
5+
---
6+
7+
## Critical Antipatterns (Will Cause Production Issues)
8+
9+
### Logging Secrets
10+
11+
```go
12+
// NEVER DO THIS
13+
log.Printf("Authenticating with API key: %s", apiKey)
14+
log.Printf("Request: %+v", req) // May contain auth headers
15+
```
16+
17+
**Why it's bad:** Credentials end up in logs, which end up in monitoring systems, which end up in security incidents.
18+
19+
**Do instead:**
20+
```go
21+
log.Printf("Authenticating with API key: %s...", apiKey[:8]) // Truncate
22+
// Or better: don't log credentials at all
23+
```
24+
25+
---
26+
27+
### Buffering All Data in Memory
28+
29+
```go
30+
// NEVER DO THIS
31+
func (u *userBuilder) List(ctx context.Context, ...) ([]*v2.Resource, ...) {
32+
allUsers := []User{}
33+
for page := 1; ; page++ {
34+
users, _ := client.ListUsers(page)
35+
allUsers = append(allUsers, users...) // Memory grows unbounded
36+
if len(users) == 0 {
37+
break
38+
}
39+
}
40+
// Process all users
41+
return convertAll(allUsers), "", nil, nil
42+
}
43+
```
44+
45+
**Why it's bad:** For large organizations (100k+ users), this OOMs.
46+
47+
**Do instead:**
48+
```go
49+
func (u *userBuilder) List(ctx context.Context, parentID *v2.ResourceId,
50+
token *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
51+
52+
page := extractPage(token)
53+
users, nextPage, _ := client.ListUsers(page)
54+
55+
resources := make([]*v2.Resource, 0, len(users))
56+
for _, user := range users {
57+
r, _ := convertUser(user)
58+
resources = append(resources, r)
59+
}
60+
61+
return resources, nextPage, nil, nil // SDK handles checkpointing
62+
}
63+
```
64+
65+
---
66+
67+
### Ignoring Context Cancellation
68+
69+
```go
70+
// NEVER DO THIS
71+
func (u *userBuilder) List(ctx context.Context, ...) {
72+
users, _ := client.ListUsers() // ctx not passed
73+
for _, user := range users {
74+
// Long processing, ignores ctx.Done()
75+
}
76+
}
77+
```
78+
79+
**Why it's bad:** Cancelled context means "stop now." Ignoring it wastes resources and causes zombie operations.
80+
81+
**Do instead:**
82+
```go
83+
func (u *userBuilder) List(ctx context.Context, ...) {
84+
users, err := client.ListUsers(ctx) // Pass ctx
85+
if err != nil {
86+
return nil, "", nil, err
87+
}
88+
89+
for _, user := range users {
90+
select {
91+
case <-ctx.Done():
92+
return nil, "", nil, ctx.Err()
93+
default:
94+
}
95+
// Process user
96+
}
97+
}
98+
```
99+
100+
---
101+
102+
### Swallowing Errors
103+
104+
```go
105+
// NEVER DO THIS
106+
users, err := client.ListUsers(ctx)
107+
if err != nil {
108+
log.Println("error:", err)
109+
// Continues with empty users - SILENT DATA LOSS
110+
}
111+
```
112+
113+
**Why it's bad:** Sync reports success but data is incomplete.
114+
115+
**Do instead:**
116+
```go
117+
users, err := client.ListUsers(ctx)
118+
if err != nil {
119+
return nil, "", nil, fmt.Errorf("baton-service: failed to list users: %w", err)
120+
}
121+
```
122+
123+
---
124+
125+
## High-Risk Antipatterns
126+
127+
### Hardcoded URLs
128+
129+
```go
130+
// BAD
131+
const baseURL = "https://api.service.com"
132+
133+
func NewClient(apiKey string) *Client {
134+
return &Client{baseURL: baseURL, apiKey: apiKey}
135+
}
136+
```
137+
138+
**Why it's bad:** Can't test against mock servers.
139+
140+
**Do instead:**
141+
```go
142+
// GOOD - configurable base URL
143+
func NewClient(baseURL, apiKey string) *Client {
144+
if baseURL == "" {
145+
baseURL = "https://api.service.com"
146+
}
147+
return &Client{baseURL: baseURL, apiKey: apiKey}
148+
}
149+
```
150+
151+
---
152+
153+
### Unchecked Type Assertions
154+
155+
```go
156+
// BAD - panics if wrong type
157+
userID := data["user_id"].(string)
158+
count := data["count"].(int)
159+
```
160+
161+
**Do instead:**
162+
```go
163+
// GOOD - safe extraction
164+
userID, ok := data["user_id"].(string)
165+
if !ok {
166+
return fmt.Errorf("user_id not a string")
167+
}
168+
```
169+
170+
---
171+
172+
### Infinite Pagination Loops
173+
174+
```go
175+
// BAD - hardcoded token causes infinite loop
176+
func (u *userBuilder) List(...) (...) {
177+
// ...
178+
return resources, "next", nil, nil // Always returns "next"
179+
}
180+
```
181+
182+
**Do instead:**
183+
```go
184+
// GOOD - token from API response
185+
if resp.HasMore {
186+
return resources, resp.NextCursor, nil, nil
187+
}
188+
return resources, "", nil, nil // Empty token ends pagination
189+
```
190+
191+
---
192+
193+
### Using Email as Resource ID
194+
195+
```go
196+
// BAD - email can change
197+
rs.NewUserResource(user.Name, userType, user.Email, opts)
198+
```
199+
200+
**Why it's bad:** If user changes email, C1 sees it as a new user.
201+
202+
**Do instead:**
203+
```go
204+
// GOOD - stable ID
205+
rs.NewUserResource(user.Name, userType, user.ID, opts)
206+
```
207+
208+
---
209+
210+
## Medium-Risk Antipatterns
211+
212+
### Missing Error Context
213+
214+
```go
215+
// BAD
216+
return fmt.Errorf("failed")
217+
return fmt.Errorf("error: %w", err)
218+
```
219+
220+
**Do instead:**
221+
```go
222+
// GOOD
223+
return fmt.Errorf("baton-service: failed to list users (page %d): %w", page, err)
224+
```
225+
226+
---
227+
228+
### Breaking Error Chain
229+
230+
```go
231+
// BAD - %v breaks errors.Is/As
232+
return fmt.Errorf("failed: %v", err)
233+
```
234+
235+
**Do instead:**
236+
```go
237+
// GOOD - %w preserves chain
238+
return fmt.Errorf("failed: %w", err)
239+
```
240+
241+
---
242+
243+
### defer Before Error Check
244+
245+
```go
246+
// BAD - panics if resp is nil
247+
resp, err := client.Do(req)
248+
defer resp.Body.Close()
249+
if err != nil {
250+
return err
251+
}
252+
```
253+
254+
**Do instead:**
255+
```go
256+
// GOOD
257+
resp, err := client.Do(req)
258+
if err != nil {
259+
return err
260+
}
261+
defer resp.Body.Close()
262+
```
263+
264+
---
265+
266+
### Nil Pointer in Error Path
267+
268+
```go
269+
// BAD - resp may be nil
270+
resp, err := client.Do(req)
271+
if err != nil {
272+
log.Printf("status: %d", resp.StatusCode) // PANIC
273+
return err
274+
}
275+
```
276+
277+
**Do instead:**
278+
```go
279+
// GOOD
280+
resp, err := client.Do(req)
281+
if err != nil {
282+
if resp != nil {
283+
log.Printf("status: %d", resp.StatusCode)
284+
}
285+
return err
286+
}
287+
```
288+
289+
---
290+
291+
## Low-Risk Antipatterns (Code Quality)
292+
293+
### Over-Fetching API Data
294+
295+
```go
296+
// BAD - fetching more than needed
297+
users, _ := client.ListUsersWithAllDetails(ctx) // Slow, unnecessary
298+
```
299+
300+
**Do instead:**
301+
```go
302+
// GOOD - fetch only what's needed
303+
users, _ := client.ListUsersBasic(ctx)
304+
```
305+
306+
---
307+
308+
### Copy-Paste Resource Builders
309+
310+
```go
311+
// BAD - duplicated code with slight variations
312+
func (u *userBuilder) List(...) {
313+
// 100 lines of code
314+
}
315+
func (g *groupBuilder) List(...) {
316+
// 95 lines of nearly identical code
317+
}
318+
```
319+
320+
**Do instead:**
321+
```go
322+
// GOOD - extract common patterns
323+
func (u *userBuilder) List(...) {
324+
return listResources(ctx, token, u.client.ListUsers, convertUser)
325+
}
326+
func (g *groupBuilder) List(...) {
327+
return listResources(ctx, token, g.client.ListGroups, convertGroup)
328+
}
329+
```
330+
331+
---
332+
333+
### Magic Numbers
334+
335+
```go
336+
// BAD
337+
client.ListUsers(100) // What is 100?
338+
time.Sleep(5 * time.Second) // Why 5?
339+
```
340+
341+
**Do instead:**
342+
```go
343+
// GOOD
344+
const defaultPageSize = 100
345+
client.ListUsers(defaultPageSize)
346+
347+
const rateLimitBackoff = 5 * time.Second
348+
time.Sleep(rateLimitBackoff)
349+
```
350+
351+
---
352+
353+
### Unused Imports and Variables
354+
355+
```go
356+
// BAD - triggers linter warnings
357+
import (
358+
"unused/package"
359+
)
360+
361+
func foo() {
362+
x := computeSomething() // x never used
363+
}
364+
```
365+
366+
Keep code clean. Run `go vet` and `golangci-lint`.
367+
368+
---
369+
370+
## Antipattern Detection Commands
371+
372+
```bash
373+
# Logging secrets
374+
grep -r 'log.*apiKey\|log.*password\|log.*secret\|log.*token' --include="*.go"
375+
376+
# Missing ctx pass-through
377+
grep -r 'ListUsers()\|GetUser()' --include="*.go" | grep -v ctx
378+
379+
# Error swallowing
380+
grep -rA2 'if err != nil' --include="*.go" | grep -B1 'log.Print' | grep -v return
381+
382+
# Hardcoded URLs
383+
grep -r 'https://.*\.com' --include="*.go" | grep -v _test.go
384+
385+
# Direct type assertions
386+
grep -rE '\.\([A-Za-z]+\)[^,]' --include="*.go" | grep -v ", ok"
387+
```

0 commit comments

Comments
 (0)