github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/google.golang.org/appengine/search/struct.go (about)

     1  // Copyright 2015 Google Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package search
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  	"strings"
    11  	"sync"
    12  )
    13  
    14  // ErrFieldMismatch is returned when a field is to be loaded into a different
    15  // than the one it was stored from, or when a field is missing or unexported in
    16  // the destination struct.
    17  type ErrFieldMismatch struct {
    18  	FieldName string
    19  	Reason    string
    20  }
    21  
    22  func (e *ErrFieldMismatch) Error() string {
    23  	return fmt.Sprintf("search: cannot load field %q: %s", e.FieldName, e.Reason)
    24  }
    25  
    26  // ErrFacetMismatch is returned when a facet is to be loaded into a different
    27  // type than the one it was stored from, or when a field is missing or
    28  // unexported in the destination struct. StructType is the type of the struct
    29  // pointed to by the destination argument passed to Iterator.Next.
    30  type ErrFacetMismatch struct {
    31  	StructType reflect.Type
    32  	FacetName  string
    33  	Reason     string
    34  }
    35  
    36  func (e *ErrFacetMismatch) Error() string {
    37  	return fmt.Sprintf("search: cannot load facet %q into a %q: %s", e.FacetName, e.StructType, e.Reason)
    38  }
    39  
    40  // structCodec defines how to convert a given struct to/from a search document.
    41  type structCodec struct {
    42  	// byIndex returns the struct tag for the i'th struct field.
    43  	byIndex []structTag
    44  
    45  	// fieldByName returns the index of the struct field for the given field name.
    46  	fieldByName map[string]int
    47  
    48  	// facetByName returns the index of the struct field for the given facet name,
    49  	facetByName map[string]int
    50  }
    51  
    52  // structTag holds a structured version of each struct field's parsed tag.
    53  type structTag struct {
    54  	name  string
    55  	facet bool
    56  }
    57  
    58  var (
    59  	codecsMu sync.RWMutex
    60  	codecs   = map[reflect.Type]*structCodec{}
    61  )
    62  
    63  func loadCodec(t reflect.Type) (*structCodec, error) {
    64  	codecsMu.RLock()
    65  	codec, ok := codecs[t]
    66  	codecsMu.RUnlock()
    67  	if ok {
    68  		return codec, nil
    69  	}
    70  
    71  	codecsMu.Lock()
    72  	defer codecsMu.Unlock()
    73  	if codec, ok := codecs[t]; ok {
    74  		return codec, nil
    75  	}
    76  
    77  	codec = &structCodec{
    78  		fieldByName: make(map[string]int),
    79  		facetByName: make(map[string]int),
    80  	}
    81  
    82  	for i, I := 0, t.NumField(); i < I; i++ {
    83  		f := t.Field(i)
    84  		name, opts := f.Tag.Get("search"), ""
    85  		if i := strings.Index(name, ","); i != -1 {
    86  			name, opts = name[:i], name[i+1:]
    87  		}
    88  		// TODO(davidday): Support name=="-" as per datastore.
    89  		if name == "" {
    90  			name = f.Name
    91  		} else if !validFieldName(name) {
    92  			return nil, fmt.Errorf("search: struct tag has invalid field name: %q", name)
    93  		}
    94  		facet := opts == "facet"
    95  		codec.byIndex = append(codec.byIndex, structTag{name: name, facet: facet})
    96  		if facet {
    97  			codec.facetByName[name] = i
    98  		} else {
    99  			codec.fieldByName[name] = i
   100  		}
   101  	}
   102  
   103  	codecs[t] = codec
   104  	return codec, nil
   105  }
   106  
   107  // structFLS adapts a struct to be a FieldLoadSaver.
   108  type structFLS struct {
   109  	v     reflect.Value
   110  	codec *structCodec
   111  }
   112  
   113  func (s structFLS) Load(fields []Field, meta *DocumentMetadata) error {
   114  	var err error
   115  	for _, field := range fields {
   116  		i, ok := s.codec.fieldByName[field.Name]
   117  		if !ok {
   118  			// Note the error, but keep going.
   119  			err = &ErrFieldMismatch{
   120  				FieldName: field.Name,
   121  				Reason:    "no such struct field",
   122  			}
   123  			continue
   124  
   125  		}
   126  		f := s.v.Field(i)
   127  		if !f.CanSet() {
   128  			// Note the error, but keep going.
   129  			err = &ErrFieldMismatch{
   130  				FieldName: field.Name,
   131  				Reason:    "cannot set struct field",
   132  			}
   133  			continue
   134  		}
   135  		v := reflect.ValueOf(field.Value)
   136  		if ft, vt := f.Type(), v.Type(); ft != vt {
   137  			err = &ErrFieldMismatch{
   138  				FieldName: field.Name,
   139  				Reason:    fmt.Sprintf("type mismatch: %v for %v data", ft, vt),
   140  			}
   141  			continue
   142  		}
   143  		f.Set(v)
   144  	}
   145  	if meta == nil {
   146  		return nil
   147  	}
   148  	for _, facet := range meta.Facets {
   149  		i, ok := s.codec.facetByName[facet.Name]
   150  		if !ok {
   151  			// Note the error, but keep going.
   152  			if err == nil {
   153  				err = &ErrFacetMismatch{
   154  					StructType: s.v.Type(),
   155  					FacetName:  facet.Name,
   156  					Reason:     "no matching field found",
   157  				}
   158  			}
   159  			continue
   160  		}
   161  		f := s.v.Field(i)
   162  		if !f.CanSet() {
   163  			// Note the error, but keep going.
   164  			if err == nil {
   165  				err = &ErrFacetMismatch{
   166  					StructType: s.v.Type(),
   167  					FacetName:  facet.Name,
   168  					Reason:     "unable to set unexported field of struct",
   169  				}
   170  			}
   171  			continue
   172  		}
   173  		v := reflect.ValueOf(facet.Value)
   174  		if ft, vt := f.Type(), v.Type(); ft != vt {
   175  			if err == nil {
   176  				err = &ErrFacetMismatch{
   177  					StructType: s.v.Type(),
   178  					FacetName:  facet.Name,
   179  					Reason:     fmt.Sprintf("type mismatch: %v for %d data", ft, vt),
   180  				}
   181  				continue
   182  			}
   183  		}
   184  		f.Set(v)
   185  	}
   186  	return err
   187  }
   188  
   189  func (s structFLS) Save() ([]Field, *DocumentMetadata, error) {
   190  	fields := make([]Field, 0, len(s.codec.fieldByName))
   191  	var facets []Facet
   192  	for i, tag := range s.codec.byIndex {
   193  		f := s.v.Field(i)
   194  		if !f.CanSet() {
   195  			continue
   196  		}
   197  		if tag.facet {
   198  			facets = append(facets, Facet{Name: tag.name, Value: f.Interface()})
   199  		} else {
   200  			fields = append(fields, Field{Name: tag.name, Value: f.Interface()})
   201  		}
   202  	}
   203  	return fields, &DocumentMetadata{Facets: facets}, nil
   204  }
   205  
   206  // newStructFLS returns a FieldLoadSaver for the struct pointer p.
   207  func newStructFLS(p interface{}) (FieldLoadSaver, error) {
   208  	v := reflect.ValueOf(p)
   209  	if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct {
   210  		return nil, ErrInvalidDocumentType
   211  	}
   212  	codec, err := loadCodec(v.Elem().Type())
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	return structFLS{v.Elem(), codec}, nil
   217  }
   218  
   219  func loadStructWithMeta(dst interface{}, f []Field, meta *DocumentMetadata) error {
   220  	x, err := newStructFLS(dst)
   221  	if err != nil {
   222  		return err
   223  	}
   224  	return x.Load(f, meta)
   225  }
   226  
   227  func saveStructWithMeta(src interface{}) ([]Field, *DocumentMetadata, error) {
   228  	x, err := newStructFLS(src)
   229  	if err != nil {
   230  		return nil, nil, err
   231  	}
   232  	return x.Save()
   233  }
   234  
   235  // LoadStruct loads the fields from f to dst. dst must be a struct pointer.
   236  func LoadStruct(dst interface{}, f []Field) error {
   237  	return loadStructWithMeta(dst, f, nil)
   238  }
   239  
   240  // SaveStruct returns the fields from src as a slice of Field.
   241  // src must be a struct pointer.
   242  func SaveStruct(src interface{}) ([]Field, error) {
   243  	f, _, err := saveStructWithMeta(src)
   244  	return f, err
   245  }