| // 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 |
| } |