Skip to content

Conversation

@karngyan
Copy link
Member

Fix: Generate top-level getters in getter mode

This PR fixes a bug where top-level variables in getter mode were generated as simple var declarations instead of getter functions, preventing environment variable overrides for top-level configuration values.

Changes

  • Generator fix: Top-level simple values (strings, ints, bools, durations, arrays of primitives) are now generated as getter functions instead of var declarations in getter mode
  • Code refactoring: Extracted writeGetterBody() to share getter logic between struct methods and top-level functions
  • Test coverage: Added comprehensive test for top-level getter generation (TestGenerator_GetterMode_TopLevelVariables)
  • Example update: Regenerated getter_config/config.go to demonstrate the fix - Name is now a function with env override support

Example

Before (incorrect):

var (
    Name string = "cfgx"
)

After (correct):

func Name() string {
    if v := os.Getenv("CONFIG_NAME"); v != "" {
        return v
    }
    return "cfgx"
}

Structs and arrays of structs remain as var declarations as intended.

Copilot AI review requested due to automatic review settings October 28, 2025 20:48
@github-actions
Copy link

📊 Code Coverage Report

total:									(statements)			50.9%
Coverage by file
github.com/gomantics/cfgx/cfgx.go:88:					GenerateFromFile		78.9%
github.com/gomantics/cfgx/cfgx.go:180:					Generate			100.0%
github.com/gomantics/cfgx/cfgx.go:197:					GenerateWithOptions		62.5%
github.com/gomantics/cfgx/cmd/cfgx/common.go:20:			parseFileSize			0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:38:				init				0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:43:				runDiff				0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:77:				parseTomlFile			0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:104:				computeDiffs			0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:178:				deepEqual			0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:185:				outputText			0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:221:				formatValue			0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:242:				outputJSON			0.0%
github.com/gomantics/cfgx/cmd/cfgx/generate.go:60:			init				0.0%
github.com/gomantics/cfgx/cmd/cfgx/main.go:18:				main				0.0%
github.com/gomantics/cfgx/cmd/cfgx/main.go:32:				init				0.0%
github.com/gomantics/cfgx/cmd/cfgx/watch.go:168:			init				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:33:		Logging				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:37:		File				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:44:		Format				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:51:		Level				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:58:		Rotation			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:62:		Compress			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:71:		MaxAge				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:80:		MaxSize				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:89:		Name				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:96:		Version				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:103:		Enabled				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:112:		MaxEntries			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:121:		Outputs				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:128:		Redis				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:132:		Addr				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:139:		Db				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:148:		Password			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:155:		Ttl				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:164:		ConnMaxLifetime			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:173:		Dsn				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:180:		MaxIdleConns			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:189:		MaxOpenConns			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:198:		Pool				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:202:		Enabled				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:211:		MaxSize				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:220:		MinSize				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:229:		Methods				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:236:		Path				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:243:		RateLimit			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:252:		Enabled				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:261:		Name				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:268:		Priority			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:277:		Addr				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:284:		Cert				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:332:		Debug				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:341:		IdleTimeout			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:350:		MaxHeaderBytes			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:359:		ReadTimeout			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:368:		ShutdownTimeout			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:377:		Timeout				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:386:		WriteTimeout			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:395:		AllowedOrigins			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:402:		Features			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:409:		Name				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:416:		Ports				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:423:		Weights				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:430:		Name				0.0%
github.com/gomantics/cfgx/internal/envoverride/envoverride.go:13:	Apply				50.0%
github.com/gomantics/cfgx/internal/envoverride/envoverride.go:40:	applyNested			88.2%
github.com/gomantics/cfgx/internal/envoverride/envoverride.go:79:	convertValue			93.3%
github.com/gomantics/cfgx/internal/envoverride/envoverride.go:111:	convertArray			88.9%
github.com/gomantics/cfgx/internal/generator/file_handler.go:11:	isFileReference			100.0%
github.com/gomantics/cfgx/internal/generator/file_handler.go:18:	loadFileContent			81.2%
github.com/gomantics/cfgx/internal/generator/generator.go:26:		WithPackageName			100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:33:		WithEnvOverride			0.0%
github.com/gomantics/cfgx/internal/generator/generator.go:40:		WithInputDir			100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:47:		WithMaxFileSize			100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:54:		WithMode			100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:61:		New				100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:77:		stripSuffix			40.0%
github.com/gomantics/cfgx/internal/generator/generator.go:88:		writeGetterImports		100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:109:		needsStrconvImport		100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:119:		checkStrconvNeeded		69.2%
github.com/gomantics/cfgx/internal/generator/generator.go:148:		Generate			86.4%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:24:		generateStructsAndVars		63.8%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:128:		collectNestedStructs		57.1%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:166:		generateStruct			89.5%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:208:		generateStructInit		85.2%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:272:		writeArrayOfTablesInit		58.8%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:317:		writeArrayOfStructs		0.0%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:374:		generateStructsAndGetters	68.0%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:473:		collectNestedStructsForGetters	57.1%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:502:		generateGetterMethods		62.8%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:578:		generateGetterMethod		100.0%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:586:		generateTopLevelGetter		100.0%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:599:		writeGetterBody			90.6%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:655:		envVarName			100.0%
github.com/gomantics/cfgx/internal/generator/validation.go:10:		validateFileReferences		100.0%
github.com/gomantics/cfgx/internal/generator/validation.go:20:		validateFileReferencesValue	84.6%
github.com/gomantics/cfgx/internal/generator/validation.go:51:		needsTimeImport			100.0%
github.com/gomantics/cfgx/internal/generator/validation.go:60:		needsTimeImportValue		88.9%
github.com/gomantics/cfgx/internal/generator/validation.go:82:		isDurationString		100.0%
github.com/gomantics/cfgx/internal/generator/value_writer.go:17:	toGoType			94.1%
github.com/gomantics/cfgx/internal/generator/value_writer.go:61:	writeValue			100.0%
github.com/gomantics/cfgx/internal/generator/value_writer.go:66:	writeValueWithIndent		76.5%
github.com/gomantics/cfgx/internal/generator/value_writer.go:105:	writeByteArrayLiteral		88.2%
github.com/gomantics/cfgx/internal/generator/value_writer.go:141:	writeDurationLiteral		80.0%
github.com/gomantics/cfgx/internal/generator/value_writer.go:197:	writeArray			80.0%
github.com/gomantics/cfgx/internal/pkgutil/pkgutil.go:11:		InferName			63.6%
total:									(statements)			50.9%

@karngyan karngyan merged commit f788f14 into main Oct 28, 2025
6 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the getter mode code generation to create top-level getter functions for simple variables instead of variable declarations, while maintaining variable declarations only for structs and arrays of structs. This provides a consistent interface where all values (simple types and structs) are accessed through function calls that support environment variable overrides.

  • Introduced top-level getter functions for primitive types and arrays of primitives
  • Refactored common getter body logic into a shared writeGetterBody method
  • Updated variable declarations to only include structs and arrays of structs

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
internal/generator/struct_gen.go Implements top-level getter function generation and refactors getter body logic to avoid code duplication
internal/generator/generator_test.go Adds comprehensive test coverage for the new top-level getter functionality
example/getter_config/config.go Demonstrates the change where Name is now a getter function instead of a variable
example/gen.go Simplifies go:generate commands by removing redundant path references

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +424 to +435
case []any:
// Check if it's an array of maps (structs)
if len(val) > 0 {
if _, ok := val[0].(map[string]any); ok {
// Skip array of structs
continue
}
}
// Generate getter for array of primitives
if err := g.generateTopLevelGetter(buf, key, value); err != nil {
return err
}
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty arrays of primitives ([]any) are not handled. When len(val) == 0, the code neither generates a getter nor adds a var declaration, causing these arrays to be silently omitted from the generated code. Consider generating a getter for empty arrays or handling them explicitly.

Copilot uses AI. Check for mistakes.
Comment on lines +459 to +463
if len(val) > 0 {
if _, ok := val[0].(map[string]any); ok {
structName := sx.CamelCase(key) + "Item"
fmt.Fprintf(buf, "\t%s []%s\n", varName, structName)
}
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty arrays of structs ([]any with length 0) are not added to the var block. When an empty array is encountered, no variable declaration is generated, which could cause compilation errors if the code expects this variable to exist. Consider adding a var declaration for empty arrays with a type comment or default struct type.

Suggested change
if len(val) > 0 {
if _, ok := val[0].(map[string]any); ok {
structName := sx.CamelCase(key) + "Item"
fmt.Fprintf(buf, "\t%s []%s\n", varName, structName)
}
structName := sx.CamelCase(key) + "Item"
if len(val) > 0 {
if _, ok := val[0].(map[string]any); ok {
fmt.Fprintf(buf, "\t%s []%s\n", varName, structName)
}
} else {
// Emit declaration for empty array of structs
fmt.Fprintf(buf, "\t%s []%s\n", varName, structName)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants