Adding upstream version 0.28.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
88f1d47ab6
commit
e28c88ef14
933 changed files with 194711 additions and 0 deletions
184
tools/picker/pick.go
Normal file
184
tools/picker/pick.go
Normal file
|
@ -0,0 +1,184 @@
|
|||
package picker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/search"
|
||||
"github.com/pocketbase/pocketbase/tools/tokenizer"
|
||||
)
|
||||
|
||||
// Pick converts data into a []any, map[string]any, etc. (using json marshal->unmarshal)
|
||||
// containing only the fields from the parsed rawFields expression.
|
||||
//
|
||||
// rawFields is a comma separated string of the fields to include.
|
||||
// Nested fields should be listed with dot-notation.
|
||||
// Fields value modifiers are also supported using the `:modifier(args)` format (see Modifiers).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// data := map[string]any{"a": 1, "b": 2, "c": map[string]any{"c1": 11, "c2": 22}}
|
||||
// Pick(data, "a,c.c1") // map[string]any{"a": 1, "c": map[string]any{"c1": 11}}
|
||||
func Pick(data any, rawFields string) (any, error) {
|
||||
parsedFields, err := parseFields(rawFields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// marshalize the provided data to ensure that the related json.Marshaler
|
||||
// implementations are invoked, and then convert it back to a plain
|
||||
// json value that we can further operate on.
|
||||
//
|
||||
// @todo research other approaches to avoid the double serialization
|
||||
// ---
|
||||
encoded, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var decoded any
|
||||
if err := json.Unmarshal(encoded, &decoded); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ---
|
||||
|
||||
// special cases to preserve the same fields format when used with single item or search results data.
|
||||
var isSearchResult bool
|
||||
switch data.(type) {
|
||||
case search.Result, *search.Result:
|
||||
isSearchResult = true
|
||||
}
|
||||
|
||||
if isSearchResult {
|
||||
if decodedMap, ok := decoded.(map[string]any); ok {
|
||||
pickParsedFields(decodedMap["items"], parsedFields)
|
||||
}
|
||||
} else {
|
||||
pickParsedFields(decoded, parsedFields)
|
||||
}
|
||||
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
func parseFields(rawFields string) (map[string]Modifier, error) {
|
||||
t := tokenizer.NewFromString(rawFields)
|
||||
|
||||
fields, err := t.ScanAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]Modifier, len(fields))
|
||||
|
||||
for _, f := range fields {
|
||||
parts := strings.SplitN(strings.TrimSpace(f), ":", 2)
|
||||
|
||||
if len(parts) > 1 {
|
||||
m, err := initModifer(parts[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[parts[0]] = m
|
||||
} else {
|
||||
result[parts[0]] = nil
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func pickParsedFields(data any, fields map[string]Modifier) error {
|
||||
switch v := data.(type) {
|
||||
case map[string]any:
|
||||
pickMapFields(v, fields)
|
||||
case []map[string]any:
|
||||
for _, item := range v {
|
||||
if err := pickMapFields(item, fields); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case []any:
|
||||
if len(v) == 0 {
|
||||
return nil // nothing to pick
|
||||
}
|
||||
|
||||
if _, ok := v[0].(map[string]any); !ok {
|
||||
return nil // for now ignore non-map values
|
||||
}
|
||||
|
||||
for _, item := range v {
|
||||
if err := pickMapFields(item.(map[string]any), fields); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pickMapFields(data map[string]any, fields map[string]Modifier) error {
|
||||
if len(fields) == 0 {
|
||||
return nil // nothing to pick
|
||||
}
|
||||
|
||||
if m, ok := fields["*"]; ok {
|
||||
// append all missing root level data keys
|
||||
for k := range data {
|
||||
var exists bool
|
||||
|
||||
for f := range fields {
|
||||
if strings.HasPrefix(f+".", k+".") {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !exists {
|
||||
fields[k] = m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DataLoop:
|
||||
for k := range data {
|
||||
matchingFields := make(map[string]Modifier, len(fields))
|
||||
for f, m := range fields {
|
||||
if strings.HasPrefix(f+".", k+".") {
|
||||
matchingFields[f] = m
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(matchingFields) == 0 {
|
||||
delete(data, k)
|
||||
continue DataLoop
|
||||
}
|
||||
|
||||
// remove the current key from the matching fields path
|
||||
for f, m := range matchingFields {
|
||||
remains := strings.TrimSuffix(strings.TrimPrefix(f+".", k+"."), ".")
|
||||
|
||||
// final key
|
||||
if remains == "" {
|
||||
if m != nil {
|
||||
var err error
|
||||
data[k], err = m.Modify(data[k])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue DataLoop
|
||||
}
|
||||
|
||||
// cleanup the old field key and continue with the rest of the field path
|
||||
delete(matchingFields, f)
|
||||
matchingFields[remains] = m
|
||||
}
|
||||
|
||||
if err := pickParsedFields(data[k], matchingFields); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue