@packageonly Annotation
The @packageonly annotation restricts usage of types, functions, or methods to specific packages.
Motivation
Go doesn't provide built-in mechanisms to restrict which packages can use specific code. This is needed for maintaining clean architecture:
- Controllers should only be used in routing layer
- Database models should only be accessed by repository layer
- Internal utilities should stay within specific modules
The @packageonly annotation fills this gap.
Syntax
// @packageonly
type Helper struct {}
// @packageonly pkg1, pkg2
func SpecialFunction() {}
// @packageonly myapp/internal/auth
func (s *Service) AdminMethod() {}
Parameters
- Package list (optional): Comma-separated list of allowed packages
- Can specify package names (e.g.,
testing) or full paths (e.g.,myapp/internal/auth) - If no packages specified, only the current package is allowed
- Current package is always included automatically
How It Works
GoGreement detects usage of @packageonly items outside allowed packages:
- Type usage: Variable declarations, composite literals, type assertions
- Function calls: Direct calls to
@packageonlyfunctions - Method calls: Calls to
@packageonlymethods
Key Behaviors
- Current package always allowed: The declaring package is automatically included
- No generics support: Cannot be used with generic declarations
- Per-file deduplication: Only one error per type per file (avoids spam)
- Catches all usage: Type assertions, composite literals, variable declarations
- Can be suppressed: Use
@ignoreto allow usage in specific places - Cross-package enforcement: Works even if
@packageonlywas declared in an external module - Multiple annotations supported: Multiple
@packageonlyannotations on the same declaration combine their package lists - all specified packages are allowed
Can Be Declared On
Types
// @packageonly routes
type Controller struct {
service *Service
}
Functions
// @packageonly myapp/internal/admin
func ExecuteAdminCommand(cmd string) error {
return nil
}
Methods
type Repository struct {
db *sql.DB
}
// @packageonly testing
func (r *Repository) ClearAll() error {
return nil
}
Error Codes
| Code | Description | Example |
|---|---|---|
| PKGO01 | PackageOnly type used outside allowed packages | var h Helper in unauthorized package |
| PKGO02 | PackageOnly function called outside allowed packages | ExecuteAdminCommand() in unauthorized package |
| PKGO03 | PackageOnly method called outside allowed packages | repo.ClearAll() in unauthorized package |
Examples
✅ Basic Usage
helpers/helper.go:
package helpers
// @packageonly services, handlers
type InternalHelper struct {
state map[string]interface{}
}
// @packageonly services
func ProcessInternal(data string) error {
return nil
}
services/service.go:
package services
import "myapp/helpers"
func BusinessLogic() {
h := helpers.InternalHelper{} // ✅ Allowed - services in list
helpers.ProcessInternal("data") // ✅ Allowed
}
main.go:
package main
import "myapp/helpers"
func main() {
// ❌ [PKGO01] type InternalHelper is marked @packageonly and can only be used in packages: [services, handlers, helpers]
h := helpers.InternalHelper{}
// ❌ [PKGO02] function ProcessInternal is marked @packageonly and can only be called in packages: [services, helpers]
helpers.ProcessInternal("data")
}
✅ Current Package Always Allowed
package auth
// @packageonly
type SessionManager struct {
sessions map[string]*Session
}
func NewSessionManager() *SessionManager {
return &SessionManager{ // ✅ Allowed - same package
sessions: make(map[string]*Session),
}
}
✅ Method-Level Restrictions
package repository
type UserRepository struct {
db *sql.DB
}
// Public method - no restrictions
func (r *UserRepository) GetUser(id int) (*User, error) {
return nil, nil
}
// @packageonly testing
func (r *UserRepository) InsertTestData(users []User) error {
return nil
}
Usage:
package handlers
import "myapp/repository"
func HandleRequest(repo *repository.UserRepository) {
user, _ := repo.GetUser(1) // ✅ Allowed - public method
// ❌ [PKGO03] method InsertTestData on UserRepository is marked @packageonly and can only be called in packages: [testing, repository]
repo.InsertTestData(nil)
}
✅ Using @ignore to Suppress
// @packageonly services
type InternalCache struct {
data map[string]string
}
func debugFunction() {
// @ignore PKGO01
cache := InternalCache{} // ✅ Suppressed
}
✅ Multiple Annotations (Merged)
Multiple @packageonly annotations on the same declaration are merged together:
package helpers
// @packageonly services
// @packageonly handlers, controllers
type SharedHelper struct {
data string
}
This is equivalent to:
// @packageonly services, handlers, controllers
type SharedHelper struct {
data string
}
All packages from all @packageonly annotations are combined into a single allowed list. This is useful when annotations are added incrementally or by different developers.
✅ Cross-Package Enforcement
Package external/lib:
package lib
// @packageonly myapp/internal/core
type InternalAPI struct {
token string
}
Package myapp/main:
package main
import "external/lib"
func main() {
// ❌ [PKGO01] type InternalAPI is marked @packageonly and can only be used in packages: [myapp/internal/core, lib]
api := lib.InternalAPI{}
}
Package myapp/internal/core:
package core
import "external/lib"
func CoreLogic() {
api := lib.InternalAPI{} // ✅ Allowed
}
Best Practices
Architectural Boundaries
// @packageonly routes
type HTTPController struct {
service *BusinessService
}
// @packageonly repository
type DatabaseConnection struct {
db *sql.DB
}
Administrative Functions
// @packageonly admin, internal/admin
func ResetAllData() error {
return nil
}
Deduplication Behavior
Type usage violations are deduplicated per file:
// @packageonly admin
type AdminHelper struct {}
func UnauthorizedCode() {
var h1 AdminHelper // ❌ ERROR: PKGO01
var h2 AdminHelper // ✅ No error - deduplicated
var h3 AdminHelper // ✅ No error - deduplicated
}
Function and method calls are NOT deduplicated - each call reports a separate error.
Limitations
Package Name Ambiguity
If multiple packages have the same name, use full paths:
// ❌ Ambiguous
// @packageonly util
// ✅ Clear
// @packageonly myapp/internal/util
Related Annotations
- @ignore: Suppress violations when needed
- @testonly: Simpler version that restricts to test files only