1package gocb
2
3import (
4	"encoding/json"
5	"time"
6
7	"github.com/pkg/errors"
8)
9
10// Result is the base type for the return types of operations
11type Result struct {
12	cas Cas
13}
14
15// Cas returns the cas of the result.
16func (d *Result) Cas() Cas {
17	return d.cas
18}
19
20// GetResult is the return type of Get operations.
21type GetResult struct {
22	Result
23	transcoder Transcoder
24	flags      uint32
25	contents   []byte
26	expiry     *time.Duration
27}
28
29// Content assigns the value of the result into the valuePtr using default decoding.
30func (d *GetResult) Content(valuePtr interface{}) error {
31	return d.transcoder.Decode(d.contents, d.flags, valuePtr)
32}
33
34// Expiry returns the expiry value for the result if it available.  Note that a nil
35// pointer indicates that the Expiry was fetched, while a valid pointer to a zero
36// Duration indicates that the document will never expire.
37func (d *GetResult) Expiry() *time.Duration {
38	return d.expiry
39}
40
41func (d *GetResult) fromFullProjection(ops []LookupInSpec, result *LookupInResult, fields []string) error {
42	if len(fields) == 0 {
43		// This is a special case where user specified a full doc fetch with expiration.
44		d.contents = result.contents[0].data
45		return nil
46	}
47
48	if len(result.contents) != 1 {
49		return makeInvalidArgumentsError("fromFullProjection should only be called with 1 subdoc result")
50	}
51
52	resultContent := result.contents[0]
53	if resultContent.err != nil {
54		return resultContent.err
55	}
56
57	var content map[string]interface{}
58	err := json.Unmarshal(resultContent.data, &content)
59	if err != nil {
60		return err
61	}
62
63	newContent := make(map[string]interface{})
64	for _, field := range fields {
65		parts := d.pathParts(field)
66		d.set(parts, newContent, content[field])
67	}
68
69	bytes, err := json.Marshal(newContent)
70	if err != nil {
71		return errors.Wrap(err, "could not marshal result contents")
72	}
73	d.contents = bytes
74
75	return nil
76}
77
78func (d *GetResult) fromSubDoc(ops []LookupInSpec, result *LookupInResult) error {
79	content := make(map[string]interface{})
80
81	for i, op := range ops {
82		err := result.contents[i].err
83		if err != nil {
84			// We return the first error that has occurred, this will be
85			// a SubDocument error and will indicate the real reason.
86			return err
87		}
88
89		parts := d.pathParts(op.path)
90		d.set(parts, content, result.contents[i].data)
91	}
92
93	bytes, err := json.Marshal(content)
94	if err != nil {
95		return errors.Wrap(err, "could not marshal result contents")
96	}
97	d.contents = bytes
98
99	return nil
100}
101
102type subdocPath struct {
103	path    string
104	isArray bool
105}
106
107func (d *GetResult) pathParts(pathStr string) []subdocPath {
108	pathLen := len(pathStr)
109	var elemIdx int
110	var i int
111	var paths []subdocPath
112
113	for i < pathLen {
114		ch := pathStr[i]
115		i++
116
117		if ch == '[' {
118			// opening of an array
119			isArr := false
120			arrayStart := i
121
122			for i < pathLen {
123				arrCh := pathStr[i]
124				if arrCh == ']' {
125					isArr = true
126					i++
127					break
128				} else if arrCh == '.' {
129					i++
130					break
131				}
132				i++
133			}
134
135			if isArr {
136				paths = append(paths, subdocPath{path: pathStr[elemIdx : arrayStart-1], isArray: true})
137			} else {
138				paths = append(paths, subdocPath{path: pathStr[elemIdx:i], isArray: false})
139			}
140			elemIdx = i
141
142			if i < pathLen && pathStr[i] == '.' {
143				i++
144				elemIdx = i
145			}
146		} else if ch == '.' {
147			paths = append(paths, subdocPath{path: pathStr[elemIdx : i-1]})
148			elemIdx = i
149		}
150	}
151
152	if elemIdx != i {
153		// this should only ever be an object as an array would have ended in [...]
154		paths = append(paths, subdocPath{path: pathStr[elemIdx:i]})
155	}
156
157	return paths
158}
159
160func (d *GetResult) set(paths []subdocPath, content interface{}, value interface{}) interface{} {
161	path := paths[0]
162	if len(paths) == 1 {
163		if path.isArray {
164			arr := make([]interface{}, 0)
165			arr = append(arr, value)
166			if _, ok := content.(map[string]interface{}); ok {
167				content.(map[string]interface{})[path.path] = arr
168			} else if _, ok := content.([]interface{}); ok {
169				content = append(content.([]interface{}), arr)
170			} else {
171				logErrorf("Projections encountered a non-array or object content assigning an array")
172			}
173		} else {
174			if _, ok := content.([]interface{}); ok {
175				elem := make(map[string]interface{})
176				elem[path.path] = value
177				content = append(content.([]interface{}), elem)
178			} else {
179				content.(map[string]interface{})[path.path] = value
180			}
181		}
182		return content
183	}
184
185	if path.isArray {
186		if _, ok := content.([]interface{}); ok {
187			var m []interface{}
188			content = append(content.([]interface{}), d.set(paths[1:], m, value))
189			return content
190		} else if cMap, ok := content.(map[string]interface{}); ok {
191			cMap[path.path] = make([]interface{}, 0)
192			cMap[path.path] = d.set(paths[1:], cMap[path.path], value)
193			return content
194
195		} else {
196			logErrorf("Projections encountered a non-array or object content assigning an array")
197		}
198	} else {
199		if arr, ok := content.([]interface{}); ok {
200			m := make(map[string]interface{})
201			m[path.path] = make(map[string]interface{})
202			content = append(arr, m)
203			d.set(paths[1:], m[path.path], value)
204			return content
205		}
206		cMap, ok := content.(map[string]interface{})
207		if !ok {
208			// this isn't possible but the linter won't play nice without it
209			logErrorf("Failed to assert projection content to a map")
210		}
211		cMap[path.path] = make(map[string]interface{})
212		return d.set(paths[1:], cMap[path.path], value)
213	}
214
215	return content
216}
217
218// LookupInResult is the return type for LookupIn.
219type LookupInResult struct {
220	Result
221	contents []lookupInPartial
222}
223
224type lookupInPartial struct {
225	data json.RawMessage
226	err  error
227}
228
229func (pr *lookupInPartial) as(valuePtr interface{}) error {
230	if pr.err != nil {
231		return pr.err
232	}
233
234	if valuePtr == nil {
235		return nil
236	}
237
238	if valuePtr, ok := valuePtr.(*[]byte); ok {
239		*valuePtr = pr.data
240		return nil
241	}
242
243	return json.Unmarshal(pr.data, valuePtr)
244}
245
246func (pr *lookupInPartial) exists() bool {
247	err := pr.as(nil)
248	return err == nil
249}
250
251// ContentAt retrieves the value of the operation by its index. The index is the position of
252// the operation as it was added to the builder.
253func (lir *LookupInResult) ContentAt(idx uint, valuePtr interface{}) error {
254	if idx >= uint(len(lir.contents)) {
255		return makeInvalidArgumentsError("invalid index")
256	}
257	return lir.contents[idx].as(valuePtr)
258}
259
260// Exists verifies that the item at idx exists.
261func (lir *LookupInResult) Exists(idx uint) bool {
262	if idx >= uint(len(lir.contents)) {
263		return false
264	}
265	return lir.contents[idx].exists()
266}
267
268// ExistsResult is the return type of Exist operations.
269type ExistsResult struct {
270	Result
271	docExists bool
272}
273
274// Exists returns whether or not the document exists.
275func (d *ExistsResult) Exists() bool {
276	return d.docExists
277}
278
279// MutationResult is the return type of any store related operations. It contains Cas and mutation tokens.
280type MutationResult struct {
281	Result
282	mt *MutationToken
283}
284
285// MutationToken returns the mutation token belonging to an operation.
286func (mr MutationResult) MutationToken() *MutationToken {
287	return mr.mt
288}
289
290// MutateInResult is the return type of any mutate in related operations.
291// It contains Cas, mutation tokens and any returned content.
292type MutateInResult struct {
293	MutationResult
294	contents []mutateInPartial
295}
296
297type mutateInPartial struct {
298	data json.RawMessage
299}
300
301func (pr *mutateInPartial) as(valuePtr interface{}) error {
302	if valuePtr == nil {
303		return nil
304	}
305
306	if valuePtr, ok := valuePtr.(*[]byte); ok {
307		*valuePtr = pr.data
308		return nil
309	}
310
311	return json.Unmarshal(pr.data, valuePtr)
312}
313
314// ContentAt retrieves the value of the operation by its index. The index is the position of
315// the operation as it was added to the builder.
316func (mir MutateInResult) ContentAt(idx uint, valuePtr interface{}) error {
317	return mir.contents[idx].as(valuePtr)
318}
319
320// CounterResult is the return type of counter operations.
321type CounterResult struct {
322	MutationResult
323	content uint64
324}
325
326// MutationToken returns the mutation token belonging to an operation.
327func (mr CounterResult) MutationToken() *MutationToken {
328	return mr.mt
329}
330
331// Cas returns the Cas value for a document following an operation.
332func (mr CounterResult) Cas() Cas {
333	return mr.cas
334}
335
336// Content returns the new value for the counter document.
337func (mr CounterResult) Content() uint64 {
338	return mr.content
339}
340
341// GetReplicaResult is the return type of GetReplica operations.
342type GetReplicaResult struct {
343	GetResult
344	isReplica bool
345}
346
347// IsReplica returns whether or not this result came from a replica server.
348func (r *GetReplicaResult) IsReplica() bool {
349	return r.isReplica
350}
351