2018-10-31 16:01:17 -04:00
|
|
|
package stores
|
|
|
|
|
|
|
|
|
|
import (
|
2023-11-17 14:55:14 -08:00
|
|
|
"encoding/json"
|
2018-10-31 16:01:17 -04:00
|
|
|
"fmt"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
)
|
|
|
|
|
|
2018-10-31 22:44:08 +01:00
|
|
|
const mapSeparator = "__map_"
|
|
|
|
|
const listSeparator = "__list_"
|
2018-10-31 16:01:17 -04:00
|
|
|
|
|
|
|
|
// flattenAndMerge flattens the provided value and merges into the
|
|
|
|
|
// into map using prefix
|
|
|
|
|
func flattenAndMerge(into map[string]interface{}, prefix string, value interface{}) {
|
|
|
|
|
flattenedValue := flattenValue(value)
|
|
|
|
|
if flattenedValue, ok := flattenedValue.(map[string]interface{}); ok {
|
|
|
|
|
for flatK, flatV := range flattenedValue {
|
|
|
|
|
into[prefix+flatK] = flatV
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
into[prefix] = value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func flattenValue(value interface{}) interface{} {
|
|
|
|
|
var output interface{}
|
|
|
|
|
switch value := value.(type) {
|
|
|
|
|
case map[string]interface{}:
|
|
|
|
|
newMap := make(map[string]interface{})
|
|
|
|
|
for k, v := range value {
|
2018-10-31 22:44:08 +01:00
|
|
|
flattenAndMerge(newMap, mapSeparator+k, v)
|
2018-10-31 16:01:17 -04:00
|
|
|
}
|
|
|
|
|
output = newMap
|
|
|
|
|
case []interface{}:
|
|
|
|
|
newMap := make(map[string]interface{})
|
|
|
|
|
for i, v := range value {
|
2018-10-31 22:44:08 +01:00
|
|
|
flattenAndMerge(newMap, listSeparator+fmt.Sprintf("%d", i), v)
|
2018-10-31 16:01:17 -04:00
|
|
|
}
|
|
|
|
|
output = newMap
|
|
|
|
|
default:
|
|
|
|
|
output = value
|
|
|
|
|
}
|
|
|
|
|
return output
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-27 23:03:14 -08:00
|
|
|
// Flatten flattens a map with potentially nested maps into a flat
|
2018-10-31 16:01:17 -04:00
|
|
|
// map. Only string keys are allowed on both the top-level map and
|
|
|
|
|
// child maps.
|
2023-11-27 23:03:14 -08:00
|
|
|
func Flatten(in map[string]interface{}) map[string]interface{} {
|
2018-10-31 16:01:17 -04:00
|
|
|
newMap := make(map[string]interface{})
|
|
|
|
|
for k, v := range in {
|
|
|
|
|
if flat, ok := flattenValue(v).(map[string]interface{}); ok {
|
|
|
|
|
for flatK, flatV := range flat {
|
|
|
|
|
newMap[k+flatK] = flatV
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
newMap[k] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return newMap
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-27 23:03:14 -08:00
|
|
|
func FlattenMetadata(md Metadata) (map[string]interface{}, error) {
|
2023-11-17 14:55:14 -08:00
|
|
|
var mdMap map[string]interface{}
|
|
|
|
|
jsonBytes, err := json.Marshal(md)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
err = json.Unmarshal(jsonBytes, &mdMap)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-27 23:03:14 -08:00
|
|
|
flat := Flatten(mdMap)
|
|
|
|
|
encodeNonStrings(flat)
|
|
|
|
|
encodeNewLines(flat)
|
2023-11-17 14:55:14 -08:00
|
|
|
return flat, nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-31 16:01:17 -04:00
|
|
|
type token interface{}
|
|
|
|
|
|
|
|
|
|
type mapToken struct {
|
|
|
|
|
key string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type listToken struct {
|
|
|
|
|
position int
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-27 23:03:14 -08:00
|
|
|
// tokenize converts a path generated by Flatten to be used as a key
|
2018-10-31 16:01:17 -04:00
|
|
|
// in the flattened map, and converts it to a slice of tokens
|
|
|
|
|
func tokenize(path string) []token {
|
|
|
|
|
const (
|
2018-10-31 22:44:08 +01:00
|
|
|
StateNormal = 0
|
|
|
|
|
StateMap = iota
|
|
|
|
|
StateList = iota
|
2018-10-31 16:01:17 -04:00
|
|
|
)
|
|
|
|
|
var tokens []token
|
2018-10-31 22:44:08 +01:00
|
|
|
state := StateNormal
|
2018-10-31 16:01:17 -04:00
|
|
|
lastTokenEnd := 0
|
|
|
|
|
i := 0
|
|
|
|
|
finishPrevToken := func() {
|
|
|
|
|
var t token
|
|
|
|
|
switch state {
|
2018-10-31 22:44:08 +01:00
|
|
|
case StateNormal:
|
2018-10-31 16:01:17 -04:00
|
|
|
t = mapToken{path[lastTokenEnd:i]}
|
2018-10-31 22:44:08 +01:00
|
|
|
case StateMap:
|
|
|
|
|
t = mapToken{path[lastTokenEnd+len(mapSeparator) : i]}
|
|
|
|
|
case StateList:
|
|
|
|
|
pos, _ := strconv.Atoi(path[lastTokenEnd+len(listSeparator) : i])
|
2018-10-31 16:01:17 -04:00
|
|
|
t = listToken{pos}
|
|
|
|
|
}
|
|
|
|
|
lastTokenEnd = i
|
|
|
|
|
tokens = append(tokens, t)
|
|
|
|
|
}
|
|
|
|
|
for i < len(path) {
|
2018-10-31 22:44:08 +01:00
|
|
|
if strings.HasPrefix(path[i:], mapSeparator) {
|
2018-10-31 16:01:17 -04:00
|
|
|
finishPrevToken()
|
2018-10-31 22:44:08 +01:00
|
|
|
state = StateMap
|
|
|
|
|
i += len(mapSeparator)
|
|
|
|
|
} else if strings.HasPrefix(path[i:], listSeparator) {
|
2018-10-31 16:01:17 -04:00
|
|
|
finishPrevToken()
|
2018-10-31 22:44:08 +01:00
|
|
|
state = StateList
|
|
|
|
|
i += len(listSeparator)
|
2018-10-31 16:01:17 -04:00
|
|
|
} else {
|
2019-07-08 15:32:33 -07:00
|
|
|
i++
|
2018-10-31 16:01:17 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
finishPrevToken()
|
|
|
|
|
return tokens
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unflatten takes the currentNode, currentToken, nextToken and value
|
|
|
|
|
// and populates currentNode such that currentToken can be considered
|
|
|
|
|
// processed. It inspects nextToken to decide what type to allocate
|
|
|
|
|
// and assign under currentNode.
|
|
|
|
|
func unflatten(currentNode interface{}, currentToken, nextToken token, value interface{}) interface{} {
|
|
|
|
|
switch currentToken := currentToken.(type) {
|
|
|
|
|
case mapToken:
|
|
|
|
|
currentNode := currentNode.(map[string]interface{})
|
|
|
|
|
switch nextToken := nextToken.(type) {
|
|
|
|
|
case mapToken:
|
|
|
|
|
if _, ok := currentNode[currentToken.key]; !ok {
|
|
|
|
|
currentNode[currentToken.key] = make(map[string]interface{})
|
|
|
|
|
}
|
|
|
|
|
next := currentNode[currentToken.key].(map[string]interface{})
|
|
|
|
|
return next
|
|
|
|
|
case listToken:
|
|
|
|
|
if _, ok := currentNode[currentToken.key]; !ok {
|
|
|
|
|
currentNode[currentToken.key] = make([]interface{}, nextToken.position+1)
|
|
|
|
|
}
|
|
|
|
|
next := currentNode[currentToken.key].([]interface{})
|
|
|
|
|
if nextToken.position >= len(next) {
|
|
|
|
|
// Grow the slice and reassign it
|
|
|
|
|
newNext := make([]interface{}, nextToken.position+1)
|
|
|
|
|
copy(newNext, next)
|
|
|
|
|
next = newNext
|
|
|
|
|
currentNode[currentToken.key] = next
|
|
|
|
|
}
|
|
|
|
|
return next
|
|
|
|
|
default:
|
|
|
|
|
currentNode[currentToken.key] = value
|
|
|
|
|
}
|
|
|
|
|
case listToken:
|
|
|
|
|
currentNode := currentNode.([]interface{})
|
|
|
|
|
switch nextToken := nextToken.(type) {
|
|
|
|
|
case mapToken:
|
|
|
|
|
if currentNode[currentToken.position] == nil {
|
|
|
|
|
currentNode[currentToken.position] = make(map[string]interface{})
|
|
|
|
|
}
|
|
|
|
|
next := currentNode[currentToken.position].(map[string]interface{})
|
|
|
|
|
return next
|
|
|
|
|
case listToken:
|
|
|
|
|
if currentNode[currentToken.position] == nil {
|
|
|
|
|
currentNode[currentToken.position] = make([]interface{}, nextToken.position+1)
|
|
|
|
|
}
|
|
|
|
|
next := currentNode[currentToken.position].([]interface{})
|
|
|
|
|
if nextToken.position >= len(next) {
|
|
|
|
|
// Grow the slice and reassign it
|
|
|
|
|
newNext := make([]interface{}, nextToken.position+1)
|
|
|
|
|
copy(newNext, next)
|
|
|
|
|
next = newNext
|
|
|
|
|
currentNode[currentToken.position] = next
|
|
|
|
|
}
|
|
|
|
|
return next
|
|
|
|
|
default:
|
|
|
|
|
currentNode[currentToken.position] = value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-27 23:03:14 -08:00
|
|
|
// Unflatten unflattens a map flattened by Flatten
|
|
|
|
|
func Unflatten(in map[string]interface{}) map[string]interface{} {
|
2018-10-31 16:01:17 -04:00
|
|
|
newMap := make(map[string]interface{})
|
|
|
|
|
for k, v := range in {
|
|
|
|
|
var current interface{} = newMap
|
2018-10-31 22:44:08 +01:00
|
|
|
tokens := append(tokenize(k), nil)
|
|
|
|
|
for i := 0; i < len(tokens)-1; i++ {
|
|
|
|
|
current = unflatten(current, tokens[i], tokens[i+1], v)
|
2018-10-31 16:01:17 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return newMap
|
|
|
|
|
}
|
2023-11-17 14:55:14 -08:00
|
|
|
|
2023-11-27 23:03:14 -08:00
|
|
|
// UnflattenMetadata unflattens a map flattened by FlattenMetadata into Metadata
|
|
|
|
|
func UnflattenMetadata(in map[string]interface{}) (Metadata, error) {
|
|
|
|
|
decodeNewLines(in)
|
|
|
|
|
decodeNonStrings(in)
|
|
|
|
|
m := Unflatten(in)
|
2023-11-17 14:55:14 -08:00
|
|
|
var md Metadata
|
|
|
|
|
jsonBytes, err := json.Marshal(m)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return md, err
|
|
|
|
|
}
|
|
|
|
|
err = json.Unmarshal(jsonBytes, &md)
|
|
|
|
|
return md, err
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-27 23:03:14 -08:00
|
|
|
func decodeNewLines(m map[string]interface{}) {
|
|
|
|
|
for k, v := range m {
|
|
|
|
|
if s, ok := v.(string); ok {
|
|
|
|
|
m[k] = strings.Replace(s, "\\n", "\n", -1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func encodeNewLines(m map[string]interface{}) {
|
|
|
|
|
for k, v := range m {
|
|
|
|
|
if s, ok := v.(string); ok {
|
|
|
|
|
m[k] = strings.Replace(s, "\n", "\\n", -1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// decodeNonStrings will look for known keys that are not strings and decode to the appropriate type
|
|
|
|
|
func decodeNonStrings(m map[string]interface{}) {
|
2023-11-17 14:55:14 -08:00
|
|
|
if v, ok := m["mac_only_encrypted"]; ok {
|
|
|
|
|
m["mac_only_encrypted"] = false
|
|
|
|
|
if v == "true" {
|
|
|
|
|
m["mac_only_encrypted"] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-27 23:03:14 -08:00
|
|
|
// encodeNonStrings will look for known keys that are not strings and will encode it to strings
|
|
|
|
|
func encodeNonStrings(m map[string]interface{}) {
|
2023-11-17 14:55:14 -08:00
|
|
|
if v, found := m["mac_only_encrypted"]; found {
|
|
|
|
|
if vBool, ok := v.(bool); ok {
|
|
|
|
|
m["mac_only_encrypted"] = "false"
|
|
|
|
|
if vBool {
|
|
|
|
|
m["mac_only_encrypted"] = "true"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|