Documentation
¶
Index ¶
- func RegisterCreate[In Model](pattern string, mux *http.ServeMux, f func(context.Context, In) (int, error), ...)
- func RegisterDelete(pattern string, mux *http.ServeMux, f func(context.Context, int) error, ...)
- func RegisterGet[Out Model](pattern string, mux *http.ServeMux, ...)
- func RegisterGetAll[Out any](pattern string, mux *http.ServeMux, f func(context.Context) ([]Out, error), ...)
- func RegisterUpdate[In Model](pattern string, mux *http.ServeMux, ...)
- type DBQuerier
- type DefaultErrorHandler
- type ErrorHandler
- type Model
- type NullTime
- type Reflection
- type Repository
- func (r *Repository[M]) Create(ctx context.Context, model M) (int, error)
- func (r *Repository[M]) Delete(ctx context.Context, id int) error
- func (r *Repository[M]) Get(ctx context.Context, id int) (M, error)
- func (r *Repository[M]) GetAll(ctx context.Context) ([]M, error)
- func (r *Repository[M]) GetDB() *sql.DB
- func (r *Repository[M]) GetTable() string
- func (r *Repository[M]) Update(ctx context.Context, model M, id int) error
- func (r *Repository[M]) WithOnMutate(fn func(context.Context)) *Repository[M]
- func (r *Repository[M]) WithTransform() *Repository[M]
- func (r *Repository[M]) WithValidate() *Repository[M]
- type Transformatable
- type Validatable
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RegisterCreate ¶
func RegisterCreate[In Model](pattern string, mux *http.ServeMux, f func(context.Context, In) (int, error), eh ErrorHandler)
RegisterCreate registers an HTTP handler for creating resources.
The handler:
- Decodes JSON from the request body into the model type
- Calls the provided function (typically Repository.Create)
- Returns the created resource ID as JSON with 201 status
Repository hooks are executed during the create operation:
- Validation (if configured with WithValidate)
- Transformation (if configured with WithTransform)
- OnMutate callback (if configured with WithOnMutate)
Example:
repo := NewGenericRepository(db, "users", func() *User { return &User{} }).
WithValidate().
WithTransform().
WithOnMutate(cacheInvalidator)
mux := http.NewServeMux()
RegisterCreate("POST /users", mux, repo.Create, DefaultErrorHandler{})
func RegisterDelete ¶
func RegisterDelete(pattern string, mux *http.ServeMux, f func(context.Context, int) error, eh ErrorHandler)
RegisterDelete registers an HTTP handler for deleting a resource by ID.
The handler:
- Extracts the ID from the URL path parameter
- Calls the provided function (typically Repository.Delete)
- Returns 200 status on success
Repository hooks are executed during the delete operation:
- OnMutate callback (if configured with WithOnMutate)
Example:
repo := NewGenericRepository(db, "users", func() *User { return &User{} }).
WithOnMutate(cacheInvalidator)
mux := http.NewServeMux()
RegisterDelete("DELETE /users/{id}", mux, repo.Delete, DefaultErrorHandler{})
func RegisterGet ¶
func RegisterGet[Out Model](pattern string, mux *http.ServeMux, f func(ctx context.Context, id int) (Out, error), eh ErrorHandler)
RegisterGet registers an HTTP handler for retrieving a single resource by ID.
The handler:
- Extracts the ID from the URL path parameter
- Calls the provided function (typically Repository.Get)
- Returns the resource as JSON with 200 status
Example:
repo := NewGenericRepository(db, "users", func() *User { return &User{} })
mux := http.NewServeMux()
RegisterGet("GET /users/{id}", mux, repo.Get, DefaultErrorHandler{})
func RegisterGetAll ¶
func RegisterGetAll[Out any](pattern string, mux *http.ServeMux, f func(context.Context) ([]Out, error), eh ErrorHandler)
RegisterGetAll registers an HTTP handler for retrieving all resources.
The handler:
- Calls the provided function (typically Repository.GetAll)
- Returns the resources as a JSON array with 200 status
Example:
repo := NewGenericRepository(db, "users", func() *User { return &User{} })
mux := http.NewServeMux()
RegisterGetAll("GET /users", mux, repo.GetAll, DefaultErrorHandler{})
func RegisterUpdate ¶
func RegisterUpdate[In Model](pattern string, mux *http.ServeMux, f func(ctx context.Context, in In, id int) error, eh ErrorHandler)
RegisterUpdate registers an HTTP handler for updating a resource.
The handler:
- Decodes JSON from the request body into the model type
- Extracts the ID from the URL path parameter
- Calls the provided function (typically Repository.Update)
- Returns 200 status on success
Repository hooks are executed during the update operation:
- Validation (if configured with WithValidate)
- Transformation (if configured with WithTransform)
- OnMutate callback (if configured with WithOnMutate)
Example:
repo := NewGenericRepository(db, "users", func() *User { return &User{} }).
WithValidate().
WithTransform().
WithOnMutate(cacheInvalidator)
mux := http.NewServeMux()
RegisterUpdate("POST /users/{id}", mux, repo.Update, DefaultErrorHandler{})
Types ¶
type DBQuerier ¶ added in v1.3.2
type DBQuerier interface {
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
DBQuerier is an interface for database query operations. It provides a subset of *sql.DB functionality to enable easier testing and mocking in validation logic.
type DefaultErrorHandler ¶ added in v1.3.4
type DefaultErrorHandler struct{}
DefaultErrorHandler is the default implementation of ErrorHandler. It writes errors as plain text HTTP responses.
func (DefaultErrorHandler) WriteError ¶ added in v1.3.4
func (d DefaultErrorHandler) WriteError(w http.ResponseWriter, _ *http.Request, err error, customMsg string, statusCode int)
WriteError writes an error response to the HTTP response writer. If customMsg is provided, it's used instead of the error message.
type ErrorHandler ¶ added in v1.3.4
type ErrorHandler interface {
WriteError(w http.ResponseWriter, r *http.Request, err error, customMsg string, statusCode int)
}
ErrorHandler is an interface for handling HTTP errors in a consistent way. Implement this interface to customize error responses across all CRUD endpoints.
type Model ¶
Model is the base interface that all repository models must implement. It provides reflection capabilities to convert structs to maps for database operations.
type NullTime ¶ added in v1.3.6
NullTime represents a time.Time that can scan from both native time types (PostgreSQL) and string representations (SQLite).
type Reflection ¶
type Reflection struct{}
func (*Reflection) StructToMap ¶
func (r *Reflection) StructToMap(d interface{}) map[string]any
type Repository ¶
type Repository[M Model] struct { // contains filtered or unexported fields }
Repository is a generic CRUD repository for database operations.
Thread Safety: The repository uses a mutex to ensure thread-safe Create, Update, Delete, Get, and GetAll operations. This prevents race conditions when multiple goroutines access the same repository instance. However, this may become a bottleneck under very high concurrency. Consider using separate repository instances per request if needed.
Hook Execution Order: For Create and Update operations, hooks execute in this order:
- Validate (if enabled via WithValidate)
- Transform (if enabled via WithTransform)
- Database operation (INSERT/UPDATE)
- OnMutate callback (if configured via WithOnMutate)
For Delete operations:
- Database operation (DELETE)
- OnMutate callback (if configured)
func NewGenericRepository ¶
func NewGenericRepository[M Model](db *sql.DB, table string, callback func() M) *Repository[M]
NewGenericRepository creates a new generic CRUD repository.
Parameters:
- db: The database connection
- table: The name of the database table
- callback: A factory function that returns a new instance of the model type
The repository can be configured with optional hooks using the builder pattern:
repo := NewGenericRepository(db, "users", func() *User { return &User{} }).
WithValidate().
WithTransform().
WithOnMutate(cacheInvalidator)
By default, the repository:
- Does NOT validate models (use WithValidate to enable)
- Does NOT transform models (use WithTransform to enable)
- Does NOT call mutation callbacks (use WithOnMutate to register)
- DOES automatically set created_at and updated_at timestamps if fields exist
func (*Repository[M]) Create ¶
func (r *Repository[M]) Create(ctx context.Context, model M) (int, error)
func (*Repository[M]) GetDB ¶ added in v1.3.4
func (r *Repository[M]) GetDB() *sql.DB
func (*Repository[M]) GetTable ¶
func (r *Repository[M]) GetTable() string
func (*Repository[M]) Update ¶
func (r *Repository[M]) Update(ctx context.Context, model M, id int) error
func (*Repository[M]) WithOnMutate ¶ added in v1.4.0
func (r *Repository[M]) WithOnMutate(fn func(context.Context)) *Repository[M]
WithOnMutate registers a callback function that will be called after successful Create, Update, and Delete operations. This is useful for side effects like cache invalidation, event publishing, or audit logging.
The callback is called after the database operation completes successfully. It does not return an error - if the callback fails, it won't affect the database operation (best-effort execution).
Common use cases:
- Cache invalidation
- Publishing domain events
- Updating search indices
- Audit logging
Example:
cache := make(map[int]*User)
repo := NewGenericRepository(db, "users", func() *User { return &User{} }).
WithOnMutate(func(ctx context.Context) {
// Clear cache on any mutation
for k := range cache {
delete(cache, k)
}
})
func (*Repository[M]) WithTransform ¶ added in v1.4.0
func (r *Repository[M]) WithTransform() *Repository[M]
WithTransform enables transformation for Create and Update operations. When enabled, the repository will call the model's transform method (if it implements Transformatable) after validation but before persisting to the database.
This allows you to normalize or enrich data before it's stored.
Example:
repo := NewGenericRepository(db, "users", func() *User { return &User{} }).
WithTransform()
func (*Repository[M]) WithValidate ¶ added in v1.4.0
func (r *Repository[M]) WithValidate() *Repository[M]
WithValidate enables validation for Create and Update operations. When enabled, the repository will call the model's validate method (if it implements Validatable) before persisting data to the database.
If validation fails, the operation is aborted and the validation error is returned.
Example:
repo := NewGenericRepository(db, "users", func() *User { return &User{} }).
WithValidate()
type Transformatable ¶ added in v1.4.0
Transformatable is an interface that models can implement to transform data before it is persisted to the database. The transform method is called after validation but before the database operation when the repository is configured with WithTransform().
The method must return a Model that satisfies the same type M as the repository. Transformations are applied to both Create and Update operations.
Common use cases:
- Normalizing data (e.g., lowercasing email addresses)
- Enriching data (e.g., setting default values)
- Sanitizing input (e.g., trimming whitespace)
Security note: Be cautious when implementing transform. Ensure that transformations don't bypass intended security restrictions or modify sensitive fields unexpectedly.
Example:
func (u *User) transform(ctx context.Context) (Model, error) {
u.Email = strings.ToLower(strings.TrimSpace(u.Email))
u.Name = strings.TrimSpace(u.Name)
return u, nil
}
type Validatable ¶ added in v1.3.0
Validatable is an interface that models can implement to provide custom validation logic. The validate method is called automatically before Create and Update operations when the repository is configured with WithValidate().
The method receives a DBQuerier interface (compatible with *sql.DB) to allow database queries for validation (e.g., checking uniqueness constraints).
Example:
func (u *User) validate(ctx context.Context, db DBQuerier) error {
if u.Email == "" {
return errors.New("email is required")
}
// Check uniqueness
var exists bool
row := db.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM users WHERE email = ?)", u.Email)
if err := row.Scan(&exists); err != nil {
return err
}
if exists {
return errors.New("email already exists")
}
return nil
}