Moko Consulting

Open-source software for Joomla, Gitea, and web platforms. Home of MokoSuite, MokoGitea, and MokoCLI.

Tennessee
standards/coding-go.-

Go Coding Standards

Go standards for the MokoGitea fork and any Go-based tooling.

Formatting

  • gofmt is mandatory — no exceptions. All code must be formatted with gofmt before commit.
  • goimports for import grouping: stdlib, third-party, project packages.
  • Run golangci-lint run before submitting PRs.

Naming Conventions

  • Packages: lowercase, single word preferred (backup, manifest, webhook)
  • Exported identifiers: MixedCaps (BackupEngine, ParseManifest)
  • Unexported identifiers: mixedCaps (parseConfig, newHandler)
  • Interfaces: named by method (Reader, Validator, ManifestProvider) — not IReader
  • Acronyms: consistent casing (HTTPHandler, APIClient, htmlParser)
  • No underscores in Go identifiers (snake_case is not idiomatic Go)

Error Handling

  • Always check errors — never use _ to discard errors unless explicitly justified with a comment
  • Wrap errors with context using fmt.Errorf:
if err := db.QueryRow(ctx, query).Scan(&result); err != nil {
    return fmt.Errorf("manifest: failed to load repo %d: %w", repoID, err)
}
  • Sentinel errors for expected conditions:
var ErrManifestNotFound = errors.New("manifest not found")
  • Custom error types when callers need to inspect error details:
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation: %s: %s", e.Field, e.Message)
}

Context Propagation

  • context.Context is always the first parameter
  • Never store contexts in structs
  • Use context.WithTimeout for external calls
  • Pass context through the full call chain
func (s *ManifestService) GetManifest(ctx context.Context, repoID int64) (*Manifest, error) {
    // ...
}

Struct Design

  • Embed interfaces for composition, not inheritance
  • Constructor functions named New{Type}:
func NewBackupEngine(db *sql.DB, fs afero.Fs) *BackupEngine {
    return &BackupEngine{db: db, fs: fs}
}
  • Functional options for complex configuration:
type Option func(*Engine)

func WithCompression(level int) Option {
    return func(e *Engine) { e.compressionLevel = level }
}

Testing

  • Tests in _test.go files alongside source
  • Table-driven tests for multiple cases:
func TestParseManifest(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    *Manifest
        wantErr bool
    }{
        {"valid joomla", validXML, &Manifest{Platform: "joomla"}, false},
        {"empty", "", nil, true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := ParseManifest([]byte(tt.input))
            if (err != nil) != tt.wantErr {
                t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
            }
            // ...
        })
    }
}
  • Use t.Helper() in test helper functions
  • Use testify/assert for complex assertions
  • go test -race ./... must pass

MokoGitea-Specific Guidelines

  • Custom code goes in modules/moko/ or routers/api/v1/moko/
  • Minimize changes to upstream Gitea files to ease rebasing
  • Add // MokoGitea: comment prefix when modifying upstream files
  • Custom database migrations in models/migrations/moko/
  • Keep .mokogitea/ manifest handling isolated in modules/moko/manifest/