xe-go-style
Xe's Go Style Guide
Write Go code following the conventions and patterns used by Xe Iaso.
Project Structure
├── cmd/ # Main applications (each subdirectory is a binary)
│ ├── x/ # CLI with subcommands
│ │ ├── main.go
│ │ └── cmd/ # Subcommand packages
│ └── sakurajima/ # Service binaries
│ ├── main.go
│ └── internal/ # Command-specific internal packages
├── internal/ # Private packages shared across commands
├── web/ # Web-related services and API clients
└── gen/ # Generated code (protobuf)
Package naming: lowercase, single words preferred (slog, flagenv, kahless).
CLI Pattern (CRITICAL)
All command-line tools MUST call flagenv.Parse() and then flag.Parse().
package main
import (
"flag"
"github.com/facebookgo/flagenv"
)
var (
bind = flag.String("bind", ":8080", "HTTP bind address")
dbLoc = flag.String("db-loc", "", "Database location")
)
func main() {
flagenv.Parse()
flag.Parse()
}
Configuration loading order: flags → env vars (flagenv) → flags (final override).
Subcommands
Use github.com/google/subcommands for subcommand-based CLIs:
package main
import (
"context"
"flag"
"os"
"github.com/facebookgo/flagenv"
"github.com/google/subcommands"
)
func main() {
flagenv.Parse()
subcommands.Register(subcommands.HelpCommand(), "")
subcommands.Register(subcommands.FlagsCommand(), "")
subcommands.Register(subcommands.CommandsCommand(), "")
subcommands.Register(&serveCmd{}, "")
subcommands.Register(&versionCmd{}, "")
os.Exit(int(subcommands.Execute(context.Background())))
}
type serveCmd struct {
dbLoc string
}
func (c *serveCmd) Name() string { return "serve" }
func (c *serveCmd) Synopsis() string { return "Start the server" }
func (c *serveCmd) Usage() string {
return "serve [flags]\nStart the HTTP server.\n"
}
func (c *serveCmd) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.dbLoc, "db-loc", "", "Database location")
}
func (c *serveCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
// Implementation
return subcommands.ExitSuccess
}
Flag names use kebab-case: --db-loc, --grpc-bind, --slog-level.
Error Handling
Define package-level sentinel errors:
var ErrNotFound = errors.New("store: key not found")
var ErrCantDecode = errors.New("store: can't decode value")
Wrap errors with context using %w:
if err != nil {
return nil, fmt.Errorf("failed to connect to database: %w", err)
}
return nil, fmt.Errorf("ollama: error encoding request: %w", err)
Combine multiple validation errors with errors.Join:
func (t *Toplevel) Valid() error {
var errs []error
if err := t.Bind.Valid(); err != nil {
errs = append(errs, fmt.Errorf("invalid bind block:\n%w", err))
}
if len(errs) != 0 {
return fmt.Errorf("invalid configuration file:\n%w", errors.Join(errs...))
}
return nil
}
Check errors with errors.Is() and errors.As():
if errors.Is(err, ErrNotFound) {
// handle not found
}
Custom error types implement Error() and slog.LogValue():
type Error struct {
WantStatus, GotStatus int
URL *url.URL
Method string
ResponseBody string
}
func (e Error) Error() string {
return fmt.Sprintf("%s %s: wanted status code %d, got: %d: %v",
e.Method, e.URL, e.WantStatus, e.GotStatus, e.ResponseBody)
}
func (e Error) LogValue() slog.Value {
return slog.GroupValue(
slog.Int("want_status", e.WantStatus),
slog.Int("got_status", e.GotStatus),
slog.String("url", e.URL.String()),
slog.String("method", e.Method),
slog.String("body", e.ResponseBody),
)
}
Logging
Use log/slog (never the plain log package). Output JSON to stderr. Use -slog-level flag for runtime control.
slog.Info("starting up",
"bind", *bind,
"db-loc", *dbLoc,
)
slog.Error("failed to create dao", "err", err)
slog.Debug("processing", "count", len(items))
Always use "err" as the key for errors. Implement LogValue() for complex types:
func (s *Show) LogValue() slog.Value {
return slog.GroupValue(
slog.String("title", s.GetTitle()),
slog.String("disk_path", s.GetDiskPath()),
)
}
HTTP Patterns
Middleware pattern:
func PasswordMiddleware(username, password string, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok || user != username || pass != password {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
Chaining middleware:
var h http.Handler = topLevel
h = xffMW.Handler(h)
h = cors.Default().Handler(h)
h = FlyRegionAnnotation(h)
HTTP client with context and error handling:
func (c *Client) doRequest(ctx context.Context, method, path string, wantCode int, body io.Reader) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, method, u.String(), body)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
resp, err := c.http.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
if resp.StatusCode != wantCode {
return nil, web.NewError(wantCode, resp)
}
return resp, nil
}
Server lifecycle with errgroup:
g, gCtx := errgroup.WithContext(ctx)
g.Go(func() error {
ln, err := net.Listen("tcp", cfg.Bind.HTTP)
if err != nil {
return fmt.Errorf("failed to listen: %w", err)
}
return rtr.HandleHTTP(gCtx, ln)
})
return g.Wait()
Testing
Tests co-located with source (*_test.go). Use table-driven tests:
func TestDomainValid(t *testing.T) {
t.Parallel()
for _, tt := range []struct {
name string
input Domain
err error
errContains string
}{
{name: "simple happy path", input: Domain{Name: "example.com"}},
{name: "invalid domain", input: Domain{Name: "\uFFFD.com"}, err: ErrInvalidDomainName},
} {
t.Run(tt.name, func(t *testing.T) {
err := tt.input.Valid()
if tt.err != nil {
if !errors.Is(err, tt.err) {
t.Logf("want: %v", tt.err)
t.Logf("got: %v", err)
t.Error("got wrong error")
}
}
})
}
}
Testing best practices:
- Use
ttas the loop variable in table-driven tests - Always include
namefield for subtests - Use
t.Helper()for helper functions - Use
t.Parallel()for independent tests - Use
t.TempDir()for temporary directories - Use
httptest.NewServer()for HTTP mocking - Use
errors.Is()for error comparison - Include
t.Logf("want: %v", x)andt.Logf("got: %v", y)for debugging
func loadConfig(t *testing.T, fname string) config.Toplevel {
t.Helper()
// ...
}
Code Style
- Use
go fmt/goimportsfor formatting - Tabs for indentation
camelCasefor variablesPascalCasefor exported identifiers- Files use snake_case
- Packages use lower-case module names
More from xe/skills
gorm-dao
Write GORM data access code using the DAO (Data Access Object) pattern. Use when creating database models, writing queries, setting up GORM, adding CRUD methods, or working with gorm.io in Go services. Also use when the user mentions "DAO", "data access", "ORM", "database models", "GORM", or is building a Go service that talks to a relational database.
4templ-components
Create reusable templ UI components with props, children, and composition patterns. Use when building UI components, creating component libraries, mentions 'button component', 'card component', or 'reusable templ components'.
4templ-syntax
Learn and write templ component syntax including expressions, conditionals, loops, and Go integration. Use when writing .templ files, learning templ syntax, or mentions 'templ component', 'templ expressions', or '.templ file syntax'.
4templ-htmx
Build interactive hypermedia-driven applications with templ and HTMX. Use when creating dynamic UIs, real-time updates, AJAX interactions, mentions 'HTMX', 'dynamic content', or 'interactive templ app'.
4xe-writing-style
Transform unstructured notes into polished blog posts in Xe Iaso's voice. Use
1