From 01ada77f1d2ac88c0a5c6c275312b89eae6304fd Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Wed, 22 Oct 2025 19:02:10 -0700 Subject: [PATCH 1/9] refactor to consolidated error constants --- cfg/config.go | 8 ++------ errors/errors.go | 33 +++++++++++++++++++++++++++++++++ output/context.go | 14 +++++--------- output/invoke.go | 21 +++++---------------- runtime/funcs.go | 11 ++--------- welder/weld.go | 15 ++++----------- 6 files changed, 51 insertions(+), 51 deletions(-) create mode 100644 errors/errors.go diff --git a/cfg/config.go b/cfg/config.go index bbe378e..8d60ebc 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -4,11 +4,11 @@ import ( "fmt" "sort" + "github.com/gofoji/foji/errors" "github.com/gofoji/foji/stringlist" ) type ( - Error string FileHandler func(string) error // Used for post generation processing ParamMap map[string]any // Generic bucket of params passed to templates Processes map[string]Process @@ -100,7 +100,7 @@ func (pp Processes) String() string { } const ( - errProcess = Error("Process") + errProcess = errors.Error("Process") missingBundleFormat = "%w '%s' referenced by bundle `%s` not found. Possible options: %s" missingFormat = "%w '%s' not found. Possible options: %s" ) @@ -176,7 +176,3 @@ func (o Output) All() stringlist.StringMap { func (f FileInput) IsEmpty() bool { return len(f.Files) == 0 } - -func (e Error) Error() string { - return string(e) -} diff --git a/errors/errors.go b/errors/errors.go new file mode 100644 index 0000000..38adca9 --- /dev/null +++ b/errors/errors.go @@ -0,0 +1,33 @@ +// Package errors provides unified error types and constants used throughout foji. +package errors + +// Error is a simple error string type used for custom error definitions. +type Error string + +// Error implements the error interface for Error type. +func (e Error) Error() string { + return string(e) +} + +const ( + // ErrRuntime indicates an error occurred in template runtime functions. + ErrRuntime = Error("runtime") + + // ErrWeld indicates an error occurred during the welding process. + ErrWeld = Error("welding error") + + // ErrMissingRequirement indicates a required condition was not met. + ErrMissingRequirement = Error("requires") + + // ErrNotNeeded indicates the output generation should be skipped. + ErrNotNeeded = Error("not needed") + + // ErrPermExists indicates a permanent file (prefixed with !) already exists. + ErrPermExists = Error("file exists") + + // ErrInvalidDictParams indicates invalid parameters in WithParams call. + ErrInvalidDictParams = Error("invalid dict params in call to WithParams, must be key and value pairs") + + // ErrInvalidDictKey indicates an invalid dictionary key in WithParams call. + ErrInvalidDictKey = Error("invalid dict params in call to WithParams, must be key and value pairs") +) diff --git a/output/context.go b/output/context.go index fc53ed0..ff98b09 100644 --- a/output/context.go +++ b/output/context.go @@ -8,6 +8,7 @@ import ( "github.com/rs/zerolog" "github.com/gofoji/foji/cfg" + fojiErrors "github.com/gofoji/foji/errors" "github.com/gofoji/foji/runtime" "github.com/gofoji/foji/stringlist" ) @@ -39,7 +40,7 @@ func (c *Context) Aborted() error { // NotNeededIf given bool is true the execution is aborted, and can be used to prevent generation of a file. func (c *Context) NotNeededIf(t bool, reason string) (string, error) { if t { - c.AbortError = fmt.Errorf("%w: %s", ErrNotNeeded, reason) + c.AbortError = fmt.Errorf("%w: %s", fojiErrors.ErrNotNeeded, reason) return "", c.AbortError } @@ -50,7 +51,7 @@ func (c *Context) NotNeededIf(t bool, reason string) (string, error) { // ErrorIf if given bool is true the execution is fatally aborted, and stops processing. func (c *Context) ErrorIf(t bool, reason string) (string, error) { if t { - c.AbortError = fmt.Errorf("%w: %s", ErrMissingRequirement, reason) + c.AbortError = fmt.Errorf("%w: %s", fojiErrors.ErrMissingRequirement, reason) return "", c.AbortError } @@ -58,16 +59,11 @@ func (c *Context) ErrorIf(t bool, reason string) (string, error) { return "", nil } -const ( - ErrInvalidDictParams = Error("invalid dict params in call to WithParams, must be key and value pairs") - ErrInvalidDictKey = Error("invalid dict params in call to WithParams, must be key and value pairs") -) - // WithParams Clones the current context and adds runtime params for each pair of key, value provided. // Used for executing sub templates that still need access to the context. func (c *Context) WithParams(values ...any) (*Context, error) { if len(values)%2 != 0 { - return nil, ErrInvalidDictParams + return nil, fojiErrors.ErrInvalidDictParams } out := *c @@ -76,7 +72,7 @@ func (c *Context) WithParams(values ...any) (*Context, error) { for i := 0; i < len(values); i += 2 { key, ok := values[i].(string) if !ok { - return nil, ErrInvalidDictKey + return nil, fojiErrors.ErrInvalidDictKey } out.RuntimeParams[key] = values[i+1] diff --git a/output/invoke.go b/output/invoke.go index ecfa4f0..ab7f062 100644 --- a/output/invoke.go +++ b/output/invoke.go @@ -12,23 +12,12 @@ import ( "github.com/rs/zerolog" "github.com/gofoji/foji/cfg" + fojiErrors "github.com/gofoji/foji/errors" "github.com/gofoji/foji/foji" "github.com/gofoji/foji/runtime" "github.com/gofoji/foji/stringlist" ) -type Error string - -func (e Error) Error() string { - return string(e) -} - -const ( - ErrMissingRequirement = Error("requires") - ErrNotNeeded = Error("not needed") - ErrPermExists = Error("file exists") -) - type FuncMapper interface { Funcs() plates.FuncMap } @@ -57,7 +46,7 @@ func (p ProcessRunner) process(tm stringlist.StringMap, data any) error { err = p.template(targetFile, templateFile, data) if err != nil { - if !errors.Is(err, ErrPermExists) { + if !errors.Is(err, fojiErrors.ErrPermExists) { return err } @@ -144,7 +133,7 @@ func (p ProcessRunner) template(outputFile, templateFile string, data any) error outputFile = p.dir + outputFile if permFile && fileExists(outputFile) { - return ErrPermExists + return fojiErrors.ErrPermExists } if p.simulate { @@ -158,13 +147,13 @@ func (p ProcessRunner) template(outputFile, templateFile string, data any) error err = p.FromFile(templateFile).ToFile(outputFile, data) if err != nil { - if errors.Is(err, ErrNotNeeded) { + if errors.Is(err, fojiErrors.ErrNotNeeded) { l.Info().Err(err).Msg("skipped") return nil } - if errors.Is(err, ErrMissingRequirement) { + if errors.Is(err, fojiErrors.ErrMissingRequirement) { return err //nolint:wrapcheck } diff --git a/runtime/funcs.go b/runtime/funcs.go index b4d59f2..d44e79e 100644 --- a/runtime/funcs.go +++ b/runtime/funcs.go @@ -14,17 +14,10 @@ import ( "github.com/gofoji/foji/cfg" "github.com/gofoji/foji/color" + "github.com/gofoji/foji/errors" "github.com/gofoji/foji/stringlist" ) -type Error string - -func (e Error) Error() string { - return string(e) -} - -var ErrRuntime = Error("runtime") - var Funcs = map[string]any{ // Case "camel": kace.Camel, @@ -250,7 +243,7 @@ func In(needle any, haystack ...any) (bool, error) { return false, nil default: - return false, fmt.Errorf("%w: must be iterable type, found type %s", ErrRuntime, tp) + return false, fmt.Errorf("%w: must be iterable type, found type %s", errors.ErrRuntime, tp) } } diff --git a/welder/weld.go b/welder/weld.go index 531b9d4..f05e711 100644 --- a/welder/weld.go +++ b/welder/weld.go @@ -9,6 +9,7 @@ import ( "github.com/rs/zerolog" "github.com/gofoji/foji/cfg" + "github.com/gofoji/foji/errors" "github.com/gofoji/foji/input" "github.com/gofoji/foji/input/db" "github.com/gofoji/foji/input/db/pg" @@ -40,14 +41,6 @@ type Processor struct { run func(simulate bool, p cfg.Process, ff []input.FileGroup) error } -type Error string - -func (e Error) Error() string { - return string(e) -} - -const ErrWeld = Error("welding error") - // New creates a new welder. func New(logger zerolog.Logger, config cfg.Config, targets []cfg.Process) *Welder { w := Welder{ @@ -152,7 +145,7 @@ func (w *Welder) getResource(resource string) (input.FileGroup, error) { in, ok := w.config.Files[resource] if !ok { - return r.Loaded, fmt.Errorf("%w: invalid resource reference: %s", ErrWeld, resource) + return r.Loaded, fmt.Errorf("%w: invalid resource reference: %s", errors.ErrWeld, resource) } f, err := input.Parse(w.ctx, w.logger, in) @@ -197,7 +190,7 @@ func (w *Welder) getProcessFiles(p cfg.Process) ([]input.FileGroup, error) { func (w *Welder) parseDB() (db.DB, error) { if w.conn == nil { - return nil, fmt.Errorf("%w: db not initialized", ErrWeld) + return nil, fmt.Errorf("%w: db not initialized", errors.ErrWeld) } repo := pg.New(w.conn, w.logger) @@ -216,7 +209,7 @@ func (w *Welder) initDBConnection() error { } if w.config.DB.Connection == "" { - return fmt.Errorf("%w: missing db.connection", ErrWeld) + return fmt.Errorf("%w: missing db.connection", errors.ErrWeld) } w.logger.Debug().Str("Connection", w.config.DB.Connection).Msg("Loading Database") From a8aac5d61b352269f5dc0567f0724da5309e7af8 Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Wed, 22 Oct 2025 19:14:25 -0700 Subject: [PATCH 2/9] bubble up errors instead of panic --- cfg/version.go | 2 +- input/openapi/parse.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cfg/version.go b/cfg/version.go index 0c025c8..b2362ee 100644 --- a/cfg/version.go +++ b/cfg/version.go @@ -8,7 +8,7 @@ import ( func Version() string { info, ok := debug.ReadBuildInfo() if !ok { - panic("Failed to read build info") + return "(dev build)" } if info.Main.Version != "" { diff --git a/input/openapi/parse.go b/input/openapi/parse.go index 09565c9..f0e3ec9 100644 --- a/input/openapi/parse.go +++ b/input/openapi/parse.go @@ -32,7 +32,7 @@ func Parse(_ context.Context, logger zerolog.Logger, inGroups []input.FileGroup) swagger, err := loader.LoadFromData(f.Content) if err != nil { - panic(err) + return nil, err } d := File{Input: f, API: swagger} From daa295947e529f39307a365b5ed2d62f76b1087a Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Wed, 22 Oct 2025 19:39:12 -0700 Subject: [PATCH 3/9] move silent CSV error to template abort --- output/openapi.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/output/openapi.go b/output/openapi.go index 1f6fe87..a147504 100644 --- a/output/openapi.go +++ b/output/openapi.go @@ -562,7 +562,8 @@ func (o *OpenAPIFileContext) DefaultValues(val string) []string { records, err := csvReader.ReadAll() if err != nil { - o.Logger.Err(err).Str("val", val).Msg("error reading csv for default") + o.AbortError = fmt.Errorf("error reading csv for default: %w: %q", err, val) + return nil } if len(records) > 0 { From ee2c2c87eee8823d9ba7cf26c8a05a129af4c985 Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Wed, 22 Oct 2025 20:06:53 -0700 Subject: [PATCH 4/9] minor cleanup for type resolver and extension checker --- output/db.go | 24 +----------------------- output/mapper.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ output/openapi.go | 22 ++++++++++------------ output/sql.go | 29 +---------------------------- 4 files changed, 59 insertions(+), 63 deletions(-) create mode 100644 output/mapper.go diff --git a/output/db.go b/output/db.go index c866e0e..7df18e8 100644 --- a/output/db.go +++ b/output/db.go @@ -139,29 +139,7 @@ func (s *SchemasContext) Parameterize(cc db.Columns, format, pkg string) string } func (s SchemasContext) GetType(c *db.Column, pkg string) string { - pp := strings.Split(c.Path(), ".") - for i := range pp { - p := strings.Join(pp[i:], ".") - - t, ok := s.Maps.Type["."+p] - if ok { - return s.CheckPackage(t, pkg) - } - } - - if c.Nullable { - t, ok := s.Maps.Nullable[c.Type] - if ok { - return s.CheckPackage(t, pkg) - } - } - - t, ok := s.Maps.Type[c.Type] - if ok { - return s.CheckPackage(t, pkg) - } - - return fmt.Sprintf("UNKNOWN:path(%s):type(%s)", c.Path(), c.Type) + return ResolveType(s.Maps, func(t string) string { return s.CheckPackage(t, pkg) }, c.Type, c.Nullable, c.Path()) } const ValidTypeElems = 2 diff --git a/output/mapper.go b/output/mapper.go new file mode 100644 index 0000000..b03b387 --- /dev/null +++ b/output/mapper.go @@ -0,0 +1,47 @@ +package output + +import ( + "fmt" + "strings" + + "github.com/gofoji/foji/cfg" +) + +// ResolveType resolves a type to a Go type using the configured type maps. +// It checks in the following order: +// 1. Path-based type map (e.g., ".table.column" -> "string") +// 2. Nullable type map (if column/param is nullable) +// 3. Generic type map (e.g., "varchar" -> "string") +// Returns an UNKNOWN placeholder if no mapping is found. +func ResolveType(maps cfg.Maps, checkFunc func(string) string, columnType string, nullable bool, path string) string { + // Check path-based mappings first + pp := strings.Split(path, ".") + for i := range pp { + p := strings.Join(pp[i:], ".") + t, ok := maps.Type["."+p] + if ok { + return checkFunc(t) + } + } + + // Check nullable type mapping + if nullable { + t, ok := maps.Nullable[columnType] + if ok { + return checkFunc(t) + } + } + + // Check standard type mapping + t, ok := maps.Type[columnType] + if ok { + return checkFunc(t) + } + + // Check for qualified names (containing . or /) + if strings.ContainsAny(columnType, "./") { + return checkFunc(columnType) + } + + return fmt.Sprintf("UNKNOWN:path(%s):type(%s)", path, columnType) +} diff --git a/output/openapi.go b/output/openapi.go index a147504..c0c6bc0 100644 --- a/output/openapi.go +++ b/output/openapi.go @@ -117,8 +117,11 @@ func (o *OpenAPIFileContext) getXGoType(currentPackage string, goType any) strin return fmt.Sprintf("INVALID x-go-type: %v", goType) } -func (o *OpenAPIFileContext) OpHasExtension(op *openapi3.Operation, ext string) bool { - v, ok := op.Extensions[ext] +// HasExtensionValue checks if an extension exists and has a truthy value. +// For boolean extensions, it returns the boolean value. +// For other extensions, it returns true if they exist. +func HasExtensionValue(extensions map[string]interface{}, ext string) bool { + v, ok := extensions[ext] if !ok { return false } @@ -130,17 +133,12 @@ func (o *OpenAPIFileContext) OpHasExtension(op *openapi3.Operation, ext string) return true } -func (o *OpenAPIFileContext) SecurityHasExtension(scheme *openapi3.SecuritySchemeRef, ext string) bool { - v, ok := scheme.Value.Extensions[ext] - if !ok { - return false - } - - if b, isBool := v.(bool); isBool { - return b - } +func (o *OpenAPIFileContext) OpHasExtension(op *openapi3.Operation, ext string) bool { + return HasExtensionValue(op.Extensions, ext) +} - return true +func (o *OpenAPIFileContext) SecurityHasExtension(scheme *openapi3.SecuritySchemeRef, ext string) bool { + return HasExtensionValue(scheme.Value.Extensions, ext) } func (o *OpenAPIFileContext) HasExtension(s *openapi3.SchemaRef, ext string) bool { diff --git a/output/sql.go b/output/sql.go index a9c5dd7..eac415f 100644 --- a/output/sql.go +++ b/output/sql.go @@ -115,34 +115,7 @@ func (q SQLContext) GetType(c *sql.Param, pkg string) string { return c.Type } - pp := strings.Split(c.Path(), ".") - for i := range pp { - p := strings.Join(pp[i:], ".") - - t, ok := q.Maps.Type["."+p] - if ok { - return q.CheckPackage(t, pkg) - } - } - - if c.Nullable { - t, ok := q.Maps.Nullable[c.Type] - if ok { - return q.CheckPackage(t, pkg) - } - } - - t, ok := q.Maps.Type[c.Type] - if ok { - return q.CheckPackage(t, pkg) - } - - if strings.ContainsAny(c.Type, "./") { - // Qualified Name - return q.CheckPackage(c.Type, pkg) - } - - return fmt.Sprintf("UNKNOWN:path(%s):type(%s)", c.Path(), c.Type) + return ResolveType(q.Maps, func(t string) string { return q.CheckPackage(t, pkg) }, c.Type, c.Nullable, c.Path()) } var errMissingParam = errors.New("missing Param.Package") From f7455643231d0e35d04e56dc1019b421c6e36f50 Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Wed, 22 Oct 2025 20:29:09 -0700 Subject: [PATCH 5/9] standardize context creation --- output/context.go | 8 ++++++++ output/db.go | 2 +- output/openapi.go | 2 +- output/proto.go | 2 +- output/sql.go | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/output/context.go b/output/context.go index ff98b09..9887d77 100644 --- a/output/context.go +++ b/output/context.go @@ -27,6 +27,14 @@ type Context struct { AbortError error } +// NewContext creates a new base context with the given process and logger. +func NewContext(p cfg.Process, l zerolog.Logger) Context { + return Context{ + Process: p, + Logger: l, + } +} + // Funcs defaults the default case funcs based on the Process.Case. func (c *Context) Funcs() plates.FuncMap { return runtime.CaseFuncs(c.Case) diff --git a/output/db.go b/output/db.go index 7df18e8..103afdf 100644 --- a/output/db.go +++ b/output/db.go @@ -26,7 +26,7 @@ func HasDBOutput(o cfg.Output) bool { func DB(p cfg.Process, fn cfg.FileHandler, logger zerolog.Logger, schemas db.DB, simulate bool) error { ctx := SchemasContext{ - Context: Context{Process: p, Logger: logger}, + Context: NewContext(p, logger), DB: schemas, } diff --git a/output/openapi.go b/output/openapi.go index c0c6bc0..4fd07f3 100644 --- a/output/openapi.go +++ b/output/openapi.go @@ -31,7 +31,7 @@ func OpenAPI(p cfg.Process, fn cfg.FileHandler, l zerolog.Logger, groups openapi for _, ff := range groups { for _, f := range ff { ctx := OpenAPIFileContext{ - Context: Context{Process: p, Logger: l}, + Context: NewContext(p, l), File: f, } diff --git a/output/proto.go b/output/proto.go index dc65d72..236a6af 100644 --- a/output/proto.go +++ b/output/proto.go @@ -21,7 +21,7 @@ func HasProtoOutput(o cfg.Output) bool { func Proto(p cfg.Process, fn cfg.FileHandler, l zerolog.Logger, groups proto.PBFileGroups, simulate bool) error { base := ProtoContext{ - Context: Context{Process: p, Logger: l}, + Context: NewContext(p, l), FileGroups: groups, } runner := NewProcessRunner(p.RootDir, fn, l, simulate) diff --git a/output/sql.go b/output/sql.go index eac415f..7d39a43 100644 --- a/output/sql.go +++ b/output/sql.go @@ -27,7 +27,7 @@ func HasSQLOutput(o cfg.Output) bool { func SQL(p cfg.Process, fn cfg.FileHandler, l zerolog.Logger, fileGroups sql.FileGroups, simulate bool) error { base := SQLContext{ - Context: Context{Process: p, Logger: l}, + Context: NewContext(p, l), FileGroups: fileGroups, } From 45dfe5e335d03236757bcda52d0efe33e1c06038 Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Wed, 22 Oct 2025 20:55:40 -0700 Subject: [PATCH 6/9] move file handling logic out of commands --- cmd/copy_template.go | 27 ++---------------- cmd/fileutil.go | 65 ++++++++++++++++++++++++++++++++++++++++++++ cmd/init.go | 32 ---------------------- 3 files changed, 67 insertions(+), 57 deletions(-) create mode 100644 cmd/fileutil.go diff --git a/cmd/copy_template.go b/cmd/copy_template.go index 60479a0..feb1e15 100644 --- a/cmd/copy_template.go +++ b/cmd/copy_template.go @@ -3,8 +3,6 @@ package cmd import ( "fmt" "os" - "path/filepath" - "strings" "github.com/rs/zerolog" "github.com/spf13/cobra" @@ -42,12 +40,12 @@ func writeTemplate(l zerolog.Logger, dir, filename string, useStdout, overwrite } if dir != "" { - filename = changeDirectory(dir, "foji", filename) + filename = ChangeDirectory(dir, "foji", filename) } l = l.With().Str("template", filename).Logger() - if useStdout || overwrite || !fileExists(filename) { + if useStdout || overwrite || !FileExists(filename) { l.Debug().Msg("Writing") err = WriteToFile(b, filename) @@ -60,24 +58,3 @@ func writeTemplate(l zerolog.Logger, dir, filename string, useStdout, overwrite return nil } - -func fileExists(filename string) bool { - fileInfo, err := os.Stat(filename) - - return err == nil && fileInfo.Mode().IsRegular() -} - -func changeDirectory(dir, swapDir, filename string) string { - path := strings.Split(filename, string(os.PathSeparator)) - if len(path) == 0 { - return filename - } - - if path[0] == swapDir { - path[0] = dir - } else { - path = append([]string{dir}, path...) - } - - return filepath.Join(path...) -} diff --git a/cmd/fileutil.go b/cmd/fileutil.go new file mode 100644 index 0000000..f504d07 --- /dev/null +++ b/cmd/fileutil.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +// File permission constants +const ( + DirPerm = 0o700 // rwx------ + FilePerm = 0o600 // rw------- +) + +// WriteToFile writes data to a file, creating directories as needed. +// It creates parent directories with DirPerm and the file with FilePerm. +func WriteToFile(source []byte, file string) error { + err := os.MkdirAll(filepath.Dir(file), DirPerm) + if err != nil { + return fmt.Errorf("create output directory:%w", err) + } + + f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, FilePerm) + if err != nil { + return fmt.Errorf("open file:%w", err) + } + + _, err = f.Write(source) + + closeErr := f.Close() + if closeErr != nil { + return fmt.Errorf("closing file:%w", closeErr) + } + + if err != nil { + return fmt.Errorf("writing file:%w", err) + } + + return nil +} + +// FileExists checks if a file exists and is a regular file (not a directory). +func FileExists(filename string) bool { + fileInfo, err := os.Stat(filename) + return err == nil && fileInfo.Mode().IsRegular() +} + +// ChangeDirectory modifies a file path by changing its directory prefix. +// If the filename starts with swapDir, it replaces it with dir. +// Otherwise, it prepends dir to the path. +func ChangeDirectory(dir, swapDir, filename string) string { + path := strings.Split(filename, string(os.PathSeparator)) + if len(path) == 0 { + return filename + } + + if path[0] == swapDir { + path[0] = dir + } else { + path = append([]string{dir}, path...) + } + + return filepath.Join(path...) +} diff --git a/cmd/init.go b/cmd/init.go index 3a0f417..7145ab1 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -1,9 +1,7 @@ package cmd import ( - "fmt" "os" - "path/filepath" "github.com/rs/zerolog/log" "github.com/spf13/cobra" @@ -54,33 +52,3 @@ func writeConfig() { l.Info().Msg("wrote sample foji config file") } - -const ( - permRWXUser = 0o700 - permRWUser = 0o600 -) - -func WriteToFile(source []byte, file string) error { - err := os.MkdirAll(filepath.Dir(file), permRWXUser) - if err != nil { - return fmt.Errorf("create output directory:%w", err) - } - - f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, permRWUser) - if err != nil { - return fmt.Errorf("open file:%w", err) - } - - _, err = f.Write(source) - - closeErr := f.Close() - if closeErr != nil { - return fmt.Errorf("closing file:%w", closeErr) - } - - if err != nil { - return fmt.Errorf("writing file:%w", err) - } - - return nil -} From 2b1de2a5754fc98310ea9c36195d3fd96be9ee06 Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Fri, 24 Oct 2025 10:55:06 -0700 Subject: [PATCH 7/9] fix errs package naming --- cfg/config.go | 4 ++-- {errors => errs}/errors.go | 4 ++-- output/context.go | 10 +++++----- output/invoke.go | 10 +++++----- runtime/funcs.go | 4 ++-- welder/weld.go | 8 ++++---- 6 files changed, 20 insertions(+), 20 deletions(-) rename {errors => errs}/errors.go (91%) diff --git a/cfg/config.go b/cfg/config.go index 8d60ebc..3444a21 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -4,7 +4,7 @@ import ( "fmt" "sort" - "github.com/gofoji/foji/errors" + "github.com/gofoji/foji/errs" "github.com/gofoji/foji/stringlist" ) @@ -100,7 +100,7 @@ func (pp Processes) String() string { } const ( - errProcess = errors.Error("Process") + errProcess = errs.Error("Process") missingBundleFormat = "%w '%s' referenced by bundle `%s` not found. Possible options: %s" missingFormat = "%w '%s' not found. Possible options: %s" ) diff --git a/errors/errors.go b/errs/errors.go similarity index 91% rename from errors/errors.go rename to errs/errors.go index 38adca9..de6fef2 100644 --- a/errors/errors.go +++ b/errs/errors.go @@ -1,5 +1,5 @@ -// Package errors provides unified error types and constants used throughout foji. -package errors +// Package errs provides unified error types and constants used throughout foji. +package errs // Error is a simple error string type used for custom error definitions. type Error string diff --git a/output/context.go b/output/context.go index 9887d77..ca90735 100644 --- a/output/context.go +++ b/output/context.go @@ -8,7 +8,7 @@ import ( "github.com/rs/zerolog" "github.com/gofoji/foji/cfg" - fojiErrors "github.com/gofoji/foji/errors" + "github.com/gofoji/foji/errs" "github.com/gofoji/foji/runtime" "github.com/gofoji/foji/stringlist" ) @@ -48,7 +48,7 @@ func (c *Context) Aborted() error { // NotNeededIf given bool is true the execution is aborted, and can be used to prevent generation of a file. func (c *Context) NotNeededIf(t bool, reason string) (string, error) { if t { - c.AbortError = fmt.Errorf("%w: %s", fojiErrors.ErrNotNeeded, reason) + c.AbortError = fmt.Errorf("%w: %s", errs.ErrNotNeeded, reason) return "", c.AbortError } @@ -59,7 +59,7 @@ func (c *Context) NotNeededIf(t bool, reason string) (string, error) { // ErrorIf if given bool is true the execution is fatally aborted, and stops processing. func (c *Context) ErrorIf(t bool, reason string) (string, error) { if t { - c.AbortError = fmt.Errorf("%w: %s", fojiErrors.ErrMissingRequirement, reason) + c.AbortError = fmt.Errorf("%w: %s", errs.ErrMissingRequirement, reason) return "", c.AbortError } @@ -71,7 +71,7 @@ func (c *Context) ErrorIf(t bool, reason string) (string, error) { // Used for executing sub templates that still need access to the context. func (c *Context) WithParams(values ...any) (*Context, error) { if len(values)%2 != 0 { - return nil, fojiErrors.ErrInvalidDictParams + return nil, errs.ErrInvalidDictParams } out := *c @@ -80,7 +80,7 @@ func (c *Context) WithParams(values ...any) (*Context, error) { for i := 0; i < len(values); i += 2 { key, ok := values[i].(string) if !ok { - return nil, fojiErrors.ErrInvalidDictKey + return nil, errs.ErrInvalidDictKey } out.RuntimeParams[key] = values[i+1] diff --git a/output/invoke.go b/output/invoke.go index ab7f062..b6777fc 100644 --- a/output/invoke.go +++ b/output/invoke.go @@ -12,7 +12,7 @@ import ( "github.com/rs/zerolog" "github.com/gofoji/foji/cfg" - fojiErrors "github.com/gofoji/foji/errors" + "github.com/gofoji/foji/errs" "github.com/gofoji/foji/foji" "github.com/gofoji/foji/runtime" "github.com/gofoji/foji/stringlist" @@ -46,7 +46,7 @@ func (p ProcessRunner) process(tm stringlist.StringMap, data any) error { err = p.template(targetFile, templateFile, data) if err != nil { - if !errors.Is(err, fojiErrors.ErrPermExists) { + if !errors.Is(err, errs.ErrPermExists) { return err } @@ -133,7 +133,7 @@ func (p ProcessRunner) template(outputFile, templateFile string, data any) error outputFile = p.dir + outputFile if permFile && fileExists(outputFile) { - return fojiErrors.ErrPermExists + return errs.ErrPermExists } if p.simulate { @@ -147,13 +147,13 @@ func (p ProcessRunner) template(outputFile, templateFile string, data any) error err = p.FromFile(templateFile).ToFile(outputFile, data) if err != nil { - if errors.Is(err, fojiErrors.ErrNotNeeded) { + if errors.Is(err, errs.ErrNotNeeded) { l.Info().Err(err).Msg("skipped") return nil } - if errors.Is(err, fojiErrors.ErrMissingRequirement) { + if errors.Is(err, errs.ErrMissingRequirement) { return err //nolint:wrapcheck } diff --git a/runtime/funcs.go b/runtime/funcs.go index d44e79e..e40a659 100644 --- a/runtime/funcs.go +++ b/runtime/funcs.go @@ -14,7 +14,7 @@ import ( "github.com/gofoji/foji/cfg" "github.com/gofoji/foji/color" - "github.com/gofoji/foji/errors" + "github.com/gofoji/foji/errs" "github.com/gofoji/foji/stringlist" ) @@ -243,7 +243,7 @@ func In(needle any, haystack ...any) (bool, error) { return false, nil default: - return false, fmt.Errorf("%w: must be iterable type, found type %s", errors.ErrRuntime, tp) + return false, fmt.Errorf("%w: must be iterable type, found type %s", errs.ErrRuntime, tp) } } diff --git a/welder/weld.go b/welder/weld.go index f05e711..38df487 100644 --- a/welder/weld.go +++ b/welder/weld.go @@ -9,7 +9,7 @@ import ( "github.com/rs/zerolog" "github.com/gofoji/foji/cfg" - "github.com/gofoji/foji/errors" + "github.com/gofoji/foji/errs" "github.com/gofoji/foji/input" "github.com/gofoji/foji/input/db" "github.com/gofoji/foji/input/db/pg" @@ -145,7 +145,7 @@ func (w *Welder) getResource(resource string) (input.FileGroup, error) { in, ok := w.config.Files[resource] if !ok { - return r.Loaded, fmt.Errorf("%w: invalid resource reference: %s", errors.ErrWeld, resource) + return r.Loaded, fmt.Errorf("%w: invalid resource reference: %s", errs.ErrWeld, resource) } f, err := input.Parse(w.ctx, w.logger, in) @@ -190,7 +190,7 @@ func (w *Welder) getProcessFiles(p cfg.Process) ([]input.FileGroup, error) { func (w *Welder) parseDB() (db.DB, error) { if w.conn == nil { - return nil, fmt.Errorf("%w: db not initialized", errors.ErrWeld) + return nil, fmt.Errorf("%w: db not initialized", errs.ErrWeld) } repo := pg.New(w.conn, w.logger) @@ -209,7 +209,7 @@ func (w *Welder) initDBConnection() error { } if w.config.DB.Connection == "" { - return fmt.Errorf("%w: missing db.connection", errors.ErrWeld) + return fmt.Errorf("%w: missing db.connection", errs.ErrWeld) } w.logger.Debug().Str("Connection", w.config.DB.Connection).Msg("Loading Database") From 110d29b1a83309a4bde98467a20ce727a0603d3e Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Fri, 24 Oct 2025 11:04:14 -0700 Subject: [PATCH 8/9] fix references to `interface{}` to `any` --- foji/openapi/model.go.tpl | 2 +- output/openapi.go | 2 +- tests/auth/model_gen.go | 2 +- tests/example/model_gen.go | 42 +++++++++++++++++++------------------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/foji/openapi/model.go.tpl b/foji/openapi/model.go.tpl index d3a92db..96aa48a 100644 --- a/foji/openapi/model.go.tpl +++ b/foji/openapi/model.go.tpl @@ -78,7 +78,7 @@ func (e {{ $enumType }}) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *{{ $enumType }}) Scan(src interface{}) error { +func (e *{{ $enumType }}) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("{{ $enumType }}.scan: scanned a %T, not []byte", src) //nolint diff --git a/output/openapi.go b/output/openapi.go index 4fd07f3..761c253 100644 --- a/output/openapi.go +++ b/output/openapi.go @@ -120,7 +120,7 @@ func (o *OpenAPIFileContext) getXGoType(currentPackage string, goType any) strin // HasExtensionValue checks if an extension exists and has a truthy value. // For boolean extensions, it returns the boolean value. // For other extensions, it returns true if they exist. -func HasExtensionValue(extensions map[string]interface{}, ext string) bool { +func HasExtensionValue(extensions map[string]any, ext string) bool { v, ok := extensions[ext] if !ok { return false diff --git a/tests/auth/model_gen.go b/tests/auth/model_gen.go index e31fa6b..289dd1c 100644 --- a/tests/auth/model_gen.go +++ b/tests/auth/model_gen.go @@ -95,7 +95,7 @@ func (e UserRole) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *UserRole) Scan(src interface{}) error { +func (e *UserRole) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("UserRole.scan: scanned a %T, not []byte", src) //nolint diff --git a/tests/example/model_gen.go b/tests/example/model_gen.go index b04ae0e..3d15654 100644 --- a/tests/example/model_gen.go +++ b/tests/example/model_gen.go @@ -1171,7 +1171,7 @@ func (e Season) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *Season) Scan(src interface{}) error { +func (e *Season) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("Season.scan: scanned a %T, not []byte", src) //nolint @@ -1253,7 +1253,7 @@ func (e SeasonNullable) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *SeasonNullable) Scan(src interface{}) error { +func (e *SeasonNullable) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("SeasonNullable.scan: scanned a %T, not []byte", src) //nolint @@ -1458,7 +1458,7 @@ func (e XarrayEnumItem) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *XarrayEnumItem) Scan(src interface{}) error { +func (e *XarrayEnumItem) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("XarrayEnumItem.scan: scanned a %T, not []byte", src) //nolint @@ -1548,7 +1548,7 @@ func (e XarrayObjectArrayEnumItemList) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *XarrayObjectArrayEnumItemList) Scan(src interface{}) error { +func (e *XarrayObjectArrayEnumItemList) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("XarrayObjectArrayEnumItemList.scan: scanned a %T, not []byte", src) //nolint @@ -1634,7 +1634,7 @@ func (e XarrayObjectEnumItemOptions) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *XarrayObjectEnumItemOptions) Scan(src interface{}) error { +func (e *XarrayObjectEnumItemOptions) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("XarrayObjectEnumItemOptions.scan: scanned a %T, not []byte", src) //nolint @@ -1719,7 +1719,7 @@ func (e XobjectArrayEnumItems) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *XobjectArrayEnumItems) Scan(src interface{}) error { +func (e *XobjectArrayEnumItems) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("XobjectArrayEnumItems.scan: scanned a %T, not []byte", src) //nolint @@ -1807,7 +1807,7 @@ func (e XobjectArrayObjectEnumListOptions) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *XobjectArrayObjectEnumListOptions) Scan(src interface{}) error { +func (e *XobjectArrayObjectEnumListOptions) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("XobjectArrayObjectEnumListOptions.scan: scanned a %T, not []byte", src) //nolint @@ -1888,7 +1888,7 @@ func (e XobjectEnumOptions) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *XobjectEnumOptions) Scan(src interface{}) error { +func (e *XobjectEnumOptions) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("XobjectEnumOptions.scan: scanned a %T, not []byte", src) //nolint @@ -2045,7 +2045,7 @@ func (e ColorQuery) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *ColorQuery) Scan(src interface{}) error { +func (e *ColorQuery) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("ColorQuery.scan: scanned a %T, not []byte", src) //nolint @@ -2119,7 +2119,7 @@ func (e ColorQueryDefault) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *ColorQueryDefault) Scan(src interface{}) error { +func (e *ColorQueryDefault) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("ColorQueryDefault.scan: scanned a %T, not []byte", src) //nolint @@ -2226,7 +2226,7 @@ func (e AddFormRequestF08) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *AddFormRequestF08) Scan(src interface{}) error { +func (e *AddFormRequestF08) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("AddFormRequestF08.scan: scanned a %T, not []byte", src) //nolint @@ -2300,7 +2300,7 @@ func (e AddFormRequestF08Null) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *AddFormRequestF08Null) Scan(src interface{}) error { +func (e *AddFormRequestF08Null) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("AddFormRequestF08Null.scan: scanned a %T, not []byte", src) //nolint @@ -2775,7 +2775,7 @@ func (e AddInlinedBodyRequestF08) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *AddInlinedBodyRequestF08) Scan(src interface{}) error { +func (e *AddInlinedBodyRequestF08) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("AddInlinedBodyRequestF08.scan: scanned a %T, not []byte", src) //nolint @@ -2849,7 +2849,7 @@ func (e AddInlinedBodyRequestF08Null) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *AddInlinedBodyRequestF08Null) Scan(src interface{}) error { +func (e *AddInlinedBodyRequestF08Null) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("AddInlinedBodyRequestF08Null.scan: scanned a %T, not []byte", src) //nolint @@ -3028,7 +3028,7 @@ func (e GetExampleParamsEnumTest) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *GetExampleParamsEnumTest) Scan(src interface{}) error { +func (e *GetExampleParamsEnumTest) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("GetExampleParamsEnumTest.scan: scanned a %T, not []byte", src) //nolint @@ -3102,7 +3102,7 @@ func (e GetRawRequestVehicle) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *GetRawRequestVehicle) Scan(src interface{}) error { +func (e *GetRawRequestVehicle) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("GetRawRequestVehicle.scan: scanned a %T, not []byte", src) //nolint @@ -3176,7 +3176,7 @@ func (e GetRawRequestResponseVehicle) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *GetRawRequestResponseVehicle) Scan(src interface{}) error { +func (e *GetRawRequestResponseVehicle) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("GetRawRequestResponseVehicle.scan: scanned a %T, not []byte", src) //nolint @@ -3250,7 +3250,7 @@ func (e GetRawRequestResponseAndHeadersVehicle) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *GetRawRequestResponseAndHeadersVehicle) Scan(src interface{}) error { +func (e *GetRawRequestResponseAndHeadersVehicle) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("GetRawRequestResponseAndHeadersVehicle.scan: scanned a %T, not []byte", src) //nolint @@ -3324,7 +3324,7 @@ func (e GetRawResponseVehicle) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *GetRawResponseVehicle) Scan(src interface{}) error { +func (e *GetRawResponseVehicle) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("GetRawResponseVehicle.scan: scanned a %T, not []byte", src) //nolint @@ -3398,7 +3398,7 @@ func (e GetTestVehicle) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *GetTestVehicle) Scan(src interface{}) error { +func (e *GetTestVehicle) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("GetTestVehicle.scan: scanned a %T, not []byte", src) //nolint @@ -3472,7 +3472,7 @@ func (e GetTestVehicleDefault) Value() (driver.Value, error) { return json.Marshal(e.String()) } -func (e *GetTestVehicleDefault) Scan(src interface{}) error { +func (e *GetTestVehicleDefault) Scan(src any) error { s, ok := src.(string) if !ok { return fmt.Errorf("GetTestVehicleDefault.scan: scanned a %T, not []byte", src) //nolint From eab6dfa8b336e171470a0a14f7b4ac728acf35c8 Mon Sep 17 00:00:00 2001 From: Marc Bir Date: Fri, 24 Oct 2025 11:05:24 -0700 Subject: [PATCH 9/9] lint cleanup --- output/mapper.go | 1 + 1 file changed, 1 insertion(+) diff --git a/output/mapper.go b/output/mapper.go index b03b387..07802af 100644 --- a/output/mapper.go +++ b/output/mapper.go @@ -18,6 +18,7 @@ func ResolveType(maps cfg.Maps, checkFunc func(string) string, columnType string pp := strings.Split(path, ".") for i := range pp { p := strings.Join(pp[i:], ".") + t, ok := maps.Type["."+p] if ok { return checkFunc(t)