1// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
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//   http://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// author       sigu-399
16// author-github  https://github.com/sigu-399
17// author-mail    sigu.399@gmail.com
18//
19// repository-name  jsonpointer
20// repository-desc  An implementation of JSON Pointer - Go language
21//
22// description    Automated tests on package.
23//
24// created        03-03-2013
25
26package jsonpointer
27
28import (
29	"encoding/json"
30	"fmt"
31	"strconv"
32	"testing"
33
34	"github.com/stretchr/testify/assert"
35)
36
37const (
38	TestDocumentNBItems = 11
39	TestNodeObjNBItems  = 4
40	TestDocumentString  = `{
41"foo": ["bar", "baz"],
42"obj": { "a":1, "b":2, "c":[3,4], "d":[ {"e":9}, {"f":[50,51]} ] },
43"": 0,
44"a/b": 1,
45"c%d": 2,
46"e^f": 3,
47"g|h": 4,
48"i\\j": 5,
49"k\"l": 6,
50" ": 7,
51"m~n": 8
52}`
53)
54
55var testDocumentJSON interface{}
56
57type testStructJSON struct {
58	Foo []string `json:"foo"`
59	Obj struct {
60		A int   `json:"a"`
61		B int   `json:"b"`
62		C []int `json:"c"`
63		D []struct {
64			E int   `json:"e"`
65			F []int `json:"f"`
66		} `json:"d"`
67	} `json:"obj"`
68}
69
70type aliasedMap map[string]interface{}
71
72var testStructJSONDoc testStructJSON
73var testStructJSONPtr *testStructJSON
74
75func init() {
76	json.Unmarshal([]byte(TestDocumentString), &testDocumentJSON)
77	json.Unmarshal([]byte(TestDocumentString), &testStructJSONDoc)
78	testStructJSONPtr = &testStructJSONDoc
79}
80
81func TestEscaping(t *testing.T) {
82
83	ins := []string{`/`, `/`, `/a~1b`, `/a~1b`, `/c%d`, `/e^f`, `/g|h`, `/i\j`, `/k"l`, `/ `, `/m~0n`}
84	outs := []float64{0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8}
85
86	for i := range ins {
87		p, err := New(ins[i])
88		if assert.NoError(t, err, "input: %v", ins[i]) {
89			result, _, err := p.Get(testDocumentJSON)
90			if assert.NoError(t, err, "input: %v", ins[i]) {
91				assert.Equal(t, outs[i], result, "input: %v", ins[i])
92			}
93		}
94	}
95
96}
97
98func TestFullDocument(t *testing.T) {
99
100	in := ``
101
102	p, err := New(in)
103	if err != nil {
104		t.Errorf("New(%v) error %v", in, err.Error())
105	}
106
107	result, _, err := p.Get(testDocumentJSON)
108	if err != nil {
109		t.Errorf("Get(%v) error %v", in, err.Error())
110	}
111
112	if len(result.(map[string]interface{})) != TestDocumentNBItems {
113		t.Errorf("Get(%v) = %v, expect full document", in, result)
114	}
115
116	result, _, err = p.get(testDocumentJSON, nil)
117	if err != nil {
118		t.Errorf("Get(%v) error %v", in, err.Error())
119	}
120
121	if len(result.(map[string]interface{})) != TestDocumentNBItems {
122		t.Errorf("Get(%v) = %v, expect full document", in, result)
123	}
124}
125
126func TestDecodedTokens(t *testing.T) {
127	p, err := New("/obj/a~1b")
128	assert.NoError(t, err)
129	assert.Equal(t, []string{"obj", "a/b"}, p.DecodedTokens())
130}
131
132func TestIsEmpty(t *testing.T) {
133	p, err := New("")
134	assert.NoError(t, err)
135	assert.True(t, p.IsEmpty())
136	p, err = New("/obj")
137	assert.NoError(t, err)
138	assert.False(t, p.IsEmpty())
139}
140
141func TestGetSingle(t *testing.T) {
142	in := `/obj`
143
144	_, err := New(in)
145	assert.NoError(t, err)
146	result, _, err := GetForToken(testDocumentJSON, "obj")
147	assert.NoError(t, err)
148	assert.Len(t, result, TestNodeObjNBItems)
149
150	result, _, err = GetForToken(testStructJSONDoc, "Obj")
151	assert.Error(t, err)
152	assert.Nil(t, result)
153
154	result, _, err = GetForToken(testStructJSONDoc, "Obj2")
155	assert.Error(t, err)
156	assert.Nil(t, result)
157}
158
159type pointableImpl struct {
160	a string
161}
162
163func (p pointableImpl) JSONLookup(token string) (interface{}, error) {
164	if token == "some" {
165		return p.a, nil
166	}
167	return nil, fmt.Errorf("object has no field %q", token)
168}
169
170type pointableMap map[string]string
171
172func (p pointableMap) JSONLookup(token string) (interface{}, error) {
173	if token == "swap" {
174		return p["swapped"], nil
175	}
176
177	v, ok := p[token]
178	if ok {
179		return v, nil
180	}
181
182	return nil, fmt.Errorf("object has no key %q", token)
183}
184
185func TestPointableInterface(t *testing.T) {
186	p := &pointableImpl{"hello"}
187
188	result, _, err := GetForToken(p, "some")
189	assert.NoError(t, err)
190	assert.Equal(t, p.a, result)
191
192	result, _, err = GetForToken(p, "something")
193	assert.Error(t, err)
194	assert.Nil(t, result)
195
196	pm := pointableMap{"swapped": "hello", "a": "world"}
197	result, _, err = GetForToken(pm, "swap")
198	assert.NoError(t, err)
199	assert.Equal(t, pm["swapped"], result)
200
201	result, _, err = GetForToken(pm, "a")
202	assert.NoError(t, err)
203	assert.Equal(t, pm["a"], result)
204}
205
206func TestGetNode(t *testing.T) {
207
208	in := `/obj`
209
210	p, err := New(in)
211	assert.NoError(t, err)
212	result, _, err := p.Get(testDocumentJSON)
213	assert.NoError(t, err)
214	assert.Len(t, result, TestNodeObjNBItems)
215
216	result, _, err = p.Get(aliasedMap(testDocumentJSON.(map[string]interface{})))
217	assert.NoError(t, err)
218	assert.Len(t, result, TestNodeObjNBItems)
219
220	result, _, err = p.Get(testStructJSONDoc)
221	assert.NoError(t, err)
222	assert.Equal(t, testStructJSONDoc.Obj, result)
223
224	result, _, err = p.Get(testStructJSONPtr)
225	assert.NoError(t, err)
226	assert.Equal(t, testStructJSONDoc.Obj, result)
227}
228
229func TestArray(t *testing.T) {
230
231	ins := []string{`/foo/0`, `/foo/0`, `/foo/1`}
232	outs := []string{"bar", "bar", "baz"}
233
234	for i := range ins {
235		p, err := New(ins[i])
236		assert.NoError(t, err)
237
238		result, _, err := p.Get(testStructJSONDoc)
239		assert.NoError(t, err)
240		assert.Equal(t, outs[i], result)
241
242		result, _, err = p.Get(testStructJSONPtr)
243		assert.NoError(t, err)
244		assert.Equal(t, outs[i], result)
245
246		result, _, err = p.Get(testDocumentJSON)
247		assert.NoError(t, err)
248		assert.Equal(t, outs[i], result)
249	}
250}
251
252func TestOtherThings(t *testing.T) {
253	_, err := New("abc")
254	assert.Error(t, err)
255
256	p, err := New("")
257	assert.NoError(t, err)
258	assert.Equal(t, "", p.String())
259
260	p, err = New("/obj/a")
261	assert.Equal(t, "/obj/a", p.String())
262
263	s := Escape("m~n")
264	assert.Equal(t, "m~0n", s)
265	s = Escape("m/n")
266	assert.Equal(t, "m~1n", s)
267
268	p, err = New("/foo/3")
269	assert.NoError(t, err)
270	_, _, err = p.Get(testDocumentJSON)
271	assert.Error(t, err)
272
273	p, err = New("/foo/a")
274	assert.NoError(t, err)
275	_, _, err = p.Get(testDocumentJSON)
276	assert.Error(t, err)
277
278	p, err = New("/notthere")
279	assert.NoError(t, err)
280	_, _, err = p.Get(testDocumentJSON)
281	assert.Error(t, err)
282
283	p, err = New("/invalid")
284	assert.NoError(t, err)
285	_, _, err = p.Get(1234)
286	assert.Error(t, err)
287
288	p, err = New("/foo/1")
289	assert.NoError(t, err)
290	expected := "hello"
291	bbb := testDocumentJSON.(map[string]interface{})["foo"]
292	bbb.([]interface{})[1] = "hello"
293
294	v, _, err := p.Get(testDocumentJSON)
295	assert.NoError(t, err)
296	assert.Equal(t, expected, v)
297
298	esc := Escape("a/")
299	assert.Equal(t, "a~1", esc)
300	unesc := Unescape(esc)
301	assert.Equal(t, "a/", unesc)
302
303	unesc = Unescape("~01")
304	assert.Equal(t, "~1", unesc)
305	assert.Equal(t, "~0~1", Escape("~/"))
306	assert.Equal(t, "~/", Unescape("~0~1"))
307}
308
309func TestObject(t *testing.T) {
310
311	ins := []string{`/obj/a`, `/obj/b`, `/obj/c/0`, `/obj/c/1`, `/obj/c/1`, `/obj/d/1/f/0`}
312	outs := []float64{1, 2, 3, 4, 4, 50}
313
314	for i := range ins {
315
316		p, err := New(ins[i])
317		assert.NoError(t, err)
318
319		result, _, err := p.Get(testDocumentJSON)
320		assert.NoError(t, err)
321		assert.Equal(t, outs[i], result)
322
323		result, _, err = p.Get(testStructJSONDoc)
324		assert.NoError(t, err)
325		assert.EqualValues(t, outs[i], result)
326
327		result, _, err = p.Get(testStructJSONPtr)
328		assert.NoError(t, err)
329		assert.EqualValues(t, outs[i], result)
330	}
331}
332
333type setJsonDocEle struct {
334	B int `json:"b"`
335	C int `json:"c"`
336}
337type setJsonDoc struct {
338	A []struct {
339		B int `json:"b"`
340		C int `json:"c"`
341	} `json:"a"`
342	D int `json:"d"`
343}
344
345type settableDoc struct {
346	Coll settableColl
347	Int  settableInt
348}
349
350func (s settableDoc) MarshalJSON() ([]byte, error) {
351	var res struct {
352		A settableColl `json:"a"`
353		D settableInt  `json:"d"`
354	}
355	res.A = s.Coll
356	res.D = s.Int
357	return json.Marshal(res)
358}
359func (s *settableDoc) UnmarshalJSON(data []byte) error {
360	var res struct {
361		A settableColl `json:"a"`
362		D settableInt  `json:"d"`
363	}
364
365	if err := json.Unmarshal(data, &res); err != nil {
366		return err
367	}
368	s.Coll = res.A
369	s.Int = res.D
370	return nil
371}
372
373// JSONLookup implements an interface to customize json pointer lookup
374func (s settableDoc) JSONLookup(token string) (interface{}, error) {
375	switch token {
376	case "a":
377		return &s.Coll, nil
378	case "d":
379		return &s.Int, nil
380	default:
381		return nil, fmt.Errorf("%s is not a known field", token)
382	}
383}
384
385// JSONLookup implements an interface to customize json pointer lookup
386func (s *settableDoc) JSONSet(token string, data interface{}) error {
387	switch token {
388	case "a":
389		switch dt := data.(type) {
390		case settableColl:
391			s.Coll = dt
392			return nil
393		case *settableColl:
394			if dt != nil {
395				s.Coll = *dt
396			} else {
397				s.Coll = settableColl{}
398			}
399			return nil
400		case []settableCollItem:
401			s.Coll.Items = dt
402			return nil
403		}
404	case "d":
405		switch dt := data.(type) {
406		case settableInt:
407			s.Int = dt
408			return nil
409		case int:
410			s.Int.Value = dt
411			return nil
412		case int8:
413			s.Int.Value = int(dt)
414			return nil
415		case int16:
416			s.Int.Value = int(dt)
417			return nil
418		case int32:
419			s.Int.Value = int(dt)
420			return nil
421		case int64:
422			s.Int.Value = int(dt)
423			return nil
424		default:
425			return fmt.Errorf("invalid type %T for %s", data, token)
426		}
427	}
428	return fmt.Errorf("%s is not a known field", token)
429}
430
431type settableColl struct {
432	Items []settableCollItem
433}
434
435func (s settableColl) MarshalJSON() ([]byte, error) {
436	return json.Marshal(s.Items)
437}
438func (s *settableColl) UnmarshalJSON(data []byte) error {
439	return json.Unmarshal(data, &s.Items)
440}
441
442// JSONLookup implements an interface to customize json pointer lookup
443func (s settableColl) JSONLookup(token string) (interface{}, error) {
444	if tok, err := strconv.Atoi(token); err == nil {
445		return &s.Items[tok], nil
446	}
447	return nil, fmt.Errorf("%s is not a valid index", token)
448}
449
450// JSONLookup implements an interface to customize json pointer lookup
451func (s *settableColl) JSONSet(token string, data interface{}) error {
452	if _, err := strconv.Atoi(token); err == nil {
453		_, err := SetForToken(s.Items, token, data)
454		return err
455	}
456	return fmt.Errorf("%s is not a valid index", token)
457}
458
459type settableCollItem struct {
460	B int `json:"b"`
461	C int `json:"c"`
462}
463
464type settableInt struct {
465	Value int
466}
467
468func (s settableInt) MarshalJSON() ([]byte, error) {
469	return json.Marshal(s.Value)
470}
471func (s *settableInt) UnmarshalJSON(data []byte) error {
472	return json.Unmarshal(data, &s.Value)
473}
474
475func TestSetNode(t *testing.T) {
476
477	jsonText := `{"a":[{"b": 1, "c": 2}], "d": 3}`
478
479	var jsonDocument interface{}
480	if assert.NoError(t, json.Unmarshal([]byte(jsonText), &jsonDocument)) {
481		in := "/a/0/c"
482		p, err := New(in)
483		if assert.NoError(t, err) {
484
485			_, err = p.Set(jsonDocument, 999)
486			assert.NoError(t, err)
487
488			firstNode := jsonDocument.(map[string]interface{})
489			assert.Len(t, firstNode, 2)
490
491			sliceNode := firstNode["a"].([]interface{})
492			assert.Len(t, sliceNode, 1)
493
494			changedNode := sliceNode[0].(map[string]interface{})
495			chNodeVI := changedNode["c"]
496			if assert.IsType(t, 0, chNodeVI) {
497				changedNodeValue := chNodeVI.(int)
498				if assert.Equal(t, 999, changedNodeValue) {
499					assert.Len(t, sliceNode, 1)
500				}
501			}
502		}
503
504		v, err := New("/a/0")
505		if assert.NoError(t, err) {
506			_, err = v.Set(jsonDocument, map[string]interface{}{"b": 3, "c": 8})
507			if assert.NoError(t, err) {
508				firstNode := jsonDocument.(map[string]interface{})
509				assert.Len(t, firstNode, 2)
510
511				sliceNode := firstNode["a"].([]interface{})
512				assert.Len(t, sliceNode, 1)
513				changedNode := sliceNode[0].(map[string]interface{})
514				assert.Equal(t, 3, changedNode["b"])
515				assert.Equal(t, 8, changedNode["c"])
516			}
517		}
518	}
519
520	var structDoc setJsonDoc
521	if assert.NoError(t, json.Unmarshal([]byte(jsonText), &structDoc)) {
522		g, err := New("/a")
523		if assert.NoError(t, err) {
524			_, err = g.Set(&structDoc, []struct {
525				B int `json:"b"`
526				C int `json:"c"`
527			}{{B: 4, C: 7}})
528
529			if assert.NoError(t, err) {
530				assert.Len(t, structDoc.A, 1)
531				changedNode := structDoc.A[0]
532				assert.Equal(t, 4, changedNode.B)
533				assert.Equal(t, 7, changedNode.C)
534			}
535		}
536
537		v, err := New("/a/0")
538		if assert.NoError(t, err) {
539			_, err = v.Set(structDoc, struct {
540				B int `json:"b"`
541				C int `json:"c"`
542			}{B: 3, C: 8})
543
544			if assert.NoError(t, err) {
545				assert.Len(t, structDoc.A, 1)
546				changedNode := structDoc.A[0]
547				assert.Equal(t, 3, changedNode.B)
548				assert.Equal(t, 8, changedNode.C)
549			}
550		}
551
552		p, err := New("/a/0/c")
553		if assert.NoError(t, err) {
554			_, err = p.Set(&structDoc, 999)
555			assert.NoError(t, err)
556			if assert.Len(t, structDoc.A, 1) {
557				assert.Equal(t, 999, structDoc.A[0].C)
558			}
559		}
560	}
561
562	var setDoc settableDoc
563	if assert.NoError(t, json.Unmarshal([]byte(jsonText), &setDoc)) {
564		g, err := New("/a")
565		if assert.NoError(t, err) {
566			_, err = g.Set(&setDoc, []settableCollItem{{B: 4, C: 7}})
567
568			if assert.NoError(t, err) {
569				assert.Len(t, setDoc.Coll.Items, 1)
570				changedNode := setDoc.Coll.Items[0]
571				assert.Equal(t, 4, changedNode.B)
572				assert.Equal(t, 7, changedNode.C)
573			}
574		}
575
576		v, err := New("/a/0")
577		if assert.NoError(t, err) {
578			_, err = v.Set(setDoc, settableCollItem{B: 3, C: 8})
579
580			if assert.NoError(t, err) {
581				assert.Len(t, setDoc.Coll.Items, 1)
582				changedNode := setDoc.Coll.Items[0]
583				assert.Equal(t, 3, changedNode.B)
584				assert.Equal(t, 8, changedNode.C)
585			}
586		}
587
588		p, err := New("/a/0/c")
589		if assert.NoError(t, err) {
590			_, err = p.Set(setDoc, 999)
591			assert.NoError(t, err)
592			if assert.Len(t, setDoc.Coll.Items, 1) {
593				assert.Equal(t, 999, setDoc.Coll.Items[0].C)
594			}
595		}
596	}
597}
598