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
gofmtis mandatory — no exceptions. All code must be formatted withgofmtbefore commit.goimportsfor import grouping: stdlib, third-party, project packages.- Run
golangci-lint runbefore 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) — notIReader - 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.Contextis always the first parameter- Never store contexts in structs
- Use
context.WithTimeoutfor 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.gofiles 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/assertfor complex assertions go test -race ./...must pass
MokoGitea-Specific Guidelines
- Custom code goes in
modules/moko/orrouters/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 inmodules/moko/manifest/
Pages