github.com/cornelk/go-cloud@v0.17.1/docstore/driver/document.go (about) 1 // Copyright 2019 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package driver 16 17 import ( 18 "reflect" 19 20 "github.com/cornelk/go-cloud/docstore/internal/fields" 21 "github.com/cornelk/go-cloud/gcerrors" 22 "github.com/cornelk/go-cloud/internal/gcerr" 23 ) 24 25 // A Document is a lightweight wrapper around either a map[string]interface{} or a 26 // struct pointer. It provides operations to get and set fields and field paths. 27 type Document struct { 28 Origin interface{} // the argument to NewDocument 29 m map[string]interface{} // nil if it's a *struct 30 s reflect.Value // the struct reflected 31 fields fields.List // for structs 32 } 33 34 // NewDocument creates a new document from doc, which must be a non-nil 35 // map[string]interface{} or struct pointer. 36 func NewDocument(doc interface{}) (Document, error) { 37 if doc == nil { 38 return Document{}, gcerr.Newf(gcerr.InvalidArgument, nil, "document cannot be nil") 39 } 40 if m, ok := doc.(map[string]interface{}); ok { 41 if m == nil { 42 return Document{}, gcerr.Newf(gcerr.InvalidArgument, nil, "document map cannot be nil") 43 } 44 return Document{Origin: doc, m: m}, nil 45 } 46 v := reflect.ValueOf(doc) 47 t := v.Type() 48 if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct { 49 return Document{}, gcerr.Newf(gcerr.InvalidArgument, nil, "expecting *struct or map[string]interface{}, got %s", t) 50 } 51 t = t.Elem() 52 if v.IsNil() { 53 return Document{}, gcerr.Newf(gcerr.InvalidArgument, nil, "document struct pointer cannot be nil") 54 } 55 fields, err := fieldCache.Fields(t) 56 if err != nil { 57 return Document{}, err 58 } 59 return Document{Origin: doc, s: v.Elem(), fields: fields}, nil 60 } 61 62 // GetField returns the value of the named document field. 63 func (d Document) GetField(field string) (interface{}, error) { 64 if d.m != nil { 65 x, ok := d.m[field] 66 if !ok { 67 return nil, gcerr.Newf(gcerr.NotFound, nil, "field %q not found in map", field) 68 } 69 return x, nil 70 } else { 71 v, err := d.structField(field) 72 if err != nil { 73 return nil, err 74 } 75 return v.Interface(), nil 76 } 77 } 78 79 // getDocument gets the value of the given field path, which must be a document. 80 // If create is true, it creates intermediate documents as needed. 81 func (d Document) getDocument(fp []string, create bool) (Document, error) { 82 if len(fp) == 0 { 83 return d, nil 84 } 85 x, err := d.GetField(fp[0]) 86 if err != nil { 87 if create && gcerrors.Code(err) == gcerrors.NotFound { 88 // TODO(jba): create the right type for the struct field. 89 x = map[string]interface{}{} 90 if err := d.SetField(fp[0], x); err != nil { 91 return Document{}, err 92 } 93 } else { 94 return Document{}, err 95 } 96 } 97 d2, err := NewDocument(x) 98 if err != nil { 99 return Document{}, err 100 } 101 return d2.getDocument(fp[1:], create) 102 } 103 104 // Get returns the value of the given field path in the document. 105 func (d Document) Get(fp []string) (interface{}, error) { 106 d2, err := d.getDocument(fp[:len(fp)-1], false) 107 if err != nil { 108 return nil, err 109 } 110 return d2.GetField(fp[len(fp)-1]) 111 } 112 113 func (d Document) structField(name string) (reflect.Value, error) { 114 // We do case-insensitive match here to cover the MongoDB's lowercaseFields 115 // option. 116 f := d.fields.MatchFold(name) 117 if f == nil { 118 return reflect.Value{}, gcerr.Newf(gcerr.NotFound, nil, "field %q not found in struct type %s", name, d.s.Type()) 119 } 120 fv, ok := fieldByIndex(d.s, f.Index) 121 if !ok { 122 return reflect.Value{}, gcerr.Newf(gcerr.InvalidArgument, nil, "nil embedded pointer; cannot get field %q from %s", 123 name, d.s.Type()) 124 } 125 return fv, nil 126 } 127 128 // Set sets the value of the field path in the document. 129 // This creates sub-maps as necessary, if possible. 130 func (d Document) Set(fp []string, val interface{}) error { 131 d2, err := d.getDocument(fp[:len(fp)-1], true) 132 if err != nil { 133 return err 134 } 135 return d2.SetField(fp[len(fp)-1], val) 136 } 137 138 // SetField sets the field to value in the document. 139 func (d Document) SetField(field string, value interface{}) error { 140 if d.m != nil { 141 d.m[field] = value 142 return nil 143 } 144 v, err := d.structField(field) 145 if err != nil { 146 return err 147 } 148 if !v.CanSet() { 149 return gcerr.Newf(gcerr.InvalidArgument, nil, "cannot set field %s in struct of type %s: not addressable", 150 field, d.s.Type()) 151 } 152 v.Set(reflect.ValueOf(value)) 153 return nil 154 } 155 156 // FieldNames returns names of the top-level fields of d. 157 func (d Document) FieldNames() []string { 158 var names []string 159 if d.m != nil { 160 for k := range d.m { 161 names = append(names, k) 162 } 163 } else { 164 for _, f := range d.fields { 165 names = append(names, f.Name) 166 } 167 } 168 return names 169 } 170 171 // Encode encodes the document using the given Encoder. 172 func (d Document) Encode(e Encoder) error { 173 if d.m != nil { 174 return encodeMap(reflect.ValueOf(d.m), e) 175 } 176 return encodeStructWithFields(d.s, d.fields, e) 177 } 178 179 // Decode decodes the document using the given Decoder. 180 func (d Document) Decode(dec Decoder) error { 181 if d.m != nil { 182 return decodeMap(reflect.ValueOf(d.m), dec) 183 } 184 return decodeStruct(d.s, dec) 185 } 186 187 // HasField returns whether or not d has a certain field. 188 func (d Document) HasField(field string) bool { 189 return d.hasField(field, true) 190 } 191 192 // HasFieldFold is like HasField but matches case-insensitively for struct 193 // field. 194 func (d Document) HasFieldFold(field string) bool { 195 return d.hasField(field, false) 196 } 197 198 func (d Document) hasField(field string, exactMatch bool) bool { 199 if d.m != nil { 200 _, ok := d.m[field] 201 return ok 202 } 203 if exactMatch { 204 return d.fields.MatchExact(field) != nil 205 } 206 return d.fields.MatchFold(field) != nil 207 }