This commit introduces the ability to handle nested fields within log entries. The file has been updated with a function that transforms flat keys with a : delimiter (e.g., obj:foo) into nested JSON objects (e.g., ). The file includes new test cases to verify that: - Nested fields are correctly unmarshalled from string representations. - Nested fields are correctly marshalled into JSON objects. This enhancement allows for more structured and organized metadata within log entries.
123 lines
2.6 KiB
Go
123 lines
2.6 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// A slice of Meta
|
|
type Metas []Meta
|
|
|
|
// Returns a single map containing all the Meta. Is useful when encoding to JSON
|
|
func (ms Metas) Map() map[string]any {
|
|
out := map[string]any{}
|
|
for _, f := range ms {
|
|
if _, found := out[f.Key]; found || f.Key == "title" || f.Key == "date" {
|
|
continue
|
|
}
|
|
if f.Key == "json" {
|
|
ob := map[string]any{}
|
|
if j, ok := f.Value.(json.RawMessage); ok {
|
|
json.Unmarshal(j, &ob)
|
|
}
|
|
// If we couldn't get valid data from there, this will just be empty
|
|
for k, v := range ob {
|
|
if k != "title" && k != "date" {
|
|
out[k] = v
|
|
}
|
|
}
|
|
} else {
|
|
out[f.Key] = f.Value
|
|
if vt, ok := f.Value.(time.Time); ok {
|
|
out[f.Key] = vt.Format(time.RFC3339)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Next we have to go through them again to find nested fields
|
|
parseNestedFields(out)
|
|
|
|
return out
|
|
}
|
|
|
|
func parseNestedFields(f map[string]any) {
|
|
for k, v := range f {
|
|
if strings.Contains(k, ":") {
|
|
idx := strings.Index(k, ":")
|
|
top := k[:idx]
|
|
bottom := k[(idx + 1):]
|
|
|
|
nest, ok := f[top].(map[string]any)
|
|
if !ok {
|
|
nest = map[string]any{}
|
|
}
|
|
nest[bottom] = v
|
|
parseNestedFields(nest)
|
|
f[top] = nest
|
|
delete(f, k)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Implements json.Marshaler
|
|
func (ms Metas) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(ms.Map())
|
|
}
|
|
|
|
// Implements json.Unmarshaler
|
|
func (ms *Metas) UnmarshalJSON(in []byte) error {
|
|
old := (*ms).Map()
|
|
out := map[string]any{}
|
|
err := json.Unmarshal(in, &out)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ret := *ms
|
|
for k, v := range out {
|
|
if _, found := old[k]; k != "title" && k != "date" && !found {
|
|
ret = append(ret, Meta{k, v})
|
|
}
|
|
}
|
|
*ms = ret
|
|
return nil
|
|
}
|
|
|
|
// Returns a new Metas with a new Meta appended
|
|
func (ms Metas) Append(k string, v any) Metas {
|
|
return append(ms, Meta{k, v})
|
|
}
|
|
|
|
// Appends a new Meta to this Metas
|
|
func (ms *Metas) AppendTo(k string, v any) {
|
|
n := (*ms).Append(k, v)
|
|
*ms = n
|
|
}
|
|
|
|
// Returns the value of the Meta with the given key, and a bool indicating if it was found
|
|
func (ms Metas) Get(key string) (any, bool) {
|
|
_, m, ok := ms.findVal(key)
|
|
return m.Value, ok
|
|
}
|
|
|
|
// Set sets the key to the given value.
|
|
// If key is already in the ms, Set updates the value of the found Meta.
|
|
// If key is not in ms, then we append a new Meta and return the modified Metas.
|
|
func (ms Metas) Set(key string, value any) Metas {
|
|
idx, m, found := ms.findVal(key)
|
|
if !found {
|
|
return ms.Append(key, value)
|
|
}
|
|
m.Value = value
|
|
ms[idx] = m
|
|
return ms
|
|
}
|
|
|
|
func (ms Metas) findVal(key string) (idx int, m Meta, found bool) {
|
|
for idx, m = range ms {
|
|
if m.Key == key {
|
|
return idx, m, true
|
|
}
|
|
}
|
|
return 0, Meta{}, false
|
|
}
|