123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- // Copyright 2015 Google Inc. All rights reserved.
- // Use of this source code is governed by the Apache 2.0
- // license that can be found in the LICENSE file.
- package search
- import (
- "fmt"
- "reflect"
- "strings"
- "sync"
- )
- // ErrFieldMismatch is returned when a field is to be loaded into a different
- // than the one it was stored from, or when a field is missing or unexported in
- // the destination struct.
- type ErrFieldMismatch struct {
- FieldName string
- Reason string
- }
- func (e *ErrFieldMismatch) Error() string {
- return fmt.Sprintf("search: cannot load field %q: %s", e.FieldName, e.Reason)
- }
- // ErrFacetMismatch is returned when a facet is to be loaded into a different
- // type than the one it was stored from, or when a field is missing or
- // unexported in the destination struct. StructType is the type of the struct
- // pointed to by the destination argument passed to Iterator.Next.
- type ErrFacetMismatch struct {
- StructType reflect.Type
- FacetName string
- Reason string
- }
- func (e *ErrFacetMismatch) Error() string {
- return fmt.Sprintf("search: cannot load facet %q into a %q: %s", e.FacetName, e.StructType, e.Reason)
- }
- // structCodec defines how to convert a given struct to/from a search document.
- type structCodec struct {
- // byIndex returns the struct tag for the i'th struct field.
- byIndex []structTag
- // fieldByName returns the index of the struct field for the given field name.
- fieldByName map[string]int
- // facetByName returns the index of the struct field for the given facet name,
- facetByName map[string]int
- }
- // structTag holds a structured version of each struct field's parsed tag.
- type structTag struct {
- name string
- facet bool
- ignore bool
- }
- var (
- codecsMu sync.RWMutex
- codecs = map[reflect.Type]*structCodec{}
- )
- func loadCodec(t reflect.Type) (*structCodec, error) {
- codecsMu.RLock()
- codec, ok := codecs[t]
- codecsMu.RUnlock()
- if ok {
- return codec, nil
- }
- codecsMu.Lock()
- defer codecsMu.Unlock()
- if codec, ok := codecs[t]; ok {
- return codec, nil
- }
- codec = &structCodec{
- fieldByName: make(map[string]int),
- facetByName: make(map[string]int),
- }
- for i, I := 0, t.NumField(); i < I; i++ {
- f := t.Field(i)
- name, opts := f.Tag.Get("search"), ""
- if i := strings.Index(name, ","); i != -1 {
- name, opts = name[:i], name[i+1:]
- }
- ignore := false
- if name == "-" {
- ignore = true
- } else if name == "" {
- name = f.Name
- } else if !validFieldName(name) {
- return nil, fmt.Errorf("search: struct tag has invalid field name: %q", name)
- }
- facet := opts == "facet"
- codec.byIndex = append(codec.byIndex, structTag{name: name, facet: facet, ignore: ignore})
- if facet {
- codec.facetByName[name] = i
- } else {
- codec.fieldByName[name] = i
- }
- }
- codecs[t] = codec
- return codec, nil
- }
- // structFLS adapts a struct to be a FieldLoadSaver.
- type structFLS struct {
- v reflect.Value
- codec *structCodec
- }
- func (s structFLS) Load(fields []Field, meta *DocumentMetadata) error {
- var err error
- for _, field := range fields {
- i, ok := s.codec.fieldByName[field.Name]
- if !ok {
- // Note the error, but keep going.
- err = &ErrFieldMismatch{
- FieldName: field.Name,
- Reason: "no such struct field",
- }
- continue
- }
- f := s.v.Field(i)
- if !f.CanSet() {
- // Note the error, but keep going.
- err = &ErrFieldMismatch{
- FieldName: field.Name,
- Reason: "cannot set struct field",
- }
- continue
- }
- v := reflect.ValueOf(field.Value)
- if ft, vt := f.Type(), v.Type(); ft != vt {
- err = &ErrFieldMismatch{
- FieldName: field.Name,
- Reason: fmt.Sprintf("type mismatch: %v for %v data", ft, vt),
- }
- continue
- }
- f.Set(v)
- }
- if meta == nil {
- return err
- }
- for _, facet := range meta.Facets {
- i, ok := s.codec.facetByName[facet.Name]
- if !ok {
- // Note the error, but keep going.
- if err == nil {
- err = &ErrFacetMismatch{
- StructType: s.v.Type(),
- FacetName: facet.Name,
- Reason: "no matching field found",
- }
- }
- continue
- }
- f := s.v.Field(i)
- if !f.CanSet() {
- // Note the error, but keep going.
- if err == nil {
- err = &ErrFacetMismatch{
- StructType: s.v.Type(),
- FacetName: facet.Name,
- Reason: "unable to set unexported field of struct",
- }
- }
- continue
- }
- v := reflect.ValueOf(facet.Value)
- if ft, vt := f.Type(), v.Type(); ft != vt {
- if err == nil {
- err = &ErrFacetMismatch{
- StructType: s.v.Type(),
- FacetName: facet.Name,
- Reason: fmt.Sprintf("type mismatch: %v for %d data", ft, vt),
- }
- continue
- }
- }
- f.Set(v)
- }
- return err
- }
- func (s structFLS) Save() ([]Field, *DocumentMetadata, error) {
- fields := make([]Field, 0, len(s.codec.fieldByName))
- var facets []Facet
- for i, tag := range s.codec.byIndex {
- if tag.ignore {
- continue
- }
- f := s.v.Field(i)
- if !f.CanSet() {
- continue
- }
- if tag.facet {
- facets = append(facets, Facet{Name: tag.name, Value: f.Interface()})
- } else {
- fields = append(fields, Field{Name: tag.name, Value: f.Interface()})
- }
- }
- return fields, &DocumentMetadata{Facets: facets}, nil
- }
- // newStructFLS returns a FieldLoadSaver for the struct pointer p.
- func newStructFLS(p interface{}) (FieldLoadSaver, error) {
- v := reflect.ValueOf(p)
- if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct {
- return nil, ErrInvalidDocumentType
- }
- codec, err := loadCodec(v.Elem().Type())
- if err != nil {
- return nil, err
- }
- return structFLS{v.Elem(), codec}, nil
- }
- func loadStructWithMeta(dst interface{}, f []Field, meta *DocumentMetadata) error {
- x, err := newStructFLS(dst)
- if err != nil {
- return err
- }
- return x.Load(f, meta)
- }
- func saveStructWithMeta(src interface{}) ([]Field, *DocumentMetadata, error) {
- x, err := newStructFLS(src)
- if err != nil {
- return nil, nil, err
- }
- return x.Save()
- }
- // LoadStruct loads the fields from f to dst. dst must be a struct pointer.
- func LoadStruct(dst interface{}, f []Field) error {
- return loadStructWithMeta(dst, f, nil)
- }
- // SaveStruct returns the fields from src as a slice of Field.
- // src must be a struct pointer.
- func SaveStruct(src interface{}) ([]Field, error) {
- f, _, err := saveStructWithMeta(src)
- return f, err
- }
|