Each recipe includes the problem, solution code, and rationale.
Configurable base URL for mocks
Problem: Test connector against mock server without hitting production.
Solution:
// pkg/config/config.go
type Config struct {
APIKey string `mapstructure:"api-key"`
BaseURL string `mapstructure:"base-url"` // Required for testability
}
// cmd/baton-example/main.go
func main() {
fields := []field.SchemaField{
field.StringField("api-key",
field.WithRequired(true),
field.WithDescription("API key for authentication"),
),
field.StringField("base-url",
field.WithDefaultValue("https://api.example.com"),
field.WithDescription("Base URL (use http://localhost:8080 for testing)"),
),
}
// ...
}
// pkg/client/client.go
func New(cfg *Config) *Client {
baseURL := cfg.BaseURL
if baseURL == "" {
baseURL = "https://api.example.com"
}
return &Client{baseURL: baseURL}
}
Usage:
# Production
./baton-example --api-key $KEY
# Testing
./baton-example --api-key test --base-url http://localhost:8080
Why: Without configurable base URL, you can’t test without hitting production. This breaks CI/CD and risks exposing production data.
Insecure TLS for local testing
Problem: Test against mock server with self-signed certificate.
Solution:
// pkg/config/config.go
type Config struct {
APIKey string `mapstructure:"api-key"`
BaseURL string `mapstructure:"base-url"`
Insecure bool `mapstructure:"insecure"` // Skip TLS verification
}
// pkg/client/client.go
func New(ctx context.Context, cfg *Config) (*Client, error) {
opts := []uhttp.Option{}
if cfg.Insecure {
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
opts = append(opts, uhttp.WithTransport(transport))
}
httpClient, err := uhttp.NewBaseHttpClient(ctx, opts...)
if err != nil {
return nil, err
}
return &Client{http: httpClient, baseURL: cfg.BaseURL}, nil
}
Usage:
# Testing with self-signed cert
./baton-example --api-key test --base-url https://localhost:8443 --insecure
Why: Mock servers often use self-signed certificates. The --insecure flag enables testing without installing custom CAs.
Never use --insecure in production. This flag is strictly for local testing with mock servers.
LDAP server-side paging
Problem: Paginate large LDAP result sets.
Solution:
import (
"encoding/base64"
"github.com/go-ldap/ldap/v3"
)
func (u *userBuilder) List(ctx context.Context, _ *v2.ResourceId,
pToken *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
const pageSize = 1000
// Create paging control
pagingControl := ldap.NewControlPaging(uint32(pageSize))
// Restore cookie from token if continuing
if pToken.Token != "" {
cookie, err := base64.StdEncoding.DecodeString(pToken.Token)
if err != nil {
return nil, "", nil, fmt.Errorf("invalid pagination token: %w", err)
}
pagingControl.SetCookie(cookie)
}
// Execute search with paging control
searchRequest := ldap.NewSearchRequest(
"dc=example,dc=com",
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0, 0, false,
"(objectClass=user)",
[]string{"cn", "mail", "sAMAccountName"},
[]ldap.Control{pagingControl},
)
result, err := u.conn.Search(searchRequest)
if err != nil {
return nil, "", nil, err
}
// Convert entries to resources
var resources []*v2.Resource
for _, entry := range result.Entries {
r, _ := resource.NewUserResource(
entry.GetAttributeValue("cn"),
userResourceType,
entry.GetAttributeValue("sAMAccountName"),
resource.WithEmail(entry.GetAttributeValue("mail"), true),
)
resources = append(resources, r)
}
// Extract next page cookie
nextToken := ""
pagingResult := ldap.FindControl(result.Controls, ldap.ControlTypePaging)
if pc, ok := pagingResult.(*ldap.ControlPaging); ok && len(pc.Cookie) > 0 {
nextToken = base64.StdEncoding.EncodeToString(pc.Cookie)
}
return resources, nextToken, nil, nil
}
Why: LDAP servers handle pagination server-side with cookies. The cookie must be base64-encoded to fit in the string token.
Problem: Paginate children within paginated parents (e.g., members within each group).
Solution:
func (g *groupBuilder) Grants(ctx context.Context, resource *v2.Resource,
pToken *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
bag := &pagination.Bag{}
if err := bag.Unmarshal(pToken.Token); err != nil {
return nil, "", nil, err
}
// On first call for this resource, push initial state
if bag.Current() == nil {
bag.Push(pagination.PageState{
ResourceID: resource.Id.Resource,
ResourceTypeID: resource.Id.ResourceType,
})
}
// Get current page of members
members, nextCursor, err := g.client.GetGroupMembers(ctx,
bag.Current().ResourceID,
bag.PageToken())
if err != nil {
return nil, "", nil, err
}
// Convert to grants
var grants []*v2.Grant
for _, member := range members {
g := grant.NewGrant(resource, "member",
&v2.ResourceId{ResourceType: "user", Resource: member.ID})
grants = append(grants, g)
}
// Update bag with next page cursor
if nextCursor != "" {
bag.Next(nextCursor)
} else {
bag.Pop() // Done with this resource
}
nextToken, err := bag.Marshal()
if err != nil {
return nil, "", nil, err
}
return grants, nextToken, nil, nil
}
Why: The Bag type is a stack that manages nested pagination state. Push when entering a nested level, pop when done.
Next