mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-13 06:09:21 -04:00
SharePoint's default for a text property is word breaking, so ours is too: every keyword field gets the _words sibling unless it opts out with NoWordBreaker, which now carries SharePoint's polarity as well. Artist, album, camera model and the other facets match by word like name and title; tags and favorites stay one label, ids, paths and the mime type one value.
93 lines
3.0 KiB
Go
93 lines
3.0 KiB
Go
package mapping
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Validate returns an error if any override key does not match a known field
|
|
// name in t. Top-level fields are identified by their json-tag name; nested
|
|
// named struct fields are reachable as "parent.child". Embedded (anonymous)
|
|
// structs are flattened, so their fields sit at the parent level (as with
|
|
// encoding/json).
|
|
func Validate(t reflect.Type, overrides map[string]FieldOpts) error {
|
|
if len(overrides) == 0 {
|
|
return nil
|
|
}
|
|
fields := collectFields(t, "")
|
|
var unknown, miscased, unbroken []string
|
|
for k, opts := range overrides {
|
|
goType, ok := fields[k]
|
|
if !ok {
|
|
unknown = append(unknown, k)
|
|
continue
|
|
}
|
|
if opts.NoWordBreaker != nil && !*opts.NoWordBreaker && !effectivelyKeyword(opts, goType) {
|
|
unbroken = append(unbroken, k)
|
|
}
|
|
// CaseInsensitive routes queries to a <field>_lowercase sibling, which is
|
|
// only generated for keyword/path fields; on any other type the query
|
|
// would target a non-existent field and silently match nothing. Use the
|
|
// effective type (override, else the inferred Go type), since an override
|
|
// with no explicit Type still infers keyword/numeric/... from the field.
|
|
if opts.CaseInsensitive != nil && *opts.CaseInsensitive && !effectivelyCased(opts, goType) {
|
|
miscased = append(miscased, k)
|
|
}
|
|
}
|
|
if len(unknown) > 0 {
|
|
sort.Strings(unknown)
|
|
return fmt.Errorf("mapping: unknown override keys: %s", strings.Join(unknown, ", "))
|
|
}
|
|
if len(miscased) > 0 {
|
|
sort.Strings(miscased)
|
|
return fmt.Errorf("mapping: CaseInsensitive is only valid on keyword/path fields: %s", strings.Join(miscased, ", "))
|
|
}
|
|
if len(unbroken) > 0 {
|
|
sort.Strings(unbroken)
|
|
return fmt.Errorf("mapping: NoWordBreaker is only valid on keyword fields: %s", strings.Join(unbroken, ", "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// effectivelyCased reports whether a field is keyword/path (the only types that
|
|
// get a _lowercase sibling), from the override type or the inferred Go type.
|
|
func effectivelyCased(opts FieldOpts, goType reflect.Type) bool {
|
|
eff := opts.Type
|
|
if eff == "" && goType != nil {
|
|
eff = inferType(goType)
|
|
}
|
|
return eff == TypeKeyword || eff == TypePath
|
|
}
|
|
|
|
// effectivelyKeyword reports whether a field is a keyword, the only type
|
|
// NoWordBreaker applies to.
|
|
func effectivelyKeyword(opts FieldOpts, goType reflect.Type) bool {
|
|
eff := opts.Type
|
|
if eff == "" && goType != nil {
|
|
eff = inferType(goType)
|
|
}
|
|
return eff == TypeKeyword
|
|
}
|
|
|
|
// collectFields maps every known field name (nested as "parent.child") to its Go
|
|
// type. Embedded structs are flattened, matching encoding/json.
|
|
func collectFields(t reflect.Type, prefix string) map[string]reflect.Type {
|
|
out := map[string]reflect.Type{}
|
|
_ = walkFields(t, func(fi fieldInfo) error {
|
|
key := fi.Name
|
|
if prefix != "" {
|
|
key = prefix + "." + fi.Name
|
|
}
|
|
out[key] = fi.GoField.Type
|
|
if sub := structType(fi.GoField.Type); sub != nil {
|
|
for k, v := range collectFields(sub, key) {
|
|
out[k] = v
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
return out
|
|
}
|