323d495840
Signed-off-by: Michael Mayer <michael@photoprism.app>
190 lines
4.9 KiB
Go
190 lines
4.9 KiB
Go
package form
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
|
|
"github.com/araddon/dateparse"
|
|
|
|
"github.com/photoprism/photoprism/pkg/clean"
|
|
"github.com/photoprism/photoprism/pkg/txt"
|
|
)
|
|
|
|
// Serialize returns a string containing all non-empty fields and values of a struct.
|
|
func Serialize(f interface{}, all bool) string {
|
|
v := reflect.ValueOf(f)
|
|
|
|
if v.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
}
|
|
|
|
if v.Kind() != reflect.Struct {
|
|
return ""
|
|
}
|
|
|
|
q := make([]string, 0, v.NumField())
|
|
|
|
// Iterate through all form fields.
|
|
for i := 0; i < v.NumField(); i++ {
|
|
fieldValue := v.Field(i)
|
|
fieldName := v.Type().Field(i).Tag.Get("form")
|
|
fieldInfo := v.Type().Field(i).Tag.Get("serialize")
|
|
|
|
// Serialize field values as string.
|
|
if fieldName != "" && fieldName != "-" && (fieldInfo != "-" || all) {
|
|
switch t := fieldValue.Interface().(type) {
|
|
case time.Time:
|
|
if val := fieldValue.Interface().(time.Time); !val.IsZero() {
|
|
if val.Hour() == 0 && val.Minute() == 0 {
|
|
q = append(q, fmt.Sprintf("%s:%s", fieldName, val.Format("2006-01-02")))
|
|
} else {
|
|
q = append(q, fmt.Sprintf("%s:\"%s\"", fieldName, val.String()))
|
|
}
|
|
}
|
|
case int, int8, int16, int32, int64:
|
|
if val := fieldValue.Int(); val != 0 {
|
|
q = append(q, fmt.Sprintf("%s:%d", fieldName, val))
|
|
}
|
|
case uint, uint8, uint16, uint32, uint64:
|
|
if val := fieldValue.Uint(); val != 0 {
|
|
q = append(q, fmt.Sprintf("%s:%d", fieldName, val))
|
|
}
|
|
case float32, float64:
|
|
if val := fieldValue.Float(); val != 0 {
|
|
q = append(q, fmt.Sprintf("%s:%f", fieldName, val))
|
|
}
|
|
case string:
|
|
if val := strings.ReplaceAll(fieldValue.String(), "\"", ""); val != "" {
|
|
if strings.ContainsAny(val, " :'()[]-+`") {
|
|
q = append(q, fmt.Sprintf("%s:\"%s\"", fieldName, val))
|
|
} else {
|
|
q = append(q, fmt.Sprintf("%s:%s", fieldName, val))
|
|
}
|
|
}
|
|
case bool:
|
|
if val := fieldValue.Bool(); val {
|
|
q = append(q, fmt.Sprintf("%s:%t", fieldName, fieldValue.Bool()))
|
|
}
|
|
default:
|
|
log.Warnf("form: failed serializing %T %s", t, clean.Token(fieldName))
|
|
}
|
|
}
|
|
}
|
|
|
|
return strings.Join(q, " ")
|
|
}
|
|
|
|
func Unserialize(f SearchForm, q string) (result error) {
|
|
var key, value []rune
|
|
var escaped, isKeyValue bool
|
|
|
|
v := reflect.ValueOf(f)
|
|
|
|
formValues := v.Elem()
|
|
|
|
n := formValues.NumField()
|
|
|
|
fieldNames := make(map[string]string, n)
|
|
|
|
// Iterate through all form fields.
|
|
for i := 0; i < formValues.NumField(); i++ {
|
|
fieldName := formValues.Type().Field(i).Name
|
|
formName := strings.ToLower(formValues.Type().Field(i).Tag.Get("form"))
|
|
formSerialize := strings.ToLower(formValues.Type().Field(i).Tag.Get("serialize"))
|
|
|
|
if fieldName == "" || formSerialize == "-" {
|
|
continue
|
|
} else if formName == "" {
|
|
formName = strings.ToLower(fieldName)
|
|
}
|
|
|
|
fieldNames[formName] = fieldName
|
|
}
|
|
|
|
f.SetQuery("")
|
|
|
|
q = strings.TrimSpace(q) + "\n"
|
|
|
|
var queryStrings []string
|
|
|
|
for _, char := range q {
|
|
if unicode.IsSpace(char) && !escaped {
|
|
if isKeyValue {
|
|
formName := strings.ToLower(string(key))
|
|
fieldName := fieldNames[formName]
|
|
|
|
field := formValues.FieldByNameFunc(func(name string) bool {
|
|
return strings.EqualFold(name, fieldName)
|
|
})
|
|
|
|
stringValue := string(value)
|
|
|
|
if fieldName != "" && fieldName != "-" && field.CanSet() {
|
|
switch field.Interface().(type) {
|
|
case time.Time:
|
|
if timeValue, err := dateparse.ParseAny(stringValue); err != nil {
|
|
result = err
|
|
} else {
|
|
field.Set(reflect.ValueOf(timeValue))
|
|
}
|
|
case float32, float64:
|
|
if floatValue, err := strconv.ParseFloat(stringValue, 64); err != nil {
|
|
result = err
|
|
} else {
|
|
field.SetFloat(floatValue)
|
|
}
|
|
case int, int8, int16, int32, int64:
|
|
if intValue, err := strconv.Atoi(stringValue); err != nil {
|
|
result = err
|
|
} else {
|
|
field.SetInt(int64(intValue))
|
|
}
|
|
case uint, uint8, uint16, uint32, uint64:
|
|
if intValue, err := strconv.Atoi(stringValue); err != nil {
|
|
result = err
|
|
} else {
|
|
field.SetUint(uint64(intValue))
|
|
}
|
|
case string:
|
|
field.SetString(clean.SearchString(stringValue))
|
|
case bool:
|
|
field.SetBool(txt.Bool(stringValue))
|
|
default:
|
|
result = fmt.Errorf("unsupported type: %s", formName)
|
|
}
|
|
} else {
|
|
result = fmt.Errorf("unknown filter: %s", formName)
|
|
}
|
|
} else if len(strings.TrimSpace(string(key))) > 0 {
|
|
queryStrings = append(queryStrings, strings.TrimSpace(string(key)))
|
|
}
|
|
|
|
escaped = false
|
|
isKeyValue = false
|
|
key = key[:0]
|
|
value = value[:0]
|
|
} else if char == ':' && !escaped {
|
|
isKeyValue = true
|
|
} else if char == '"' {
|
|
escaped = !escaped
|
|
} else if isKeyValue {
|
|
value = append(value, char)
|
|
} else {
|
|
key = append(key, unicode.ToLower(char))
|
|
}
|
|
}
|
|
|
|
if len(queryStrings) > 0 {
|
|
f.SetQuery(clean.SearchQuery(strings.Join(queryStrings, " ")))
|
|
}
|
|
|
|
if result != nil {
|
|
log.Warnf("form: failed parsing search query")
|
|
}
|
|
|
|
return result
|
|
}
|