- name
- go-coding
- description
- Create or modify Go skipper code
- license
- MIT
- compatibility
- opencode, claude
- metadata
- {"audience":"maintainers, contributors"}
## What I do
- focus on Go code and architecture
## When to use me
Use this when you are reading or writing Go code
## Quick start
Use `gopls` to navigate the code.
### Package and type organization
Group related types together using a single `type (...)` block. Use iota for
enumerated constants. Keep unexported types lowercase; only export what callers
need.
```go
type (
matchBehavior int
matchMode int
)
const (
matchBehaviorAll matchBehavior = iota
matchBehaviorAny
matchModeExact matchMode = iota
matchModeRegexp
)
type (
spec struct {
name string
matchBehavior matchBehavior
matchMode matchMode
reg *registry
}
predicate struct {
kv map[string][]valueMatcher
matchBehavior matchBehavior
cache sync.Map
}
)
```
### Precompute in Create/New, not in the hot path
`CreateFilter` and `Create` run at route-build time. Precompute everything
costly there (compile regexps, build maps, normalize config). The hot path
(`Request`, `Response`, `Match`) must be allocation-free on the common case.
```go
func (s *spec) Create(args []any) (routing.Predicate, error) {
if len(args) == 0 || len(args)%2 != 0 {
return nil, predicates.ErrInvalidPredicateParameters
}
kv := make(map[string][]valueMatcher)
for i := 0; i < len(args); i += 2 {
key, keyOk := args[i].(string)
value, valueOk := args[i+1].(string)
if !keyOk || !valueOk {
return nil, predicates.ErrInvalidPredicateParameters
}
re, err := regexp.Compile(value)
if err != nil {
return nil, predicates.ErrInvalidPredicateParameters
}
kv[key] = append(kv[key], regexMatcher{regexp: re})
}
return &predicate{kv: kv}, nil
}
```
### Cache expensive Create results to survive frequent route table rebuilds
Route tables rebuild every few seconds. Cache `CreateFilter`/`Create` results
keyed by config args to avoid recreating identical instances and leaking memory.
Use `sync.Mutex` around the map; check-then-insert is fine for idempotent
objects.
```go
type spec struct {
mu sync.Mutex
filterMap map[string]*myFilter
}
func (s *spec) CreateFilter(args []interface{}) (filters.Filter, error) {
key, err := keyFromArgs(args)
if err != nil {
return nil, err
}
s.mu.Lock()
if f, ok := s.filterMap[key]; ok {
s.mu.Unlock()
return f, nil
}
s.mu.Unlock()
// ... build f ...
s.mu.Lock()
s.filterMap[key] = f
s.mu.Unlock()
return f, nil
}
```
### Cache hot-path lookups with sync.Map
For repeated lookups in `Match`/`Request`/`Response` (e.g. parsed tokens,
validated values), use `sync.Map` — it avoids a single global mutex bottleneck
under heavy read concurrency. Periodically clear it rather than tracking
per-entry TTLs; infrequent full clears are cheap.
```go
type predicate struct {
kv map[string][]valueMatcher
cache sync.Map // wire-string → bool
}
// in a background goroutine started at spec construction time:
func (r *registry) clean() {
tick := time.NewTicker(time.Hour)
defer tick.Stop()
for {
select {
case <-r.quit:
return
case <-tick.C:
r.mu.Lock()
for _, p := range r.predicateMap {
p.cache.Clear()
}
r.mu.Unlock()
}
}
}
```
### Avoid allocations in string parsing
Prefer `strings.Index` / `strings.Cut` over `strings.Split` to avoid
allocating a slice. Use iterator-style helpers (or `strings.SplitSeq` in
go1.24+) to range over parts without materializing them.
```go
// zero-allocation split iteration (backport of strings.SplitSeq)
func splitSeq(s, sep string) func(yield func(string) bool) {
return func(yield func(string) bool) {
for {
i := strings.Index(s, sep)
if i < 0 {
break
}
if !yield(s[:i]) {
return
}
s = s[i+len(sep):]
}
yield(s)
}
}
// usage
for part := range splitSeq(header, ",") {
token, value, found := strings.Cut(strings.TrimSpace(part), "=")
_ = token; _ = value; _ = found
}
```
### Skip work when input hasn't changed
For dataclients and watchers, record the last successful input (e.g. file
bytes, ETag) and return early without parsing when nothing changed.
```go
func (c *WatchClient) loadUpdates() watchResponse {
content, err := os.ReadFile(c.fileName)
if err != nil { ... }
if bytes.Equal(content, c.lastContent) {
return watchResponse{} // no change
}
r, err := eskip.Parse(string(content))
if err != nil {
c.lastContent = nil
return watchResponse{err: err}
}
c.lastContent = content
...
}
```
### Error handling
Wrap errors with context using `fmt.Errorf("...: %w", err)`. Return sentinel
errors (`predicates.ErrInvalidPredicateParameters`) from `Create`/`CreateFilter`
for invalid config so callers can distinguish config errors from runtime errors.
Never swallow errors in the hot path.
```go
req, endpointMetrics, err := p.mapRequest(ctx, requestContext)
if err != nil {
return nil, &proxyError{err: fmt.Errorf("could not map backend request: %w", err)}
}
```
View on GitHub