Claude-skill-registry common-patterns
Reviews common RMS Go patterns including caching, retry logic, enums, and unmarshalling. Use when implementing utility patterns, seeing repeated boilerplate, or reviewing pattern usage.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/common-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-common-patterns && rm -rf "$T"
manifest:
skills/data/common-patterns/SKILL.mdsource content
Common Patterns
Purpose
Document frequently used patterns in RMS Go code. These patterns solve common problems and should be applied consistently.
Enum Pattern
Type-Safe Enums
// DO: Typed string enums type Status string const ( StatusPending Status = "PENDING" StatusInProgress Status = "IN_PROGRESS" StatusComplete Status = "COMPLETE" StatusCancelled Status = "CANCELLED" ) func (s Status) String() string { return string(s) } func (s Status) IsValid() bool { switch s { case StatusPending, StatusInProgress, StatusComplete, StatusCancelled: return true default: return false } } func (s Status) IsTerminal() bool { return s == StatusComplete || s == StatusCancelled } func ParseStatus(s string) (Status, error) { status := Status(s) if !status.IsValid() { return "", fmt.Errorf("invalid status: %s", s) } return status, nil }
Integer Enums with iota
// DO: Integer enums for ordered values type Priority int const ( PriorityLow Priority = iota + 1 PriorityMedium PriorityHigh PriorityCritical ) func (p Priority) String() string { switch p { case PriorityLow: return "low" case PriorityMedium: return "medium" case PriorityHigh: return "high" case PriorityCritical: return "critical" default: return "unknown" } }
Retry Pattern
Exponential Backoff
func WithRetry[T any](ctx context.Context, op func() (T, error), opts RetryOptions) (T, error) { var result T var lastErr error for attempt := 0; attempt <= opts.MaxRetries; attempt++ { result, lastErr = op() if lastErr == nil { return result, nil } // Don't retry on non-retryable errors if !opts.IsRetryable(lastErr) { return result, lastErr } if attempt == opts.MaxRetries { break } // Calculate backoff backoff := opts.BaseDelay * time.Duration(1<<attempt) if backoff > opts.MaxDelay { backoff = opts.MaxDelay } select { case <-time.After(backoff): case <-ctx.Done(): return result, ctx.Err() } } return result, fmt.Errorf("after %d retries: %w", opts.MaxRetries, lastErr) } type RetryOptions struct { MaxRetries int BaseDelay time.Duration MaxDelay time.Duration IsRetryable func(error) bool } var DefaultRetryOptions = RetryOptions{ MaxRetries: 3, BaseDelay: 100 * time.Millisecond, MaxDelay: 5 * time.Second, IsRetryable: func(err error) bool { // Retry transient errors return !errors.Is(err, ErrNotFound) && !errors.Is(err, ErrInvalidInput) }, }
Usage
task, err := WithRetry(ctx, func() (*Task, error) { return client.GetTask(ctx, id) }, DefaultRetryOptions)
Caching Pattern
Simple In-Memory Cache
type Cache[K comparable, V any] struct { mu sync.RWMutex items map[K]cacheItem[V] ttl time.Duration } type cacheItem[V any] struct { value V expiresAt time.Time } func NewCache[K comparable, V any](ttl time.Duration) *Cache[K, V] { return &Cache[K, V]{ items: make(map[K]cacheItem[V]), ttl: ttl, } } func (c *Cache[K, V]) Get(key K) (V, bool) { c.mu.RLock() defer c.mu.RUnlock() item, ok := c.items[key] if !ok || time.Now().After(item.expiresAt) { var zero V return zero, false } return item.value, true } func (c *Cache[K, V]) Set(key K, value V) { c.mu.Lock() defer c.mu.Unlock() c.items[key] = cacheItem[V]{ value: value, expiresAt: time.Now().Add(c.ttl), } } func (c *Cache[K, V]) Delete(key K) { c.mu.Lock() defer c.mu.Unlock() delete(c.items, key) }
Get-Or-Set Pattern
func (c *Cache[K, V]) GetOrSet(key K, fetch func() (V, error)) (V, error) { if v, ok := c.Get(key); ok { return v, nil } v, err := fetch() if err != nil { var zero V return zero, err } c.Set(key, v) return v, nil }
JSON/YAML Unmarshalling
Strict Unmarshalling
// DO: Use decoder with DisallowUnknownFields func ParseConfig(r io.Reader) (*Config, error) { var cfg Config decoder := json.NewDecoder(r) decoder.DisallowUnknownFields() if err := decoder.Decode(&cfg); err != nil { return nil, fmt.Errorf("decode config: %w", err) } return &cfg, nil }
Custom Unmarshalling
type Duration time.Duration func (d *Duration) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } duration, err := time.ParseDuration(s) if err != nil { return fmt.Errorf("invalid duration: %w", err) } *d = Duration(duration) return nil } func (d Duration) MarshalJSON() ([]byte, error) { return json.Marshal(time.Duration(d).String()) }
Enum Unmarshalling
func (s *Status) UnmarshalJSON(b []byte) error { var str string if err := json.Unmarshal(b, &str); err != nil { return err } status := Status(str) if !status.IsValid() { return fmt.Errorf("invalid status: %s", str) } *s = status return nil }
Pagination Pattern
Cursor-Based Pagination
type PageParams struct { Limit int Cursor string } type PageResult[T any] struct { Items []T NextCursor string HasMore bool } func (s *Store) List(ctx context.Context, params PageParams) (*PageResult[*Task], error) { limit := params.Limit if limit <= 0 || limit > 100 { limit = 50 } // Fetch one extra to determine if there are more tasks, err := s.query(ctx, params.Cursor, limit+1) if err != nil { return nil, err } hasMore := len(tasks) > limit if hasMore { tasks = tasks[:limit] } var nextCursor string if hasMore && len(tasks) > 0 { nextCursor = tasks[len(tasks)-1].ID.String() } return &PageResult[*Task]{ Items: tasks, NextCursor: nextCursor, HasMore: hasMore, }, nil }
Builder Pattern
Fluent Builder
type QueryBuilder struct { filters []string args []any orderBy string limit int offset int } func NewQueryBuilder() *QueryBuilder { return &QueryBuilder{} } func (b *QueryBuilder) Where(condition string, args ...any) *QueryBuilder { b.filters = append(b.filters, condition) b.args = append(b.args, args...) return b } func (b *QueryBuilder) OrderBy(column string) *QueryBuilder { b.orderBy = column return b } func (b *QueryBuilder) Limit(n int) *QueryBuilder { b.limit = n return b } func (b *QueryBuilder) Build() (string, []any) { query := "SELECT * FROM tasks" if len(b.filters) > 0 { query += " WHERE " + strings.Join(b.filters, " AND ") } if b.orderBy != "" { query += " ORDER BY " + b.orderBy } if b.limit > 0 { query += fmt.Sprintf(" LIMIT %d", b.limit) } return query, b.args } // Usage query, args := NewQueryBuilder(). Where("status = ?", "PENDING"). Where("priority >= ?", 3). OrderBy("created_at DESC"). Limit(50). Build()
Result Pattern
Result Type for Operations
type Result[T any] struct { Value T Err error } func (r Result[T]) IsOK() bool { return r.Err == nil } func (r Result[T]) Unwrap() (T, error) { return r.Value, r.Err } // Channel of results func processAll(ctx context.Context, items []Item) <-chan Result[*Task] { results := make(chan Result[*Task]) go func() { defer close(results) for _, item := range items { task, err := process(ctx, item) select { case results <- Result[*Task]{Value: task, Err: err}: case <-ctx.Done(): return } } }() return results }
Quick Reference
| Pattern | Use Case |
|---|---|
| Enum | Type-safe constants |
| Retry | Transient failures |
| Cache | Expensive computations |
| Pagination | Large datasets |
| Builder | Complex object construction |
| Result | Channel operations with errors |
See Also
- CACHING.md - Advanced caching patterns
- ENUMS.md - Enum implementation details
- functional-options - Option patterns