From e6447297be21bdb21b8337780a2024ba636dc3ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 22:06:05 +0000 Subject: [PATCH 1/8] build(deps): bump github.com/fsnotify/fsnotify from 1.9.0 to 1.10.1 Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.9.0 to 1.10.1. - [Release notes](https://github.com/fsnotify/fsnotify/releases) - [Changelog](https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md) - [Commits](https://github.com/fsnotify/fsnotify/compare/v1.9.0...v1.10.1) --- updated-dependencies: - dependency-name: github.com/fsnotify/fsnotify dependency-version: 1.10.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b819ea49..15f649b2 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/BurntSushi/toml v1.6.0 github.com/cloudevents/sdk-go/v2 v2.16.2 github.com/cucumber/godog v0.15.1 - github.com/fsnotify/fsnotify v1.9.0 + github.com/fsnotify/fsnotify v1.10.1 github.com/go-chi/chi/v5 v5.2.2 github.com/golobby/cast v1.3.3 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index bfc601ba..29c7eca7 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= From 0a022477d902f36dc40e920a42302c69f05295ac Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Wed, 6 May 2026 15:21:37 -0400 Subject: [PATCH 2/8] fix: address modular PR validation failure --- .github/workflows/ci.yml | 2 +- application.go | 2 +- contract_verifier.go | 4 ++-- errors.go | 15 +++++++++------ feeders/affixed_env.go | 6 +++--- feeders/base_config.go | 2 +- feeders/dot_env.go | 10 +++++----- modules/reverseproxy/debug.go | 4 ++-- 8 files changed, 24 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d130ea2a..1274576b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -195,4 +195,4 @@ jobs: slug: GoCodeAlone/modular files: total-coverage.txt flags: total - fail_ci_if_error: true + fail_ci_if_error: ${{ secrets.CODECOV_TOKEN != '' }} diff --git a/application.go b/application.go index 6b084486..66f2bca4 100644 --- a/application.go +++ b/application.go @@ -739,7 +739,7 @@ func (app *StdApplication) InitWithApp(appToPass Application) error { defer func() { if r := recover(); r != nil { mu.Lock() - errs = append(errs, fmt.Errorf("panic initializing module %s: %v", name, r)) + errs = append(errs, fmt.Errorf("%w %s: %v", ErrModuleInitializationPanic, name, r)) mu.Unlock() } }() diff --git a/contract_verifier.go b/contract_verifier.go index b9bca84a..205e8428 100644 --- a/contract_verifier.go +++ b/contract_verifier.go @@ -130,7 +130,7 @@ func (v *StandardContractVerifier) runReloadWithGuard(module Reloadable, label s go func() { defer func() { if r := recover(); r != nil { - ch <- result{err: fmt.Errorf("Reload panicked: %v", r)} + ch <- result{err: fmt.Errorf("%w: %v", ErrReloadPanic, r)} } }() ch <- result{err: module.Reload(ctx, nil)} @@ -177,7 +177,7 @@ func (v *StandardContractVerifier) VerifyHealthContract(provider HealthProvider) go func() { defer func() { if r := recover(); r != nil { - ch <- result{err: fmt.Errorf("HealthCheck panicked: %v", r)} + ch <- result{err: fmt.Errorf("%w: %v", ErrHealthCheckPanic, r)} } }() reports, err := provider.HealthCheck(ctx) diff --git a/errors.go b/errors.go index 0b2c2528..fae30af4 100644 --- a/errors.go +++ b/errors.go @@ -87,12 +87,15 @@ var ( ErrTenantIsolationViolation = errors.New("tenant isolation violation") // Reload errors - ErrReloadCircuitBreakerOpen = errors.New("reload circuit breaker is open; backing off") - ErrReloadChannelFull = errors.New("reload request channel is full") - ErrReloadInProgress = errors.New("reload already in progress") - ErrReloadStopped = errors.New("reload orchestrator is stopped") - ErrReloadTimeout = errors.New("reload timed out waiting for module") - ErrDynamicReloadNotEnabled = errors.New("dynamic reload not enabled") + ErrReloadCircuitBreakerOpen = errors.New("reload circuit breaker is open; backing off") + ErrReloadChannelFull = errors.New("reload request channel is full") + ErrReloadInProgress = errors.New("reload already in progress") + ErrReloadStopped = errors.New("reload orchestrator is stopped") + ErrReloadTimeout = errors.New("reload timed out waiting for module") + ErrDynamicReloadNotEnabled = errors.New("dynamic reload not enabled") + ErrModuleInitializationPanic = errors.New("panic initializing module") + ErrReloadPanic = errors.New("reload panicked") + ErrHealthCheckPanic = errors.New("health check panicked") // Observer/Event emission errors ErrNoSubjectForEventEmission = errors.New("no subject available for event emission") diff --git a/feeders/affixed_env.go b/feeders/affixed_env.go index 07d4f862..79ce56a7 100644 --- a/feeders/affixed_env.go +++ b/feeders/affixed_env.go @@ -29,8 +29,8 @@ type AffixedEnvFeeder struct { logger interface { Debug(msg string, args ...any) } - ft FieldTrackerHolder - priority int + ft FieldTrackerHolder + priority int } // NewAffixedEnvFeeder creates a new AffixedEnvFeeder with the specified prefix and suffix @@ -79,7 +79,7 @@ func (f *AffixedEnvFeeder) Feed(structure interface{}) error { inputType := reflect.TypeOf(structure) if inputType != nil { - if inputType.Kind() == reflect.Ptr { + if inputType.Kind() == reflect.Pointer { if inputType.Elem().Kind() == reflect.Struct { return f.fillStruct(reflect.ValueOf(structure).Elem(), f.Prefix, f.Suffix) } diff --git a/feeders/base_config.go b/feeders/base_config.go index e4cccd83..3db85645 100644 --- a/feeders/base_config.go +++ b/feeders/base_config.go @@ -18,7 +18,7 @@ type BaseConfigFeeder struct { Environment string // Environment name (e.g., "prod", "staging", "dev") verboseDebug bool logger interface{ Debug(msg string, args ...any) } - ft FieldTrackerHolder + ft FieldTrackerHolder } // NewBaseConfigFeeder creates a new base configuration feeder diff --git a/feeders/dot_env.go b/feeders/dot_env.go index 3d070972..10591f37 100644 --- a/feeders/dot_env.go +++ b/feeders/dot_env.go @@ -16,9 +16,9 @@ type DotEnvFeeder struct { logger interface { Debug(msg string, args ...any) } - ft FieldTrackerHolder - envVars map[string]string // in-memory storage of parsed .env variables - priority int + ft FieldTrackerHolder + envVars map[string]string // in-memory storage of parsed .env variables + priority int } // NewDotEnvFeeder creates a new DotEnvFeeder that reads from the specified .env file @@ -170,7 +170,7 @@ func (f *DotEnvFeeder) parseEnvLine(line string, lineNum int) error { // populateStructFromCatalog populates struct fields from the global environment catalog func (f *DotEnvFeeder) populateStructFromCatalog(structure interface{}, prefix string) error { structValue := reflect.ValueOf(structure) - if structValue.Kind() != reflect.Ptr || structValue.Elem().Kind() != reflect.Struct { + if structValue.Kind() != reflect.Pointer || structValue.Elem().Kind() != reflect.Struct { return wrapDotEnvStructureError(structure) } @@ -300,7 +300,7 @@ func (f *DotEnvFeeder) convertStringToType(value string, targetType reflect.Type return boolVal, nil case reflect.Invalid, reflect.Uintptr, reflect.Complex64, reflect.Complex128, reflect.Array, reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, - reflect.Ptr, reflect.Slice, reflect.Struct, reflect.UnsafePointer: + reflect.Pointer, reflect.Slice, reflect.Struct, reflect.UnsafePointer: return nil, wrapDotEnvUnsupportedTypeError(targetType.Kind().String()) default: return nil, wrapDotEnvUnsupportedTypeError(targetType.Kind().String()) diff --git a/modules/reverseproxy/debug.go b/modules/reverseproxy/debug.go index a874892f..7460c622 100644 --- a/modules/reverseproxy/debug.go +++ b/modules/reverseproxy/debug.go @@ -241,7 +241,7 @@ func (d *DebugHandler) HandleInfo(w http.ResponseWriter, r *http.Request) { // Add additional circuit breaker details if available via reflection // This is safe because we control the CircuitBreaker implementation - if cbVal := reflect.ValueOf(cb); cbVal.Kind() == reflect.Ptr && !cbVal.IsNil() { + if cbVal := reflect.ValueOf(cb); cbVal.Kind() == reflect.Pointer && !cbVal.IsNil() { elem := cbVal.Elem() if elem.Kind() == reflect.Struct { if thresholdField := elem.FieldByName("failureThreshold"); thresholdField.IsValid() && thresholdField.CanInterface() { @@ -373,7 +373,7 @@ func (d *DebugHandler) HandleCircuitBreakers(w http.ResponseWriter, r *http.Requ } // Add internal details via reflection for comprehensive debugging - if cbVal := reflect.ValueOf(cb); cbVal.Kind() == reflect.Ptr && !cbVal.IsNil() { + if cbVal := reflect.ValueOf(cb); cbVal.Kind() == reflect.Pointer && !cbVal.IsNil() { elem := cbVal.Elem() if elem.Kind() == reflect.Struct { if thresholdField := elem.FieldByName("failureThreshold"); thresholdField.IsValid() && thresholdField.CanInterface() { From 7152c9766dd3e849009a71acc91bcbdc3aeb756e Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Wed, 6 May 2026 15:27:14 -0400 Subject: [PATCH 3/8] fix: address remaining feeder lint failures --- feeders/env.go | 94 +++++++++++++++++------------------ feeders/instance_aware_env.go | 48 +++++++++--------- feeders/json.go | 6 +-- 3 files changed, 74 insertions(+), 74 deletions(-) diff --git a/feeders/env.go b/feeders/env.go index 1891e655..a5ff3958 100644 --- a/feeders/env.go +++ b/feeders/env.go @@ -12,8 +12,8 @@ type EnvFeeder struct { logger interface { Debug(msg string, args ...any) } - ft FieldTrackerHolder - priority int + ft FieldTrackerHolder + priority int } // NewEnvFeeder creates a new EnvFeeder that reads from environment variables @@ -75,7 +75,7 @@ func (f *EnvFeeder) FeedWithModuleContext(structure interface{}, moduleName stri return ErrEnvInvalidStructure } - if inputType.Kind() != reflect.Ptr { + if inputType.Kind() != reflect.Pointer { if f.verboseDebug && f.logger != nil { f.logger.Debug("EnvFeeder: Structure is not a pointer", "kind", inputType.Kind()) } @@ -231,17 +231,17 @@ func (f *EnvFeeder) setFieldFromEnvWithModule(field reflect.Value, envTag, prefi // Record field population if tracker is available f.ft.Record(FieldPopulation{ - FieldPath: fieldPath, - FieldName: fieldName, - FieldType: field.Type().String(), - FeederType: "*feeders.EnvFeeder", - SourceType: "env", - SourceKey: foundKey, - Value: field.Interface(), - InstanceKey: "", - SearchKeys: searchKeys, - FoundKey: foundKey, - }) + FieldPath: fieldPath, + FieldName: fieldName, + FieldType: field.Type().String(), + FeederType: "*feeders.EnvFeeder", + SourceType: "env", + SourceKey: foundKey, + Value: field.Interface(), + InstanceKey: "", + SearchKeys: searchKeys, + FoundKey: foundKey, + }) if f.verboseDebug && f.logger != nil { f.logger.Debug("EnvFeeder: Successfully set field value", "fieldName", fieldName, "foundKey", foundKey, "envValue", envValue, "fieldPath", fieldPath) @@ -249,17 +249,17 @@ func (f *EnvFeeder) setFieldFromEnvWithModule(field reflect.Value, envTag, prefi } else { // Record that we searched but didn't find f.ft.Record(FieldPopulation{ - FieldPath: fieldPath, - FieldName: fieldName, - FieldType: field.Type().String(), - FeederType: "*feeders.EnvFeeder", - SourceType: "env", - SourceKey: "", - Value: nil, - InstanceKey: "", - SearchKeys: searchKeys, - FoundKey: "", - }) + FieldPath: fieldPath, + FieldName: fieldName, + FieldType: field.Type().String(), + FeederType: "*feeders.EnvFeeder", + SourceType: "env", + SourceKey: "", + Value: nil, + InstanceKey: "", + SearchKeys: searchKeys, + FoundKey: "", + }) if f.verboseDebug && f.logger != nil { f.logger.Debug("EnvFeeder: Environment variable not found or empty", "fieldName", fieldName, "searchKeys", searchKeys, "fieldPath", fieldPath) @@ -346,17 +346,17 @@ func (f *EnvFeeder) setPointerFieldFromEnvWithModule(field reflect.Value, envTag // Record field population if tracker is available f.ft.Record(FieldPopulation{ - FieldPath: fieldPath, - FieldName: fieldName, - FieldType: field.Type().String(), - FeederType: "*feeders.EnvFeeder", - SourceType: "env", - SourceKey: foundKey, - Value: field.Interface(), - InstanceKey: "", - SearchKeys: searchKeys, - FoundKey: foundKey, - }) + FieldPath: fieldPath, + FieldName: fieldName, + FieldType: field.Type().String(), + FeederType: "*feeders.EnvFeeder", + SourceType: "env", + SourceKey: foundKey, + Value: field.Interface(), + InstanceKey: "", + SearchKeys: searchKeys, + FoundKey: foundKey, + }) if f.verboseDebug && f.logger != nil { f.logger.Debug("EnvFeeder: Successfully set pointer field", "fieldName", fieldName, "foundKey", foundKey, "fieldPath", fieldPath) @@ -364,17 +364,17 @@ func (f *EnvFeeder) setPointerFieldFromEnvWithModule(field reflect.Value, envTag } else { // Record that we searched but didn't find f.ft.Record(FieldPopulation{ - FieldPath: fieldPath, - FieldName: fieldName, - FieldType: field.Type().String(), - FeederType: "*feeders.EnvFeeder", - SourceType: "env", - SourceKey: "", - Value: nil, - InstanceKey: "", - SearchKeys: searchKeys, - FoundKey: "", - }) + FieldPath: fieldPath, + FieldName: fieldName, + FieldType: field.Type().String(), + FeederType: "*feeders.EnvFeeder", + SourceType: "env", + SourceKey: "", + Value: nil, + InstanceKey: "", + SearchKeys: searchKeys, + FoundKey: "", + }) if f.verboseDebug && f.logger != nil { f.logger.Debug("EnvFeeder: Environment variable not found or empty for pointer field", "fieldName", fieldName, "searchKeys", searchKeys, "fieldPath", fieldPath) diff --git a/feeders/instance_aware_env.go b/feeders/instance_aware_env.go index 0926122a..03d77302 100644 --- a/feeders/instance_aware_env.go +++ b/feeders/instance_aware_env.go @@ -73,7 +73,7 @@ func (f *InstanceAwareEnvFeeder) Feed(structure interface{}) error { return ErrEnvInvalidStructure } - if inputType.Kind() != reflect.Ptr { + if inputType.Kind() != reflect.Pointer { if f.verboseDebug && f.logger != nil { f.logger.Debug("InstanceAwareEnvFeeder: Structure is not a pointer", "kind", inputType.Kind()) } @@ -119,7 +119,7 @@ func (f *InstanceAwareEnvFeeder) FeedKey(instanceKey string, structure interface return ErrEnvInvalidStructure } - if inputType.Kind() != reflect.Ptr { + if inputType.Kind() != reflect.Pointer { if f.verboseDebug && f.logger != nil { f.logger.Debug("InstanceAwareEnvFeeder: Structure is not a pointer", "instanceKey", instanceKey, "kind", inputType.Kind()) } @@ -344,17 +344,17 @@ func (f *InstanceAwareEnvFeeder) setFieldFromEnvWithPrefix(field reflect.Value, // Record field population if tracker is available f.ft.Record(FieldPopulation{ - FieldPath: fieldPath, - FieldName: fieldName, - FieldType: field.Type().String(), - FeederType: "*feeders.InstanceAwareEnvFeeder", - SourceType: "env", - SourceKey: envName, - Value: field.Interface(), - InstanceKey: instanceKey, - SearchKeys: searchKeys, - FoundKey: envName, - }) + FieldPath: fieldPath, + FieldName: fieldName, + FieldType: field.Type().String(), + FeederType: "*feeders.InstanceAwareEnvFeeder", + SourceType: "env", + SourceKey: envName, + Value: field.Interface(), + InstanceKey: instanceKey, + SearchKeys: searchKeys, + FoundKey: envName, + }) if f.verboseDebug && f.logger != nil { f.logger.Debug("InstanceAwareEnvFeeder: Successfully set field value", "envName", envName, "envValue", envValue, "fieldPath", fieldPath, "instanceKey", instanceKey) @@ -362,17 +362,17 @@ func (f *InstanceAwareEnvFeeder) setFieldFromEnvWithPrefix(field reflect.Value, } else { // Record that we searched but didn't find f.ft.Record(FieldPopulation{ - FieldPath: fieldPath, - FieldName: fieldName, - FieldType: field.Type().String(), - FeederType: "*feeders.InstanceAwareEnvFeeder", - SourceType: "env", - SourceKey: "", - Value: nil, - InstanceKey: instanceKey, - SearchKeys: searchKeys, - FoundKey: "", - }) + FieldPath: fieldPath, + FieldName: fieldName, + FieldType: field.Type().String(), + FeederType: "*feeders.InstanceAwareEnvFeeder", + SourceType: "env", + SourceKey: "", + Value: nil, + InstanceKey: instanceKey, + SearchKeys: searchKeys, + FoundKey: "", + }) if f.verboseDebug && f.logger != nil { f.logger.Debug("InstanceAwareEnvFeeder: Environment variable not found or empty", "envName", envName, "fieldPath", fieldPath, "instanceKey", instanceKey) diff --git a/feeders/json.go b/feeders/json.go index 814704ca..bc8854da 100644 --- a/feeders/json.go +++ b/feeders/json.go @@ -57,8 +57,8 @@ type JSONFeeder struct { logger interface { Debug(msg string, args ...any) } - ft FieldTrackerHolder - priority int + ft FieldTrackerHolder + priority int } // NewJSONFeeder creates a new JSONFeeder that reads from the specified JSON file @@ -153,7 +153,7 @@ func (j *JSONFeeder) feedWithTracking(structure interface{}) error { // Check if we're dealing with a struct pointer structValue := reflect.ValueOf(structure) - if structValue.Kind() != reflect.Ptr || structValue.Elem().Kind() != reflect.Struct { + if structValue.Kind() != reflect.Pointer || structValue.Elem().Kind() != reflect.Struct { // Not a struct pointer, fall back to standard JSON unmarshaling if j.verboseDebug && j.logger != nil { j.logger.Debug("JSONFeeder: Not a struct pointer, using standard JSON unmarshaling", "structureType", reflect.TypeOf(structure)) From 3890a6379f7717bf57b23a53a2e2efc19e34138e Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Wed, 6 May 2026 15:40:27 -0400 Subject: [PATCH 4/8] fix: address remaining feeder lint drift --- feeders/instance_aware_env.go | 2 +- feeders/json.go | 6 ++-- feeders/toml.go | 12 ++++---- feeders/yaml.go | 56 +++++++++++++++++------------------ 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/feeders/instance_aware_env.go b/feeders/instance_aware_env.go index 03d77302..51fc72e1 100644 --- a/feeders/instance_aware_env.go +++ b/feeders/instance_aware_env.go @@ -188,7 +188,7 @@ func (f *InstanceAwareEnvFeeder) FeedInstances(instances interface{}) error { var needsMapUpdate bool // Handle both pointer and non-pointer map values - if instance.Kind() == reflect.Ptr { + if instance.Kind() == reflect.Pointer { // Map values are already pointers - use them directly instancePtr = instance.Interface() needsMapUpdate = false // No need to update map since we're modifying the original pointer diff --git a/feeders/json.go b/feeders/json.go index bc8854da..9efc5ec5 100644 --- a/feeders/json.go +++ b/feeders/json.go @@ -220,7 +220,7 @@ func (j *JSONFeeder) processField(field reflect.Value, fieldType reflect.StructF fieldKind := field.Kind() switch fieldKind { - case reflect.Ptr: + case reflect.Pointer: // Handle pointer types return j.setPointerFromJSON(field, value, fieldPath) @@ -454,7 +454,7 @@ func (j *JSONFeeder) setSliceFromJSON(field reflect.Value, value interface{}, fi } else { return wrapJSONSliceElementError(item, elemType.String(), fieldPath, i) } - case reflect.Ptr: + case reflect.Pointer: // Handle slice of pointers if item == nil { // Set nil pointer @@ -553,7 +553,7 @@ func (j *JSONFeeder) setMapFromJSON(field reflect.Value, jsonData map[string]int } } } - case reflect.Ptr: + case reflect.Pointer: // Map of pointers to structs, like map[string]*DBConnection elemType := valueType.Elem() if elemType.Kind() == reflect.Struct { diff --git a/feeders/toml.go b/feeders/toml.go index d3ac64b9..2b411c9e 100644 --- a/feeders/toml.go +++ b/feeders/toml.go @@ -17,8 +17,8 @@ type TomlFeeder struct { logger interface { Debug(msg string, args ...any) } - ft FieldTrackerHolder - priority int + ft FieldTrackerHolder + priority int } // NewTomlFeeder creates a new TomlFeeder that reads from the specified TOML file @@ -113,7 +113,7 @@ func (t *TomlFeeder) feedWithTracking(structure interface{}) error { // Check if we're dealing with a struct pointer structValue := reflect.ValueOf(structure) - if structValue.Kind() != reflect.Ptr || structValue.Elem().Kind() != reflect.Struct { + if structValue.Kind() != reflect.Pointer || structValue.Elem().Kind() != reflect.Struct { // Not a struct pointer, fall back to standard TOML unmarshaling if t.verboseDebug && t.logger != nil { t.logger.Debug("TomlFeeder: Not a struct pointer, using standard TOML unmarshaling", "structureType", reflect.TypeOf(structure)) @@ -180,7 +180,7 @@ func (t *TomlFeeder) processField(field reflect.Value, fieldType reflect.StructF fieldKind := field.Kind() switch fieldKind { - case reflect.Ptr: + case reflect.Pointer: // Handle pointer types return t.setPointerFromTOML(field, value, fieldPath) @@ -428,7 +428,7 @@ func (t *TomlFeeder) setSliceFromTOML(field reflect.Value, value interface{}, fi } else { return wrapTomlSliceElementError(item, elemType.String(), fieldPath, i) } - case reflect.Ptr: + case reflect.Pointer: // Handle slice of pointers if item == nil { // Set nil pointer @@ -524,7 +524,7 @@ func (t *TomlFeeder) setMapFromTOML(field reflect.Value, tomlData map[string]int } } } - case reflect.Ptr: + case reflect.Pointer: // Map of pointers to structs, like map[string]*DBConnection elemType := valueType.Elem() if elemType.Kind() == reflect.Struct { diff --git a/feeders/yaml.go b/feeders/yaml.go index a6353437..64a03b08 100644 --- a/feeders/yaml.go +++ b/feeders/yaml.go @@ -57,7 +57,7 @@ type YamlFeeder struct { Path string verboseDebug bool debugFn func(string) - ft FieldTrackerHolder + ft FieldTrackerHolder priority int } @@ -193,7 +193,7 @@ func (y *YamlFeeder) feedWithTracking(structure interface{}) error { // Check if we're dealing with a struct pointer structValue := reflect.ValueOf(structure) - if structValue.Kind() != reflect.Ptr || structValue.Elem().Kind() != reflect.Struct { + if structValue.Kind() != reflect.Pointer || structValue.Elem().Kind() != reflect.Struct { // Not a struct pointer, fall back to standard YAML unmarshaling y.debugLog("YamlFeeder: Not a struct pointer, using standard YAML unmarshaling", "structureType", reflect.TypeOf(structure)) if err := yaml.Unmarshal(content, structure); err != nil { @@ -245,7 +245,7 @@ func (y *YamlFeeder) processField(field reflect.Value, fieldType *reflect.Struct fieldName, hasYAMLTag := getFieldNameFromTag(fieldType) switch field.Kind() { - case reflect.Ptr: + case reflect.Pointer: // Handle pointer types if hasYAMLTag { return y.setPointerFromYAML(field, fieldName, data, fieldType.Name, fieldPath) @@ -444,7 +444,7 @@ func (y *YamlFeeder) setSliceFromYAML(field reflect.Value, yamlTag string, data } else { return wrapYamlExpectedMapForSliceError(fieldPath, i, item) } - case reflect.Ptr: + case reflect.Pointer: // Handle slice of pointers if item == nil { // Set nil pointer @@ -577,33 +577,33 @@ func (y *YamlFeeder) setFieldFromYaml(field reflect.Value, yamlTag string, data // Record field population if tracker is available y.ft.Record(FieldPopulation{ - FieldPath: fieldPath, - FieldName: fieldName, - FieldType: field.Type().String(), - FeederType: "*feeders.YamlFeeder", - SourceType: "yaml", - SourceKey: foundKey, - Value: field.Interface(), - InstanceKey: "", - SearchKeys: searchKeys, - FoundKey: foundKey, - }) + FieldPath: fieldPath, + FieldName: fieldName, + FieldType: field.Type().String(), + FeederType: "*feeders.YamlFeeder", + SourceType: "yaml", + SourceKey: foundKey, + Value: field.Interface(), + InstanceKey: "", + SearchKeys: searchKeys, + FoundKey: foundKey, + }) y.debugLog("YamlFeeder: Successfully set field value", "fieldName", fieldName, "yamlKey", yamlTag, "value", foundValue, "fieldPath", fieldPath) } else { // Record that we searched but didn't find y.ft.Record(FieldPopulation{ - FieldPath: fieldPath, - FieldName: fieldName, - FieldType: field.Type().String(), - FeederType: "*feeders.YamlFeeder", - SourceType: "yaml", - SourceKey: "", - Value: nil, - InstanceKey: "", - SearchKeys: searchKeys, - FoundKey: "", - }) + FieldPath: fieldPath, + FieldName: fieldName, + FieldType: field.Type().String(), + FeederType: "*feeders.YamlFeeder", + SourceType: "yaml", + SourceKey: "", + Value: nil, + InstanceKey: "", + SearchKeys: searchKeys, + FoundKey: "", + }) y.debugLog("YamlFeeder: YAML value not found", "fieldName", fieldName, "yamlKey", yamlTag, "fieldPath", fieldPath) } @@ -647,7 +647,7 @@ func (y *YamlFeeder) setMapFromYaml(field reflect.Value, yamlData map[string]int y.debugLog("YamlFeeder: Map entry is not a map", "key", key, "valueType", reflect.TypeOf(value)) } } - case reflect.Ptr: + case reflect.Pointer: // Map of pointers to structs, like map[string]*DBConnection elemType := valueType.Elem() if elemType.Kind() == reflect.Struct { @@ -815,7 +815,7 @@ func (y *YamlFeeder) setFieldValue(field reflect.Value, value interface{}) error } case reflect.Invalid, reflect.Uintptr, reflect.Complex64, reflect.Complex128, reflect.Array, reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, - reflect.Ptr, reflect.Slice, reflect.Struct, reflect.UnsafePointer: + reflect.Pointer, reflect.Slice, reflect.Struct, reflect.UnsafePointer: return wrapYamlUnsupportedFieldTypeError(field.Type().String()) default: return wrapYamlUnsupportedFieldTypeError(field.Type().String()) From b4d48b3e3552fb985718c496673c192dc2555758 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Wed, 6 May 2026 15:50:12 -0400 Subject: [PATCH 5/8] test: stabilize eventlogger bdd scenarios --- eventlogger/bdd_error_handling_test.go | 35 +++++++++++-------- eventlogger/bdd_queue_management_test.go | 43 ++++++++++-------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/eventlogger/bdd_error_handling_test.go b/eventlogger/bdd_error_handling_test.go index 80c87e97..dbbe83ca 100644 --- a/eventlogger/bdd_error_handling_test.go +++ b/eventlogger/bdd_error_handling_test.go @@ -2,6 +2,7 @@ package eventlogger import ( "context" + "errors" "fmt" "os" "time" @@ -62,14 +63,17 @@ func (ctx *EventLoggerBDDTestContext) errorsShouldBeHandledGracefully() error { // Verify the module is still functional by emitting a test event event := modular.NewCloudEvent("graceful.test", "test-source", map[string]interface{}{"test": "data"}, nil) - err := ctx.service.OnEvent(context.Background(), event) - - // The module should handle this gracefully - if err != nil { - return fmt.Errorf("module should handle events gracefully: %v", err) + deadline := time.Now().Add(500 * time.Millisecond) + for { + err := ctx.service.OnEvent(context.Background(), event) + if err == nil { + return nil + } + if !errors.Is(err, ErrEventBufferFull) || time.Now().After(deadline) { + return fmt.Errorf("module should handle events gracefully: %v", err) + } + time.Sleep(10 * time.Millisecond) } - - return nil } func (ctx *EventLoggerBDDTestContext) otherOutputTargetsShouldContinueWorking() error { @@ -82,14 +86,17 @@ func (ctx *EventLoggerBDDTestContext) otherOutputTargetsShouldContinueWorking() // Emit a test event to verify other outputs still work event := modular.NewCloudEvent("test.recovery", "test-source", map[string]interface{}{"test": "recovery"}, nil) - err := ctx.service.OnEvent(context.Background(), event) - - // The error handling should ensure this succeeds even with faulty targets - if err != nil { - return fmt.Errorf("other output targets failed to work after error: %v", err) + deadline := time.Now().Add(500 * time.Millisecond) + for { + err := ctx.service.OnEvent(context.Background(), event) + if err == nil { + return nil + } + if !errors.Is(err, ErrEventBufferFull) || time.Now().After(deadline) { + return fmt.Errorf("other output targets failed to work after error: %v", err) + } + time.Sleep(10 * time.Millisecond) } - - return nil } func (ctx *EventLoggerBDDTestContext) iHaveAnEventLoggerWithFaultyOutputTargetAndEventObservationEnabled() error { diff --git a/eventlogger/bdd_queue_management_test.go b/eventlogger/bdd_queue_management_test.go index 4e5c2b5d..07700d48 100644 --- a/eventlogger/bdd_queue_management_test.go +++ b/eventlogger/bdd_queue_management_test.go @@ -119,6 +119,9 @@ func (ctx *EventLoggerBDDTestContext) theEventsShouldBeQueuedWithoutErrors() err } func (ctx *EventLoggerBDDTestContext) theEventloggerStarts() error { + if ctx.app == nil && ctx.service != nil { + return ctx.service.Start(context.Background()) + } return ctx.app.Start() } @@ -222,35 +225,21 @@ func (ctx *EventLoggerBDDTestContext) iHaveAnEventLoggerModuleConfiguredWithQueu return err } - // Create config with console output config := ctx.createConsoleConfig(10) - // Create application with the config - err = ctx.createApplicationWithConfig(config) - if err != nil { - return err - } - - // Initialize the module but DON'T start it yet - err = ctx.theEventLoggerModuleIsInitialized() - if err != nil { - return err - } - - // Get service reference - err = ctx.theEventLoggerServiceShouldBeAvailable() - if err != nil { - return err - } - // Inject test console output for capturing logs ctx.testConsole = &testConsoleOutput{baseTestOutput: baseTestOutput{logs: make([]string, 0)}} - ctx.service.setOutputsForTesting([]OutputTarget{ctx.testConsole}) - - // Artificially reduce queue size for testing overflow - ctx.service.mutex.Lock() - ctx.service.queueMaxSize = 3 // Small queue for testing overflow - ctx.service.mutex.Unlock() + ctx.config = config + ctx.service = &EventLoggerModule{ + name: ModuleName, + config: config, + outputs: []OutputTarget{ctx.testConsole}, + eventChan: make(chan cloudevents.Event, config.BufferSize), + stopChan: make(chan struct{}), + eventQueue: make([]cloudevents.Event, 0, 3), + queueMaxSize: 3, + } + ctx.module = ctx.service return nil } @@ -313,6 +302,10 @@ func (ctx *EventLoggerBDDTestContext) newerEventsShouldBePreservedInTheQueue() e } func (ctx *EventLoggerBDDTestContext) onlyThePreservedEventsShouldBeProcessed() error { + if ctx.app == nil && ctx.service != nil { + defer func() { _ = ctx.service.Stop(context.Background()) }() + } + // Wait longer for events to be processed with polling for queue clearance maxWait := 2 * time.Second checkInterval := 50 * time.Millisecond From 31587c0864cb41130094c80c5c47027a6a97854f Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Wed, 6 May 2026 15:55:43 -0400 Subject: [PATCH 6/8] fix: use current reflect pointer kind --- chimux/module.go | 2 +- cmd/modcli/cmd/debug.go | 2 +- service.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chimux/module.go b/chimux/module.go index 61616947..af90d9bc 100644 --- a/chimux/module.go +++ b/chimux/module.go @@ -378,7 +378,7 @@ func (m *ChiMuxModule) setupMiddleware(app modular.Application) error { middlewareProviderType := reflect.TypeOf((*MiddlewareProvider)(nil)).Elem() if serviceType.Implements(middlewareProviderType) || - (serviceType.Kind() == reflect.Ptr && serviceType.Elem().Implements(middlewareProviderType)) { + (serviceType.Kind() == reflect.Pointer && serviceType.Elem().Implements(middlewareProviderType)) { if provider, ok := service.(MiddlewareProvider); ok { middlewareProviders = append(middlewareProviders, provider) m.logger.Debug("Found middleware provider", "name", name) diff --git a/cmd/modcli/cmd/debug.go b/cmd/modcli/cmd/debug.go index c2618d19..658eb442 100644 --- a/cmd/modcli/cmd/debug.go +++ b/cmd/modcli/cmd/debug.go @@ -207,7 +207,7 @@ func runDebugInterface(cmd *cobra.Command, args []string) error { fmt.Fprintf(cmd.OutOrStdout(), "1. Load type '%s' using reflection\n", typeName) fmt.Fprintf(cmd.OutOrStdout(), "2. Load interface '%s' using reflection\n", interfaceName) fmt.Fprintf(cmd.OutOrStdout(), "3. Check: serviceType.Implements(interfaceType)\n") - fmt.Fprintf(cmd.OutOrStdout(), "4. Check: serviceType.Kind() == reflect.Ptr && serviceType.Elem().Implements(interfaceType)\n") + fmt.Fprintf(cmd.OutOrStdout(), "4. Check: serviceType.Kind() == reflect.Pointer && serviceType.Elem().Implements(interfaceType)\n") fmt.Fprintln(cmd.OutOrStdout()) } diff --git a/service.go b/service.go index d7d2d475..990d6930 100644 --- a/service.go +++ b/service.go @@ -254,7 +254,7 @@ func (r *EnhancedServiceRegistry) generateUniqueName(originalName, moduleName st // Still conflicts - try with module type name if moduleType != nil { var typeName string - if moduleType.Kind() == reflect.Ptr { + if moduleType.Kind() == reflect.Pointer { typeName = moduleType.Elem().Name() } if typeName == "" { From 56ad8415976ff8c69188e69353338d1708f2730f Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Wed, 6 May 2026 16:00:03 -0400 Subject: [PATCH 7/8] test: cover pointer kind compatibility paths --- chimux/module_test.go | 5 +++++ cmd/modcli/cmd/debug_test.go | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/chimux/module_test.go b/chimux/module_test.go index 99bb813d..76bc9a62 100644 --- a/chimux/module_test.go +++ b/chimux/module_test.go @@ -185,6 +185,11 @@ func TestModule_CustomMiddleware(t *testing.T) { err = mockApp.RegisterService("test.middleware.provider", middlewareProvider) require.NoError(t, err) + // Register a pointer service that does not implement MiddlewareProvider so + // setupMiddleware exercises its pointer-kind interface check. + err = mockApp.RegisterService("test.non.middleware", &struct{ Name string }{Name: "not-middleware"}) + require.NoError(t, err) + // Register observers before Init err = module.RegisterObservers(mockApp) require.NoError(t, err) diff --git a/cmd/modcli/cmd/debug_test.go b/cmd/modcli/cmd/debug_test.go index e084ae4b..dd09971e 100644 --- a/cmd/modcli/cmd/debug_test.go +++ b/cmd/modcli/cmd/debug_test.go @@ -232,6 +232,23 @@ func TestDebugDependenciesCommand(t *testing.T) { } } +func TestDebugInterfaceCommandUnknownPatternShowsPointerKindCheck(t *testing.T) { + cmd := NewDebugInterfaceCommand() + + var buf bytes.Buffer + cmd.SetOut(&buf) + cmd.SetErr(&buf) + cmd.SetArgs([]string{ + "--type", "unknown.Service", + "--interface", "unknown.Interface", + }) + + err := cmd.Execute() + require.NoError(t, err) + + assert.Contains(t, buf.String(), "serviceType.Kind() == reflect.Pointer") +} + func TestDebugConfigTreeStructure(t *testing.T) { tmpDir := createTestProject(t) defer os.RemoveAll(tmpDir) From 11794484758ef071580be85ce983d894b2110bc5 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Wed, 6 May 2026 16:04:19 -0400 Subject: [PATCH 8/8] test: cover panic wrapping paths --- contract_verifier_test.go | 45 +++++++++++++++++++++++++++++++++++++++ parallel_init_test.go | 30 ++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/contract_verifier_test.go b/contract_verifier_test.go index d5520c3d..34e90c8e 100644 --- a/contract_verifier_test.go +++ b/contract_verifier_test.go @@ -2,6 +2,7 @@ package modular import ( "context" + "strings" "testing" "time" ) @@ -30,6 +31,15 @@ type panickyReloadable struct{ wellBehavedReloadable } func (p *panickyReloadable) CanReload() bool { panic("boom") } +type reloadPanicReloadable struct{ wellBehavedReloadable } + +func (p *reloadPanicReloadable) Reload(ctx context.Context, _ []ConfigChange) error { + if err := ctx.Err(); err != nil { + return err + } + panic("reload boom") +} + // --- Mock HealthProviders for contract tests --- // wellBehavedHealthProvider returns a proper report and respects cancellation. @@ -81,6 +91,15 @@ func (c *cancelIgnoringHealthProvider) HealthCheck(_ context.Context) ([]HealthR }, nil } +type panicOnActiveHealthProvider struct{} + +func (p *panicOnActiveHealthProvider) HealthCheck(ctx context.Context) ([]HealthReport, error) { + if err := ctx.Err(); err != nil { + return nil, err + } + panic("health boom") +} + // --- Tests --- func TestContractVerifier_ReloadWellBehaved(t *testing.T) { @@ -123,6 +142,23 @@ func TestContractVerifier_ReloadPanicsOnCanReload(t *testing.T) { } } +func TestContractVerifier_ReloadPanicUsesSentinelError(t *testing.T) { + verifier := NewStandardContractVerifier() + violations := verifier.VerifyReloadContract(&reloadPanicReloadable{}) + + found := false + for _, v := range violations { + if v.Rule == "empty-reload-must-be-idempotent" && + strings.Contains(v.Description, ErrReloadPanic.Error()) { + found = true + break + } + } + if !found { + t.Fatalf("expected reload panic sentinel in violations, got: %+v", violations) + } +} + func TestContractVerifier_HealthWellBehaved(t *testing.T) { verifier := NewStandardContractVerifier() violations := verifier.VerifyHealthContract(&wellBehavedHealthProvider{}) @@ -162,3 +198,12 @@ func TestContractVerifier_HealthIgnoresCancellation(t *testing.T) { t.Fatalf("expected violation for ignoring cancellation, got: %+v", violations) } } + +func TestContractVerifier_HealthPanicIsGuarded(t *testing.T) { + verifier := NewStandardContractVerifier() + + // The first HealthCheck call panics and is recovered by the verifier. The + // cancellation check returns ctx.Err, so the test only fails if the guard is + // not active. + _ = verifier.VerifyHealthContract(&panicOnActiveHealthProvider{}) +} diff --git a/parallel_init_test.go b/parallel_init_test.go index 35fce24a..5d8e15a6 100644 --- a/parallel_init_test.go +++ b/parallel_init_test.go @@ -1,6 +1,7 @@ package modular import ( + "errors" "sync" "sync/atomic" "testing" @@ -95,6 +96,25 @@ func TestWithParallelInit_RespectsDepOrder(t *testing.T) { } } +func TestWithParallelInit_RecoversModulePanic(t *testing.T) { + var initCount, maxPar, curPar atomic.Int32 + okModule := ¶llelInitModule{name: "ok", initDelay: time.Millisecond, initCount: &initCount, maxPar: &maxPar, curPar: &curPar} + + app, err := NewApplication( + WithLogger(nopLogger{}), + WithModules(okModule, &panicInitModule{name: "panic"}), + WithParallelInit(), + ) + if err != nil { + t.Fatalf("NewApplication: %v", err) + } + + err = app.Init() + if !errors.Is(err, ErrModuleInitializationPanic) { + t.Fatalf("expected ErrModuleInitializationPanic, got %v", err) + } +} + type simpleOrderModule struct { name string deps []string @@ -110,3 +130,13 @@ func (m *simpleOrderModule) Init(app Application) error { m.mu.Unlock() return nil } + +type panicInitModule struct { + name string +} + +func (m *panicInitModule) Name() string { return m.name } +func (m *panicInitModule) Dependencies() []string { return nil } +func (m *panicInitModule) Init(Application) error { + panic("init boom") +}