Skip to content
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestErrorMessages(t *testing.T) {
cNested := New()
cNestedMeta := New()

testMeta := &Meta{"test.source"}
testMeta := &Meta{Source: "test.source"}
cMeta.metadata = testMeta
cNestedMeta.metadata = testMeta

Expand Down
66 changes: 49 additions & 17 deletions merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
// Merge traverses the value from recursively copying all values into a hierarchy
// of Config objects plus primitives into c.
//
// Merge supports the options: PathSep, MetaData, StructTag, VarExp, ReplaceValues, AppendValues, PrependValues
// Merge supports the options: PathSep, MetaData, StructTag, VarExp, ReplaceValues, AppendValues, PrependValues, ShowRedacted
//
// Merge uses the type-dependent default encodings:
// - Boolean values are encoded as booleans.
Expand Down Expand Up @@ -62,6 +62,9 @@ import (
// // field is ignored by Merge
// Field string `config:",ignore"`
//
// // field is automatically replaced with "[REDACTED]" (use ShowRedacted option to preserve original value)
// Field string `config:",redact"`
//
// Returns an error if merging fails to normalize and validate the from value.
// If duplicate setting names are detected in the input, merging fails as well.
//
Expand Down Expand Up @@ -460,32 +463,61 @@ func normalizeValue(
) (value, Error) {
v = chaseValue(v)

// Handle redaction based on tag and option
// Redaction only applies to string and []byte types
meta := opts.meta
if tagOpts.redact && (v.Kind() == reflect.String || (v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8)) {
Comment thread
michel-laterman marked this conversation as resolved.
Outdated
if !opts.showRedacted {
// Default behavior: replace with redaction string
// Keep metadata with redacted flag
redactedMeta := opts.meta
if redactedMeta == nil {
redactedMeta = &Meta{Redacted: true}
} else {
metaCopy := *redactedMeta
metaCopy.Redacted = true
redactedMeta = &metaCopy
}
return newString(ctx, redactedMeta, sREDACT), nil
} else {
// ShowRedacted option: preserve original value but mark metadata
if meta == nil {
meta = &Meta{Redacted: true}
} else {
// Create a copy to avoid modifying shared metadata
metaCopy := *meta
metaCopy.Redacted = true
meta = &metaCopy
}
}
Comment thread
michel-laterman marked this conversation as resolved.
}

switch v.Type() {
case tDuration:
d := v.Interface().(time.Duration)
return newString(ctx, opts.meta, d.String()), nil
return newString(ctx, meta, d.String()), nil
case tRegexp:
r := v.Addr().Interface().(*regexp.Regexp)
return newString(ctx, opts.meta, r.String()), nil
return newString(ctx, meta, r.String()), nil
}

// handle primitives
switch v.Kind() {
case reflect.Bool:
return newBool(ctx, opts.meta, v.Bool()), nil
return newBool(ctx, meta, v.Bool()), nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i := v.Int()
if i > 0 {
return newUint(ctx, opts.meta, uint64(i)), nil
return newUint(ctx, meta, uint64(i)), nil
}
return newInt(ctx, opts.meta, i), nil
return newInt(ctx, meta, i), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return newUint(ctx, opts.meta, v.Uint()), nil
return newUint(ctx, meta, v.Uint()), nil
case reflect.Float32, reflect.Float64:
f := v.Float()
return newFloat(ctx, opts.meta, f), nil
return newFloat(ctx, meta, f), nil
case reflect.String:
return normalizeString(ctx, opts, v.String())
return normalizeString(ctx, opts, meta, v.String())
case reflect.Array, reflect.Slice:
return normalizeArray(opts, tagOpts, ctx, v)
case reflect.Map:
Expand All @@ -503,30 +535,30 @@ func normalizeValue(
return normalizeStructValue(opts, ctx, v)
default:
if v.IsNil() {
return &cfgNil{cfgPrimitive{ctx, opts.meta}}, nil
return &cfgNil{cfgPrimitive{ctx, meta}}, nil
}
return nil, raiseUnsupportedInputType(ctx, opts.meta, v)
return nil, raiseUnsupportedInputType(ctx, meta, v)
}
}

func normalizeString(ctx context, opts *options, str string) (value, Error) {
func normalizeString(ctx context, opts *options, meta *Meta, str string) (value, Error) {
if !opts.varexp {
return newString(ctx, opts.meta, str), nil
return newString(ctx, meta, str), nil
}

varexp, err := parseSplice(str, opts.pathSep, opts.maxIdx, opts.enableNumKeys, opts.escapePath)
if err != nil {
return nil, raiseParseSplice(ctx, opts.meta, err)
return nil, raiseParseSplice(ctx, meta, err)
}

switch p := varexp.(type) {
case constExp:
return newString(ctx, opts.meta, string(p)), nil
return newString(ctx, meta, string(p)), nil
case *reference:
return newRef(ctx, opts.meta, p), nil
return newRef(ctx, meta, p), nil
}

return newSplice(ctx, opts.meta, varexp), nil
return newSplice(ctx, meta, varexp), nil
}

func fieldOptsOverride(opts *options, fieldName string, idx int) (*options, Error) {
Expand Down
10 changes: 10 additions & 0 deletions opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type options struct {
configuredFields *fieldSet

ignoreCommas bool
showRedacted bool // When true, shows unredacted values for fields marked with redact tag
}

type valueCache map[string]spliceValue
Expand Down Expand Up @@ -195,6 +196,15 @@ func doResolveNOOP(o *options) {
})
}

// ShowRedacted option disables automatic redaction of fields marked with the `redact` tag.
// By default, fields with the `redact` tag are automatically replaced with "[REDACTED]"
// during Merge/NewFrom operations. Use this option to preserve the original unredacted values.
var ShowRedacted Option = doShowRedacted

func doShowRedacted(o *options) {
o.showRedacted = true
}

var (
// ReplaceValues option configures all merging and unpacking operations to
// replace old dictionaries and arrays while merging. Value merging can be
Expand Down
Loading