From d45949648d1ee3493918bcf84397a65ce0c00c34 Mon Sep 17 00:00:00 2001 From: Jacob Brewer Date: Tue, 3 Dec 2024 11:15:18 +0000 Subject: [PATCH] fix(generate): Adding helpers to be generated when generating templates (#53) * Adding helper templates to be rendered * Updating examples --- example/models/helpers.go | 122 ------------------ example/models/metrics.go | 15 --- helpers/db.go | 42 ------ helpers/errors.go | 12 -- pkg/generation/templates.go | 94 +++++++++++++- example/models/db.go => templates/db.tmpl | 8 +- .../models/errors.go => templates/errors.tmpl | 7 +- helpers/helpers.go => templates/helpers.tmpl | 5 +- helpers/metrics.go => templates/metrics.tmpl | 10 +- 9 files changed, 111 insertions(+), 204 deletions(-) delete mode 100644 example/models/helpers.go delete mode 100644 example/models/metrics.go delete mode 100644 helpers/db.go delete mode 100644 helpers/errors.go rename example/models/db.go => templates/db.tmpl (87%) rename example/models/errors.go => templates/errors.tmpl (59%) rename helpers/helpers.go => templates/helpers.tmpl (95%) rename helpers/metrics.go => templates/metrics.tmpl (60%) diff --git a/example/models/helpers.go b/example/models/helpers.go deleted file mode 100644 index 69bc5f6..0000000 --- a/example/models/helpers.go +++ /dev/null @@ -1,122 +0,0 @@ -package models - -import ( - "fmt" - "log/slog" - "reflect" -) - -// Saveable is the interface implemented by types which can save themselves to the database. -type Saveable interface { - Save(db DB) error -} - -// PreSaveable is the interface implemented by types which run a pre save step. -type PreSaveable interface { - PreSave(db DB) error -} - -// PostSaveable is the interface implemented by types which run a post save step. -type PostSaveable interface { - PostSave() error -} - -// SetLogger is the interface implemented by types which have the ability to configure their log entry. -type SetLogger interface { - SetLog(l *slog.Logger) -} - -// Deletable is the interface implemented by types which can delete themselves from the database. -type Deletable interface { - Delete(db DB) error -} - -// PreDeletable is the interface implemented by types which run a pre delete step. -type PreDeletable interface { - PreDelete() error -} - -// PostDeletable is the interface implemented by types which run a post delete step. -type PostDeletable interface { - PostDelete() error -} - -// TransactionFunc is a function to be called within a transaction. -type TransactionFunc func(db DB) error - -type TransactionHandler interface { - Handle(TransactionFunc) error -} - -// DBTransactionHandler handles a transaction that will return any error. -type DBTransactionHandler struct { - db Transactioner -} - -// Handle implements the TransactionHandler interface. -func (th *DBTransactionHandler) Handle(f TransactionFunc) error { - tx, err := th.db.Beginx() - if err != nil { - return fmt.Errorf("begin tx: %w", err) - } - - if err := f(tx); err != nil { - if err2 := tx.Rollback(); err2 != nil { - return fmt.Errorf("%s: %w", err, err2) - } - return fmt.Errorf("action: %w", err) - } - - if err := tx.Commit(); err != nil { - return fmt.Errorf("commit tx: %w", err) - } - - return nil -} - -// NewDBTransactionHandler returns a configured instance of DBTransactionHandler -func NewDBTransactionHandler(db Transactioner) *DBTransactionHandler { - return &DBTransactionHandler{db: db} -} - -// LoggableDBTransactionHandler handles a transaction and logs any error. -type LoggableDBTransactionHandler struct { - db Transactioner - l *slog.Logger -} - -// NewLoggableDBTransactionHandler returns a configured instance of LoggableDBTransactionHandler -func NewLoggableDBTransactionHandler(db Transactioner, l *slog.Logger) *LoggableDBTransactionHandler { - return &LoggableDBTransactionHandler{db: db, l: l} -} - -// IsKeySet returns true if -// 1. x is an integer and greater than zero. -// 2. x not an integer and is not the zero value. -// Otherwise, returns false -func IsKeySet(x interface{}) bool { - switch x := x.(type) { - case int: - return x > 0 - case int8: - return x > 0 - case int16: - return x > 0 - case int32: - return x > 0 - case int64: - return x > 0 - case uint: - return x > 0 - case uint8: - return x > 0 - case uint16: - return x > 0 - case uint32: - return x > 0 - case uint64: - return x > 0 - } - - return x != reflect.Zero(reflect.TypeOf(x)).Interface() -} diff --git a/example/models/metrics.go b/example/models/metrics.go deleted file mode 100644 index fd2a804..0000000 --- a/example/models/metrics.go +++ /dev/null @@ -1,15 +0,0 @@ -package models - -import ( - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" -) - -// DatabaseLatency is the duration of database queries. -var DatabaseLatency = promauto.NewHistogramVec( - prometheus.HistogramOpts{ - Name: "database_latency", - Help: "Duration of database queries", - }, - []string{"query"}, -) diff --git a/helpers/db.go b/helpers/db.go deleted file mode 100644 index b41cd4e..0000000 --- a/helpers/db.go +++ /dev/null @@ -1,42 +0,0 @@ -package helpers - -import ( - "database/sql" - - "github.com/jmoiron/sqlx" -) - -// DBTransactioner is the interface that database connections that can utilise -// transactions should implement. -type DBTransactioner interface { - DB - Transactioner -} - -// DB is the common interface for database operations -// -// This should work with database/sql.DB and database/sql.Tx. -type DB interface { - Exec(string, ...any) (sql.Result, error) - Query(string, ...any) (*sql.Rows, error) - QueryRow(string, ...any) *sql.Row - Get(dest any, query string, args ...interface{}) error - Select(dest any, query string, args ...interface{}) error -} - -// Transactioner is the interface that a database connection that can start -// a transaction should implement. -type Transactioner interface { - Beginx() (*sqlx.Tx, error) -} - -// XODB is a compat alias to DB -type XODB = DB - -// DBLog provides the log func used by generated queries. -var DBLog = func(string, ...any) {} - -// XOLog is a compat shim for DBLog -var XOLog = func(msg string, args ...any) { - DBLog(msg, args...) -} diff --git a/helpers/errors.go b/helpers/errors.go deleted file mode 100644 index 3102fcb..0000000 --- a/helpers/errors.go +++ /dev/null @@ -1,12 +0,0 @@ -package helpers - -import "errors" - -// ErrNoAffectedRows is returned if a model update affected no rows -var ErrNoAffectedRows = errors.New("no affected rows") - -// ErrDuplicate is returned if a duplicate entry is found -var ErrDuplicate = errors.New("duplicate entry") - -// ErrConstraintViolation is returned if a constraint is violated -var ErrConstraintViolation = errors.New("constraint violation") diff --git a/pkg/generation/templates.go b/pkg/generation/templates.go index 3756e20..f018838 100644 --- a/pkg/generation/templates.go +++ b/pkg/generation/templates.go @@ -7,6 +7,8 @@ import ( "os" "os/exec" "path/filepath" + "strings" + "sync" "text/template" "github.com/Masterminds/sprig" @@ -53,6 +55,91 @@ func RenderWithTemplates(fs embed.FS, tables []*models.Table, outputLoc string, } } + if err := renderHelpers(fs, outputLoc, fileExtensionPrefix); err != nil { + return fmt.Errorf("error rendering helpers: %w", err) + } + + return nil +} + +func renderHelpers(fs embed.FS, outputLoc string, fileExtensionPrefix string) error { + wg := new(sync.WaitGroup) + errs := new(sync.Map) + + wg.Add(1) + go func() { + defer wg.Done() + tmpl, err := template.New("db.tmpl").Funcs(sprig.TxtFuncMap()).Funcs(Helpers).ParseFS(fs, "templates/db.tmpl") + if err != nil { + errs.Store("error parsing db template", err) + return + } + + if err := generate(&templateInfo{ + OutputDir: outputLoc, + }, tmpl, outputLoc, fileExtensionPrefix); err != nil { + errs.Store("error generating db template", err) + } + }() + + wg.Add(1) + go func() { + defer wg.Done() + tmpl, err := template.New("errors.tmpl").Funcs(sprig.TxtFuncMap()).Funcs(Helpers).ParseFS(fs, "templates/errors.tmpl") + if err != nil { + errs.Store("error parsing errors template", err) + return + } + + if err := generate(&templateInfo{ + OutputDir: outputLoc, + }, tmpl, outputLoc, fileExtensionPrefix); err != nil { + errs.Store("error generating helpers template", err) + } + }() + + wg.Add(1) + go func() { + defer wg.Done() + tmpl, err := template.New("helpers.tmpl").Funcs(sprig.TxtFuncMap()).Funcs(Helpers).ParseFS(fs, "templates/helpers.tmpl") + if err != nil { + errs.Store("error parsing helpers template", err) + return + } + + if err := generate(&templateInfo{ + OutputDir: outputLoc, + }, tmpl, outputLoc, fileExtensionPrefix); err != nil { + errs.Store("error generating helpers template", err) + } + }() + + wg.Add(1) + go func() { + defer wg.Done() + tmpl, err := template.New("metrics.tmpl").Funcs(sprig.TxtFuncMap()).Funcs(Helpers).ParseFS(fs, "templates/metrics.tmpl") + if err != nil { + errs.Store("error parsing metrics template", err) + return + } + + if err := generate(&templateInfo{ + OutputDir: outputLoc, + }, tmpl, outputLoc, fileExtensionPrefix); err != nil { + errs.Store("error generating metrics template", err) + } + }() + + wg.Wait() + + errs.Range(func(key, value interface{}) bool { + if value != nil { + slog.Error(key.(string), slog.String("error", value.(error).Error())) + } + + return true + }) + return nil } @@ -66,7 +153,12 @@ func generate(t *templateInfo, tmpl *template.Template, outputLoc string, fileEx ext = fileExtensionPrefix + ext } - fn := filepath.Join(outputLoc, xstrings.ToSnakeCase(t.Table.Name)+ext) + filename := strings.ReplaceAll(tmpl.Name(), ".tmpl", "") + if t.Table != nil { + filename = xstrings.ToSnakeCase(t.Table.Name) + } + + fn := filepath.Join(outputLoc, filename+ext) if err := os.MkdirAll(filepath.Dir(fn), 0750); err != nil { return err } diff --git a/example/models/db.go b/templates/db.tmpl similarity index 87% rename from example/models/db.go rename to templates/db.tmpl index 0c31824..f6cf21a 100644 --- a/example/models/db.go +++ b/templates/db.tmpl @@ -1,4 +1,7 @@ -package models +// Package models contains the database interaction model code +// +// GENERATED BY GOSCHEMA. DO NOT EDIT. +package {{ .OutputDir | base | snakecase }} import ( "database/sql" @@ -20,7 +23,6 @@ type DB interface { Exec(string, ...any) (sql.Result, error) Query(string, ...any) (*sql.Rows, error) QueryRow(string, ...any) *sql.Row - // Additional sqlx methods we like Get(dest any, query string, args ...interface{}) error Select(dest any, query string, args ...interface{}) error } @@ -40,4 +42,4 @@ var DBLog = func(string, ...any) {} // XOLog is a compat shim for DBLog var XOLog = func(msg string, args ...any) { DBLog(msg, args...) -} +} \ No newline at end of file diff --git a/example/models/errors.go b/templates/errors.tmpl similarity index 59% rename from example/models/errors.go rename to templates/errors.tmpl index 979ad5b..692e2f6 100644 --- a/example/models/errors.go +++ b/templates/errors.tmpl @@ -1,4 +1,7 @@ -package models +// Package models contains the database interaction model code +// +// GENERATED BY GOSCHEMA. DO NOT EDIT. +package {{ .OutputDir | base | snakecase }} import "errors" @@ -9,4 +12,4 @@ var ErrNoAffectedRows = errors.New("no affected rows") var ErrDuplicate = errors.New("duplicate entry") // ErrConstraintViolation is returned if a constraint is violated -var ErrConstraintViolation = errors.New("constraint violation") +var ErrConstraintViolation = errors.New("constraint violation") \ No newline at end of file diff --git a/helpers/helpers.go b/templates/helpers.tmpl similarity index 95% rename from helpers/helpers.go rename to templates/helpers.tmpl index cc87f39..eb2ff00 100644 --- a/helpers/helpers.go +++ b/templates/helpers.tmpl @@ -1,4 +1,7 @@ -package helpers +// Package models contains the database interaction model code +// +// GENERATED BY GOSCHEMA. DO NOT EDIT. +package {{ .OutputDir | base | snakecase }} import ( "fmt" diff --git a/helpers/metrics.go b/templates/metrics.tmpl similarity index 60% rename from helpers/metrics.go rename to templates/metrics.tmpl index 45cf3bb..a0677f0 100644 --- a/helpers/metrics.go +++ b/templates/metrics.tmpl @@ -1,9 +1,7 @@ -package helpers - -import ( - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" -) +// Package models contains the database interaction model code +// +// GENERATED BY GOSCHEMA. DO NOT EDIT. +package {{ .OutputDir | base | snakecase }} // DatabaseLatency is the duration of database queries. var DatabaseLatency = promauto.NewHistogramVec(