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