go-coding
Create or modify Go skipper code
معلومات المصدر
- المستودع
- zalando/skipper
- آخر نشاط في المصدر
- ٣١ أغسطس ٢٠٢٦ في ١٣:٥٤
- لغة SKILL.md المكتشفة
- الإنجليزية
- النجوم
- ٣٬٣٣٢
- التفرعات
- ٤٠٨
خيارات التثبيت
يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.
مراجعة ملفات المصدر
اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.
عرض SKILL.md
SKILL.md
تعليمات المصدر · معاينة للقراءة فقط- 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)}
}
```
عرض على GitHub