go_service/vendor/github.com/leesper/couchdb-golang/mapping.go

227 lines
5.6 KiB
Go

package couchdb
import (
"encoding/json"
"errors"
"reflect"
)
var (
// ErrSetID for setting ID to document which already has one.
ErrSetID = errors.New("id can only be set on new documents")
// ErrNotStruct for not a struct value
ErrNotStruct = errors.New("value not of struct type")
// ErrNotDocumentEmbedded for not a document-embedded value
ErrNotDocumentEmbedded = errors.New("value not Document-embedded")
zero = reflect.Value{}
)
// Document represents a document object in database.
type Document struct {
id string
rev string
ID string `json:"_id,omitempty"` // for json only, call SetID/GetID instead
Rev string `json:"_rev,omitempty"` // for json only, call GetRev instead
}
// DocumentWithID returns a new Document with ID.
func DocumentWithID(id string) Document {
return Document{
id: id,
}
}
// SetID sets ID for new document or return error.
func (d *Document) SetID(id string) error {
if d.id != "" {
return ErrSetID
}
d.id = id
return nil
}
// GetID returns the document ID.
func (d *Document) GetID() string {
return d.id
}
// SetRev sets revision for document.
func (d *Document) SetRev(rev string) {
d.rev = rev
}
// GetRev returns the document revision.
func (d *Document) GetRev() string {
return d.rev
}
// Store stores the document in specified database.
// obj: a Document-embedded struct value, its id and rev will be updated after stored,
// so caller must pass a pointer value.
func Store(db *Database, obj interface{}) error {
ptrValue := reflect.ValueOf(obj)
if ptrValue.Kind() != reflect.Ptr || ptrValue.Elem().Kind() != reflect.Struct {
return ErrNotStruct
}
if ptrValue.Elem().FieldByName("Document") == zero {
return ErrNotDocumentEmbedded
}
jsonIDField := ptrValue.Elem().FieldByName("ID")
getIDMethod := ptrValue.MethodByName("GetID")
idStr := getIDMethod.Call([]reflect.Value{})[0].Interface().(string)
if idStr != "" {
jsonIDField.SetString(idStr)
}
jsonRevField := ptrValue.Elem().FieldByName("Rev")
getRevMethod := ptrValue.MethodByName("GetRev")
revStr := getRevMethod.Call([]reflect.Value{})[0].Interface().(string)
if revStr != "" {
jsonRevField.SetString(revStr)
}
doc, err := ToJSONCompatibleMap(ptrValue.Elem().Interface())
if err != nil {
return err
}
id, rev, err := db.Save(doc, nil)
if err != nil {
return err
}
setIDMethod := ptrValue.MethodByName("SetID")
setRevMethod := ptrValue.MethodByName("SetRev")
if idStr == "" {
setIDMethod.Call([]reflect.Value{reflect.ValueOf(id)})
}
setRevMethod.Call([]reflect.Value{reflect.ValueOf(rev)})
jsonRevField.SetString(rev)
return nil
}
// Load loads the document in specified database.
func Load(db *Database, docID string, obj interface{}) error {
ptrValue := reflect.ValueOf(obj)
if ptrValue.Kind() != reflect.Ptr || ptrValue.Elem().Kind() != reflect.Struct {
return ErrNotStruct
}
if ptrValue.Elem().FieldByName("Document") == zero {
return ErrNotDocumentEmbedded
}
doc, err := db.Get(docID, nil)
if err != nil {
return err
}
err = FromJSONCompatibleMap(obj, doc)
if err != nil {
return err
}
if id, ok := doc["_id"]; ok {
setIDMethod := ptrValue.MethodByName("SetID")
setIDMethod.Call([]reflect.Value{reflect.ValueOf(id)})
}
if rev, ok := doc["_rev"]; ok {
setRevMethod := ptrValue.MethodByName("SetRev")
setRevMethod.Call([]reflect.Value{reflect.ValueOf(rev)})
}
return nil
}
// FromJSONCompatibleMap constructs a Document-embedded struct from a JSON-compatible map.
func FromJSONCompatibleMap(obj interface{}, docMap map[string]interface{}) error {
ptrValue := reflect.ValueOf(obj)
if ptrValue.Kind() != reflect.Ptr || ptrValue.Elem().Kind() != reflect.Struct {
return ErrNotStruct
}
if ptrValue.Elem().FieldByName("Document") == zero {
return ErrNotDocumentEmbedded
}
data, err := json.Marshal(docMap)
if err != nil {
return err
}
err = json.Unmarshal(data, obj)
if err != nil {
return err
}
if id, ok := docMap["_id"]; ok {
setIDMethod := ptrValue.MethodByName("SetID")
setIDMethod.Call([]reflect.Value{reflect.ValueOf(id)})
}
if rev, ok := docMap["_rev"]; ok {
setRevMethod := ptrValue.MethodByName("SetRev")
setRevMethod.Call([]reflect.Value{reflect.ValueOf(rev)})
}
return nil
}
// ToJSONCompatibleMap converts a Document-embedded struct into a JSON-compatible map,
// e.g. anything that cannot be jsonified will be ignored silently.
func ToJSONCompatibleMap(obj interface{}) (map[string]interface{}, error) {
structValue := reflect.ValueOf(obj)
if structValue.Kind() != reflect.Struct {
return nil, ErrNotStruct
}
zero := reflect.Value{}
if structValue.FieldByName("Document") == zero {
return nil, ErrNotDocumentEmbedded
}
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
doc := map[string]interface{}{}
err = json.Unmarshal(data, &doc)
if err != nil {
return nil, err
}
return doc, nil
}
// ViewField represents a view definition value bound to Document.
type ViewField func() (*ViewDefinition, error)
// NewViewField returns a ViewField function.
// design: the name of the design document.
//
// name: the name of the view.
//
// mapFun: the map function code.
//
// reduceFun: the reduce function code(optional).
//
// language: the name of the programming language used, default is javascript.
//
// wrapper: an optional function for processing the result rows after retrieved.
//
// options: view specific options.
func NewViewField(design, name, mapFun, reduceFun, language string, wrapper func(Row) Row, options map[string]interface{}) ViewField {
f := func() (*ViewDefinition, error) {
return NewViewDefinition(design, name, mapFun, reduceFun, language, wrapper, options)
}
return ViewField(f)
}