transform: Add support for "format" option in transform.Unmarshal

Fixes #13887
This commit is contained in:
n1xx1
2025-08-05 22:19:51 +02:00
committed by GitHub
parent de4a7f1e04
commit ecc3dd1f53
3 changed files with 39 additions and 6 deletions

View File

@@ -36,6 +36,10 @@ import (
// Decoder provides some configuration options for the decoders. // Decoder provides some configuration options for the decoders.
type Decoder struct { type Decoder struct {
// Format specifies a specific format to decode from. If empty or
// unspecified, it's inferred from the contents or the filename.
Format string
// Delimiter is the field delimiter. Used in the CSV decoder. Default is // Delimiter is the field delimiter. Used in the CSV decoder. Default is
// ','. // ','.
Delimiter rune Delimiter rune
@@ -57,6 +61,7 @@ type Decoder struct {
// OptionsKey is used in cache keys. // OptionsKey is used in cache keys.
func (d Decoder) OptionsKey() string { func (d Decoder) OptionsKey() string {
var sb strings.Builder var sb strings.Builder
sb.WriteString(d.Format)
sb.WriteRune(d.Delimiter) sb.WriteRune(d.Delimiter)
sb.WriteRune(d.Comment) sb.WriteRune(d.Comment)
sb.WriteString(strconv.FormatBool(d.LazyQuotes)) sb.WriteString(strconv.FormatBool(d.LazyQuotes))

View File

@@ -72,9 +72,17 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) {
} }
v, err := ns.cacheUnmarshal.GetOrCreate(key, func(string) (*resources.StaleValue[any], error) { v, err := ns.cacheUnmarshal.GetOrCreate(key, func(string) (*resources.StaleValue[any], error) {
f := metadecoders.FormatFromStrings(r.MediaType().Suffixes()...) var f metadecoders.Format
if f == "" { if decoder.Format != "" {
return nil, fmt.Errorf("MIME %q not supported", r.MediaType()) f = metadecoders.FormatFromString(decoder.Format)
if f == "" {
return nil, fmt.Errorf("format %q not supported", decoder.Format)
}
} else {
f = metadecoders.FormatFromStrings(r.MediaType().Suffixes()...)
if f == "" {
return nil, fmt.Errorf("MIME %q not supported", r.MediaType())
}
} }
reader, err := r.ReadSeekCloser() reader, err := r.ReadSeekCloser()
@@ -119,10 +127,22 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) {
key := hashing.MD5FromStringHexEncoded(dataStr) key := hashing.MD5FromStringHexEncoded(dataStr)
if decoder != metadecoders.Default {
key += decoder.OptionsKey()
}
v, err := ns.cacheUnmarshal.GetOrCreate(key, func(string) (*resources.StaleValue[any], error) { v, err := ns.cacheUnmarshal.GetOrCreate(key, func(string) (*resources.StaleValue[any], error) {
f := decoder.FormatFromContentString(dataStr) var f metadecoders.Format
if f == "" { if decoder.Format != "" {
return nil, errors.New("unknown format") f = metadecoders.FormatFromString(decoder.Format)
if f == "" {
return nil, fmt.Errorf("format %q not supported", decoder.Format)
}
} else {
f = decoder.FormatFromContentString(dataStr)
if f == "" {
return nil, errors.New("unknown format")
}
} }
v, err := decoder.Unmarshal([]byte(dataStr), f) v, err := decoder.Unmarshal([]byte(dataStr), f)

View File

@@ -128,6 +128,9 @@ func TestUnmarshal(t *testing.T) {
{testContentResource{key: "r1", content: `a;b;c`, mime: media.Builtin.CSVType}, map[string]any{"delimiter": ";"}, func(r [][]string) { {testContentResource{key: "r1", content: `a;b;c`, mime: media.Builtin.CSVType}, map[string]any{"delimiter": ";"}, func(r [][]string) {
b.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r) b.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r)
}}, }},
{testContentResource{key: "r1", content: `{p: [a, b]}`, mime: media.Builtin.JSONType}, map[string]any{"format": "yaml"}, func(m map[string]any) {
b.Assert(m, qt.DeepEquals, map[string]any{"p": []any{"a", "b"}})
}},
{"a,b,c", nil, func(r [][]string) { {"a,b,c", nil, func(r [][]string) {
b.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r) b.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r)
}}, }},
@@ -141,6 +144,9 @@ a;b;c`, mime: media.Builtin.CSVType}, map[string]any{"DElimiter": ";", "Comment"
}}, }},
{``, nil, nil}, {``, nil, nil},
{` `, nil, nil}, {` `, nil, nil},
{`{p: [a, b]}`, map[string]any{"format": "yaml"}, func(m map[string]any) {
b.Assert(m, qt.DeepEquals, map[string]any{"p": []any{"a", "b"}})
}},
// errors // errors
{"thisisnotavaliddataformat", nil, false}, {"thisisnotavaliddataformat", nil, false},
{testContentResource{key: "r1", content: `invalid&toml"`, mime: media.Builtin.TOMLType}, nil, false}, {testContentResource{key: "r1", content: `invalid&toml"`, mime: media.Builtin.TOMLType}, nil, false},
@@ -148,6 +154,8 @@ a;b;c`, mime: media.Builtin.CSVType}, map[string]any{"DElimiter": ";", "Comment"
{"thisisnotavaliddataformat", nil, false}, {"thisisnotavaliddataformat", nil, false},
{`{ notjson }`, nil, false}, {`{ notjson }`, nil, false},
{tstNoStringer{}, nil, false}, {tstNoStringer{}, nil, false},
{"<root><a>notjson</a></root>", map[string]any{"format": "json"}, false},
{"anything", map[string]any{"format": "invalidformat"}, false},
} { } {
ns.Reset() ns.Reset()