Skip to content

support flattened map struct binding in strict mode#43

Merged
Azhovan merged 3 commits intomainfrom
feature/support-flattened-map-structs
Feb 23, 2026
Merged

support flattened map struct binding in strict mode#43
Azhovan merged 3 commits intomainfrom
feature/support-flattened-map-structs

Conversation

@asadijabar
Copy link
Collaborator

@asadijabar asadijabar commented Feb 23, 2026

What

Complete nested collection binding support for map[string]T where T is a struct in the built-in file-source flattening path:

  • Bind []T where T is a struct from YAML-decoded collection values (existing support from the prior patch remains).
  • Bind map[string]T where T is a struct from:
    • direct nested map values, and
    • flattened dotted keys produced by the built-in file source (for example, clickhouse_map.primary.host).
  • Update strict-mode unknown-key validation to allow valid dynamic nested keys under map[string]T fields while still rejecting unknown nested fields.
  • Add loader regression tests covering:
    • flattened dotted-key binding success for map[string]T
    • strict-mode acceptance of valid nested keys
    • strict-mode rejection of unknown nested keys
    • Strict(false) behavior
    • invalid nested type conversion (invalid_type)

Docs: Updated docs/configuration-sources.md (and docs/api-reference.md if included) with a map[string]Struct YAML example and behavior notes.

Why

Rigging’s file source flattens nested YAML maps into dotted keys (for example, clickhouse_map.primary.host), which previously prevented binding into map[string]Struct fields and caused strict-mode false positives for valid nested keys.

This PR completes support for a common typed YAML config shape while preserving existing public APIs and source precedence behavior.

Type

  • Fix
  • Feature
  • Docs
  • Performance
  • Breaking change

Testing

Executed:

gofmt -w binding.go loader.go loader_test.go
go test ./...   # executed with temporary GOCACHE/GOTMPDIR overrides

Checklist
[x] Tests pass (go test ./...)
[x] Formatted (gofmt -s -w .)
[x] No vet warnings (go vet ./...)
[x] Coverage maintained (>70%)
[x] Added tests if needed
[x] Updated docs if needed

fully addresses #33

Copilot AI review requested due to automatic review settings February 23, 2026 17:08
@asadijabar asadijabar self-assigned this Feb 23, 2026
@asadijabar asadijabar requested review from Azhovan and Copilot and removed request for Copilot February 23, 2026 17:08
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

Adds full support for binding map[string]Struct from flattened dotted keys produced by the file source, while updating strict-mode unknown-key validation to permit valid dynamic map-entry keys and adding regression tests.

Changes:

  • Extend strict-mode unknown-key checking to allow dotted keys that match map[string]T entry patterns (including nested struct fields).
  • Synthesize nested map values from flattened dotted keys during binding when the base map key isn’t present.
  • Add loader regression tests for flattened map binding, strict-mode behavior, and invalid type conversion.

Reviewed changes

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

File Description
loader.go Adds dynamic map-key pattern collection/matching to strict-mode unknown key validation.
binding.go Synthesizes nested map entries from flattened dotted keys; adjusts provenance recording for synthesized entries.
loader_test.go Adds tests covering flattened map[string]Struct binding and strict-mode validation behavior.
Comments suppressed due to low confidence (1)

binding.go:560

  • In synthesizeNestedMapEntry, mixedSources is set when either sourceName or sourceKey differs. For env sources, sourceKey is per-variable (e.g. env:APP_FOO), so a synthesized map built entirely from env vars will be treated as “mixed” and provenance gets cleared even though all entries come from the same source. Consider treating the map as mixed only when sourceName differs; when only sourceKey differs, keep sourceName and clear sourceKey so the map field still reports it came from the env/file source (without pretending a single variable/key produced the whole map).
	var sourceName string
	var sourceKey string
	found := false
	mixedSources := false

	for dataKey, entry := range data {
		if !strings.HasPrefix(dataKey, prefix) {
			continue
		}

		parts := strings.Split(strings.TrimPrefix(dataKey, prefix), ".")
		if len(parts) == 0 || parts[0] == "" {
			continue
		}

		setNestedMapValue(nested, parts, entry.value)
		if !found {
			sourceName = entry.sourceName
			sourceKey = entry.sourceKey
			found = true
			continue
		}

		if sourceName != entry.sourceName || sourceKey != entry.sourceKey {
			mixedSources = true
		}
	}

	if !found {
		return mergedEntry{}, false
	}

	if mixedSources {
		// A single field-level provenance source would be misleading for a synthesized mixed-source map.
		sourceName = ""
		sourceKey = ""
	}

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

Azhovan
Azhovan previously approved these changes Feb 23, 2026
@Azhovan Azhovan added documentation Improvements or additions to documentation enhancement New feature or request labels Feb 23, 2026
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

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

loader.go:327

  • After unwrapping an Optional type, the inner type should be checked for pointers and dereferenced if necessary before the struct check. Currently, if valueType is Optional[*StructType], the code would get the *StructType but not dereference it before checking if it's a struct at line 329. Consider adding pointer dereferencing after unwrapping Optional types to handle edge cases like Optional[*StructType] or Optional[map[string]*StructType].
	if isOptionalType(valueType) {
		valueType = valueType.Field(0).Type
	}

loader.go:282

  • After unwrapping an Optional type at line 281, the inner type should be checked for pointers and dereferenced if necessary. Currently, if unwrappedType is Optional[*MapType] or Optional[*StructType], the code would not properly dereference the pointer before checking the kind. Consider adding pointer dereferencing after line 282 to handle these edge cases.
			if isOptionalType(unwrappedType) {
				unwrappedType = unwrappedType.Field(0).Type
			}

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

@Azhovan Azhovan merged commit 4d4ac90 into main Feb 23, 2026
9 checks passed
@github-actions github-actions bot deleted the feature/support-flattened-map-structs branch February 23, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants